回調是 C 語言事件驅動編程的基石(如線程創建、信號處理)。C++ 繼承此機制,但成員函數隱含 this 指針,導致無法直接作為 C 風格回調。本文剖析三種主流解決方案,並觸及底層調用約定與 Thunk 技術。
《深度探索 C++ 對象模型》指出:
1.&Class::Func獲取的是成員函數地址(非普通函數指針)
2.obj.Func()編譯器轉換為Func(&obj),this作隱式首參傳遞
3. 成員函數指針與普通函數指針無法通過強轉互換
靜態成員函數無 this,簽名兼容普通函數指針。通過回調參數傳入對象指針,內部轉發:
class CallBack {
int a;
static void Display(void* _this) { // 靜態成員
CallBack* self = static_cast<CallBack*>(_this);
self->a++; printf("a is: %d", self->a);
}
public:
CallBack(CallBackTest* t) : a(2) {
t->RegisterProc(Display, this); // 傳入 this
}
};
優勢: 封裝性好、標準合規、無平台依賴、調試友好。
嘗試強轉 (func)&CallBack::Display 並傳入 this 會導致編譯錯誤。即便繞過編譯(如手工構造調用幀),運行時也會因調用約定不匹配(this 傳遞方式:寄存器/棧/偏移量)而崩潰。本質上,成員函數調用涉及 this 調整(多繼承/虛繼承下更複雜),非簡單參數傳遞可模擬。
動態生成一小段機器碼(Thunk),將 this 置入特定寄存器/棧位,再跳轉至成員函數。常見於 ATL、MFC 早期實現(CDialog::DlgProc 等)。缺點: 與編譯器、ABI、CPU 架構強綁定,維護成本極高,現代 C++ 應避免。
場景:CTestDlg 持有 A 對象,A 需回調 CTestDlg 方法。
解決:
1. A.h 前向聲明 class CTestDlg;,成員 CTestDlg* m_pDlg;
2. A.cpp 包含 "TestDlg.h" 並實現回調
3. CTestDlg 構造 A 時傳入 this
包含順序關鍵: Test.h → A.h → TestDlg.h 避免循環依賴。
std::function + std::bind/Lambda 捕獲 this(C++11)總結:靜態成員 + 顯式 this 是跨 C/C++ 邊界的標準答案;純 C++ 項目優先選用 std::function 類型擦除方案。