凡是由你設計而卻由 Windows 系統呼叫的函式,統稱為 callback 函式。這些函式都有一
定的型態,以配合 Windows的呼叫動作。
某些Windows API 函式會要求以callback 函式作為其參數之一,這些API 例如SetTimer 、
LineDDA 、EnumObjects 。通常這種API 會在進行某種行為之後或滿足某種狀態之時呼叫該
callback 函式。
什麼函式有資格在 C++ 程式中做為callback 函式?這個問題的背後是:C++ 程式中的
callback 函式有什麼特別的嗎?為什麼要特別提出討論?
是的,特別之處在於,C++ 編譯器為類別成員函式多準備了一個隱藏參數(程式碼
中看不到),這使得函式型態與 Windows callback 函式的預設型態不符。
假設我們有一個 CMyclass 如下:
class CMyclass {
private :
int nCount;
int CALLBACK _export
EnumObjectsProc(LPSTR lpLogObject, LPSTR lpData);
public :
void enumIt(CDC& dc);
}
void CMyclass::enumIt(CDC& dc)
{
// 註冊 callback 函式
dc.EnumObjects(OBJ_BRUSH, EnumObjectsProc, NULL);
}
C++ 編譯器針對 CMyclass::enumIt 實際做出來的碼相當於:
void CMyclass::enumIt(CDC& dc)
{
// 註冊 callback 函式
CDC::EnumObjects(OBJ_BRUSH, EnumObjectsProc,
NULL, (CDC *)&dc);
}
你所看到的最後一個參數,(CDC *)&dc ,其實就是 this 指標。類別成員函式靠著 this
指標才得以抓到正確物件的資料。你要知道,記憶體中只會有一份類別成員函式,
但卻可能有許多份類別成員變數 --- 每個物件擁有一份。
C++ 以隱晦的 this 指標指出正確的物件。當你這麼做:
nCount = 0;
其實是:
this->nCount = 0;
基於相同的道理,上例中的 EnumObjectsProc 既然是一個成員函式,C++ 編譯器也
會為它多準備一個隱藏參數。
好,問題就出在這個隱藏參數。callback 函式是給 Windows 呼叫用的,Windows 並
不經由任何物件呼叫這個函式,也就無由傳遞 this 指標給callback 函式,於是導至
堆疊中有一個隨機變數會成為 this 指標,而其結果當然是程式的崩潰了。
要把某個函式用作 callback 函式,就必須告訴 C++ 編譯器,不要放 this 指標作為
該函式的最後一個參數。兩個方法可以做到這一點:
1. 不要使用類別的成員函式(也就是說,要使用全域函式)做為callback 函式。
2. 使用 static 成員函式。也就是在函式前面加上 static 修飾詞。
第一種作法相當於在C 語言中使用callback 函式。第二種作法比較接近OO 的精神。
我想更進一步提醒你的是,C++ 中的 static 成員函式特性是,即使物件還沒有產生,
static 成員也已經存在(函式或變數都如此)。換句話說物件還沒有產生之前你已經
可以呼叫類別的 static 函式或使用類別的 static 變數了。請參閱第二章。
也就是說,凡宣告為 static 的東西(不管函式或變數)都並不和物件結合在一起,
它們是類別的一部份,不屬於物件。
----摘自《深入浅出MFC》