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

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

     編輯:關於C

前言

本文展示一個在 Windows x86 平台上,透過內嵌組合語言實現的 C 函數動態調用機制。可支援 __stdcall、__cdecl、thiscall 等呼叫慣例,並能處理整數、浮點數、結構體回傳值。核心參考自 UnderC 直譯器原始碼。

核心組合語言調用引擎

// 呼叫慣例旗標
const int DC_STDCALL = 0, DC_CDECL = 1, DC_QWORD = 2, DC_NOWORD = 4, DC_RET_OBJ = 8, DC_RET_VAL = 24;

void *gObjectReturnPtr = NULL;

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:
        // thiscall: this 指標放入 ecx
        mov ecx, optr
        call fn
        // cdecl 需手動清理堆疊
        mov ecx, flags
        test ecx, DC_CDECL
        jz a_over
        add esp, sz
        a_over:
        // 處理回傳值
        test ecx, DC_RET_OBJ
        jz a_again
        cmp ecx, DC_RET_VAL
        jl a_skip
        // GCC 結構體回傳約定 (edx:eax)
        mov ebx, gObjectReturnPtr
        mov [ebx], eax
        mov [ebx+4], edx
        jmp a_finish
        a_skip:
        sub esp, 4
        a_again:
        mov ebx, buff
        test ecx, DC_QWORD
        jnz a_dbl
        mov dword ptr [ebx], eax
        jmp a_finish
        a_dbl:
        fstp qword ptr [ebx]
        a_finish:
    }
}

封裝類別:Function 與 DllModule

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;
    }
};

class DllModule {
    HMODULE hmod; char dllName[128];
public:
    DllModule(const char *name) { hmod = LoadLibrary(name); strncpy(dllName, name, 127); }
    ~DllModule() { FreeLibrary(hmod); }
    void* get(const char *name) { return GetProcAddress(hmod, 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");
    // cdecl: printf
    cdll.cFunc("printf")(2, "Hello, %s\n", "baby");

    DllModule user32("user32.dll");
    // stdcall: MessageBoxW
    user32.winFunc("MessageBoxW")(4, 0, L"Hello", L"OK", 0);

    // cdecl: strstr
    cdll.cFunc("strstr")(2, "Caller has already cleaned the stack", "has");

    // thiscall: 成員函數
    class A { public: void show(char *msg) { printf("msg:%s\n", msg); } };
    A a;
    Function(addressof(A::show), &a, DC_STDCALL)(1, "Class function call");

    // stdcall: FindWindowW
    user32.winFunc("FindWindowW")(2, NULL, L"Google - Chrome");

    return 0;
}

// 取得成員函數位址 (MSVC 專用)
template<class T> void* addressof(T t) {
    void *ret; __asm { mov eax, t; mov ret, eax; } return ret;
}

關鍵技術點

  1. 參數壓堆順序:x86 呼叫慣例皆為右至左 (Right-to-Left)。
  2. 堆疊平衡:__cdecl 由呼叫者清理 (add esp, sz);__stdcall/thiscall 由被呼叫者清理 (RET n)。
  3. thiscall:this 指標置於 ECX (MSVC 約定)。
  4. 回傳值:整數/指標在 EAX;64 位整數/浮點數在 EDX:EAX 或 ST(0);大型結構體由呼叫者配置空間並傳入隱藏指標 (GCC 約定)。
  5. 可變參數:利用 operator()(int nargs, ...) 配合 &nargs 取得參數陣列起始位址。

適用場景與限制

  • 適用於 x86 (32-bit) Windows,需 MSVC 編譯器 (內嵌組語語法)。
  • x64 平台呼叫慣例完全不同 (前四參數用 RCX/RDX/R8/R9,其餘用堆疊),此程式碼**不適用**。
  • 僅供教學與特定舊系統維護參考,現代專案建議使用 libffi、Boost.TypeErasure 或 C++17 std::any/std::function 等型別安全方案。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved