首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

C++ 跨 DLL 成員函數指標回調:呼叫慣例不匹配導致棧破壞的修正

     編輯:程式設計綜合問答

跨 DLL 使用成員函數指標回調時,若函數指標型別定義與實際函式簽章不一致,會導致呼叫慣例錯誤、棧框架破壞,進而造成資料異常或崩潰。

問題代碼

// Object.h
typedef void (Object::*CallBack)(...); // ❌ 省略號 ... 表示可變參數,呼叫慣例為 cdecl

// 實際回調函數
void App::onCall(Derive* d); // 固定單一參數,預設 thiscall (MSVC)

根因

  • void (Object::*)(...) 是 可變參數成員函數指標,編譯器生成 cdecl 呼叫代碼 (呼叫者清理棧)。
  • void (Object::*)(Derive*) 是 固定參數成員函數指標,MSVC 使用 thiscall (ECX 傳 this,被呼叫者清理棧)。
  • 跨 DLL 邊界時,呼叫端與被呼叫端對棧清理責任認知不一致 → 棧指針錯位 → 後續資料讀取錯誤 (觀察到 D 的資料「變了」)。

修正方案:統一函數指標型別簽章

// Object.h 修正
typedef void (Object::*CallBack)(void* p); // ✅ 固定單一 void* 參數

// 宏調整
#define EV_CB(_obj,_func) EventHandler{_obj, (CallBack)&_func}
#define EV_INVOKE(_cb, p) do { \
    if (_cb.object && _cb.method) { \
        (_cb.object->*_cb.method)(p); \
    } \
} while(0)

// 使用時
EV_INVOKE(handler, static_cast<void*>(derivePtr));

進階建議

  • 避免跨 DLL 傳遞 C++ 物件/成員函數指標:ABI 不穩定 (編譯器版本、編譯選項、CRT 版本皆影響)。
  • 改用 C 風格介面:extern "C" typedef void (*Callback)(void* ctx, void* data);,上下文指針傳遞 this,DLL 內部再 static_cast 呼叫成員函數。
  • 或採用 COM/介面類:純虛擬類跨 DLL 安全 (vtable 布局固定)。
  • 若必須跨 DLL 用 C++,確保雙方用相同編譯器、相同 CRT (/MD 或 /MT)、相同結構對齊,並導出/導入完整類別而非單獨函數指標。

核心教訓:函數指標型別必須與目標函數簽章完全一致,跨模組邊界更嚴格要求 ABI 相容。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved