回呼函式是實現模組解耦、異步通知、泛型演算法的關鍵技術。本文系統解析回呼的內在機制、與虛擬函式/訊息/事件的關係、三種調用模式、C 語言函式指標實作、C++ 物件導向實作、Windows 訊息機制、CORBA 分散式回呼,並提供標準庫 qsort/bsearch 實戰範例。
定義:調用者將自身函式位址作為參數傳給被調用者(通常為庫函式/API),被調用者在特定時機透過該位址「回頭呼叫」調用者的函式。
三步驟:
__stdcall/CALLBACK)。鉤子函式:SetWindowsHookEx 專用的回呼函式特例。
| 機制 | 本質 | 同步性 | 解耦程度 | 典型場景 |
|---|---|---|---|---|
| 回呼函式 | 函式指標直接調用 | 同步 | 中(雙方知曉原型) | 排序比較、進度通知、定時器 |
| Windows 訊息 | 訊息隊列分派 | 同步/非同步 | 高(僅知視窗句柄) | UI 互動、執行緒間通訊 |
| 事件 | 委派/介面封裝 | 同步 | 高(介面契約) | 元件模型、C#/Java 事件 |
訊息機制可視為作業系統層面的「廣義回呼」;事件是物件導向語言將函式指標封裝為型別安全的「正統回呼」。
回呼常作為非同步註冊機制(如 SetTimer(hwnd, id, elapsed, TimerProc)),非同步通知則用 PostMessage/執行緒實現。
void Func(char *s); // 原型
void (*pFunc)(char *); // 指標變數
typedef void (*Callback)(char *); // 簡化型別
void GetCallback(Callback cb) { /* ... */ cb("data"); }
void MyCallback(char *s) { /* 處理 s */ }
GetCallback(MyCallback); // 函式名即位址
__stdcall int Callee(int);
void Caller(__cdecl int(*ptr)(int)); // 錯誤:慣例不符
__cdecl int(*p)(int) = Callee; // 編譯失敗
必須確保雙方呼叫慣例一致(__cdecl/__stdcall/__fastcall)。
void qsort(void *base, size_t n, size_t w, int (*cmp)(const void*, const void*));
void *bsearch(const void *key, const void *base, size_t n, size_t w, int (*cmp)(const void*, const void*));
int cmp_int(const void *a, const void *b) { return *(int*)a - *(int*)b; }
int main() {
int a[] = {54,21,11,67,22};
qsort(a, 5, sizeof(int), cmp_int);
// a = {11,21,22,54,67}
}
// 介面定義
class ICallback {
public:
virtual void OnEvent(const std::string& data) = 0;
virtual ~ICallback() {}
};
// 被調用者持有介面指標
class Worker {
ICallback *cb_;
public:
void SetCallback(ICallback *cb) { cb_ = cb; }
void DoWork() { if (cb_) cb_->OnEvent("result"); }
};
// 調用者實作介面
class MyHandler : public ICallback {
public:
void OnEvent(const std::string& data) override { /* ... */ }
};
// 使用
Worker w; MyHandler h; w.SetCallback(&h); w.DoWork();
優點:型別安全、支援多重回呼、可攜帶狀態(類別成員)、自然支援多態。
// 註冊視窗類時設定 WndProc(回呼入口)
// 發送同步訊息:SendMessage(hwnd, WM_USER, wParam, lParam) -> 直接呼叫 WndProc
// 發送非同步訊息:PostMessage(hwnd, WM_USER, wParam, lParam) -> 排入隊列,稍後處理
適用於 UI 執行緒與工作執行緒通訊、跨進程通訊(需 WM_COPYDATA)。
std::function/lambda/介面,避免裸函式指標。extern "C"、COM)。回呼是「好萊塢原則」——Don't call us, we'll call you——在軟體工程的具體落地,掌握其變體與適用場景,是架構師的必修課。