首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言函數動態調用實現(參考 UnderC 專案)

     編輯:關於C語言

引言

在 C 語言中實現函數的動態調用(即在運行時根據名稱或地址調用任意函數)是一項進階技術,常用於解釋器、插件系統或腳本引擎。本文參考開源專案 UnderC 的實現,展示如何在 Windows 平台上透過內嵌組合語言處理不同調用慣例(stdcall、cdecl)、參數壓棧、返回值處理以及成員函數調用。

核心組件

調用標誌常數

const int DC_STDCALL = 0;    // 標準調用
const int DC_CDECL   = 1;    // C 語言預設調用
const int DC_QWORD   = 2;    // 64 位返回值(double/__int64)
const int DC_NOWORD  = 4;
const int DC_RET_OBJ = 8;    // 返回物件
const int DC_RET_VAL = 16 + 8; // 返回值於寄存器
void *gObjectReturnPtr = NULL; // 保留欄位

核心調用函數 callfn

此函數使用內嵌組合語言(MSVC 風格)手動構建堆棧幀:

  • 將參數按逆序壓入堆棧
  • 支援 thiscall(將 this 指針置於 ECX)
  • 根據標誌決定是否由調用者清理堆棧(cdecl)
  • 處理 32 位、64 位及浮點返回值
void callfn(void* fn, int args[], int argc, void *optr, int flags, void *buff) {
    int sz = sizeof(int) * argc;
    _asm {
        mov ecx, argc
        mov ebx, args
        // 逆序壓棧
    a_loop:
        cmp ecx, 0
        jz a_out
        mov eax, [ebx + 4*ecx]
        push eax
        dec ecx
        jmp a_loop
    a_out:
        mov ecx, optr          // thiscall: this 指針
        call fn                // 調用目標函數
        // cdecl 需調用者清理堆棧
        mov ecx, flags
        test ecx, DC_CDECL
        jz a_over
        add esp, sz
    a_over:
        // 返回值處理...
    }
}

封裝類別:Function 與 DllModule

Function 類別封裝函數地址、調用慣例、this 指針及返回值緩衝區,並重載 operator() 以提供可變參數調用介面:

class Function {
    void *addr;
    int flags;
    void *thisPtr;
    union CalRet { __int64 ll; struct { int lo, hi; } i2; char b8[8]; } ret;
public:
    Function(void *func, int flag) : addr(func), flags(flag), thisPtr(NULL) {}
    Function(HMODULE hmod, const char *name, int flag) 
        : addr(GetProcAddress(hmod, name)), flags(flag), thisPtr(NULL) {}
    Function(void *func, void *thisPtr, int flag) 
        : addr(func), thisPtr(thisPtr), flags(flag) {}
    int operator()(int nargs, ...) {
        callfn(addr, &nargs, nargs, thisPtr, flags, ret.b8);
        return ret.i2.lo;
    }
};

DllModule 封裝 LoadLibrary/FreeLibrary,提供 cFunc(cdecl)與 winFunc(stdcall)快捷建立 Function 物件:

class DllModule {
    HMODULE hmodule;
    char dllName[128];
public:
    DllModule(const char *name) { 
        hmodule = LoadLibrary(name); 
        strncpy(dllName, name, 127); 
    }
    ~DllModule() { FreeLibrary(hmodule); }
    void* get(const char *name) { return GetProcAddress(hmodule, name); }
    Function cFunc(const char *name, void *thisPtr = NULL) 
        { return Function(get(name), thisPtr, DC_CDECL); }
    Function winFunc(const char *name, void *thisPtr = NULL) 
        { return Function(get(name), thisPtr, DC_STDCALL); }
};

使用範例

int main() {
    DllModule cdll("msvcrt.dll");
    cdll.cFunc("printf")(2, "Hello, %s\n", "baby");  // cdecl
    DllModule user32("user32.dll");
    user32.winFunc("MessageBoxW")(4, 0, L"Hello", L"OK", 0); // stdcall

    // 成員函數調用
    class A { public: void show(char *msg) { printf("msg:%s\n", msg); } };
    A a;
    // 取得成員函數地址需特殊處理
    template<class T> void* addressof(T t) { void *ret; __asm { mov eax, t; mov ret, eax } return ret; }
    Function(addressof(A::show), &a, DC_STDCALL)(1, "Class function call");
    return 0;
}

關鍵點總結

  1. 手動堆棧操作可跨越調用慣例差異。
  2. 成員函數調用需將 this 放入 ECX(MSVC thiscall)。
  3. 返回值處理需區分整數、浮點、結構體等情況。
  4. 此技術為動態語言綁定、插件架構奠定基礎。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved