跨 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,被呼叫者清理棧)。// 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));
extern "C" typedef void (*Callback)(void* ctx, void* data);,上下文指針傳遞 this,DLL 內部再 static_cast 呼叫成員函數。核心教訓:函數指標型別必須與目標函數簽章完全一致,跨模組邊界更嚴格要求 ABI 相容。