在進行軟體開發的過程中,常會用到一些聲明為CALLBACK 的函數,這些函數就是回呼函數。使用
回呼函數可以改善軟體的結構、提高軟體的複用性。比如,在一個規模較大的軟體項目中,可以將一些資
源或相對獨立的處理模組封裝到動態串連庫(DLL) 中,然後通過回呼函數在不同的場合來使用這些資源
和模組。利用回呼函數還可以進行程式間複雜的通訊,實現一些通知的功能,在某些場合它是比訊息更
合適的一種方式;在一些特殊的情況下,回呼函數更有不可替代的作用。Win32 API 中有許多回呼函數的
應用,在進行軟體設計時也會經常用到這種函數,而有些時候則需要編寫自己的回呼函數。因此,理解回
調函數的原理並掌握它的基本用法是非常必要的。
C++ 是當代使用最廣泛的語言,從嵌入式系統到大型主機系統、從LINUX 到WINDOWS , 在大型系統的
編製中,到處都是它的身影。它以高效和易編程性獲得了許多資深程式員的信賴。在DirectX Play 開發過
程中,經常需要使用到回呼函數,直接使用回呼函數顯得複雜麻煩,採用用C++ 實現對回呼函數的封裝,
使回呼函數變得方便實用,對於DirectX Play 等編程就顯得是非常有意義的。
回呼函數簡單講就是一個函數指標。寫一個函數,然後把函數地址傳遞給這個函數指標就可以了。
回呼函數的原形對C++ 的成員函數用做回呼函數的影響是什麼?
編寫回呼函數簡單地說就是函數原形一致。函數的輸入參數,輸出參數一致是很容易保證的。要注
意調用類型一致性。函數傳參數有好幾種類型,搞錯了傳參數的方式,系統必然運行錯誤。一般來說都
是WINAPI 傳參數方式。要注意C++ 的類的成員函數和一般的C 函數的區別。C++ 類採用this 規則傳
遞函數。在使用類的成員函數作為回呼函數,要求該成員函數被聲名為靜態成員函數,並且注意函數聲
名的時候要同時聲明好參數路由規則。
1 靜態成員函數調用非靜態成員函數
由於靜態成員函數的存在是在類的執行個體產生之前,在每本C++ 的書中都提到,靜態成員函數只可以
調用靜態成員函數,並使用類的靜態成員。但如果這樣,寫一個全部都是靜態成員的類,似乎失去了C++
類的大部分優點。它在完美的封裝的同時,使得類不可以執行個體化。比如,對視窗訊息處理的回呼函數編
程,系統中有無數個視窗,如果寫一個全部都是靜態成員的類,是無法描述這些視窗的。怎麼在靜態成員
函數中使用類的非靜態成員呢?
可以給靜態成員函數傳入參數,而且一般設計的比較好的回呼函數都提供一個資料區塊指標作為傳入
參數,這時候就可以採用這種結構。
回呼函數類型void Fun(void 3 pData ,UINT uMsg)
class CFun
{
static void StaticFun(void 3 pData ,UINT uMsg)
{
CFun 3 pThisObject = (CFun 3 ) pData ;
pThisObject -> Fun(uMsg) ; / / (1)
}
void Fun(UINT nMsg)
{
}
};
如此,回呼函數就和C++ 類形成了一個完美的體系。由於(1) 處使用的是CFun 的成員函數,該類生
成的執行個體可以訪問類的其他任何函數成員和資料成員。這樣處理回呼函數只是把C++ 類和回呼函數連
接起來了。但還沒有發揮出C++ 的全部優勢。現在把FUN 的函式宣告改一下。
2 函數的分發再處理
class CFun
{
static void StaticFun(void 3 pData ,UINT uMsg) ;
protected:
vitual void Fun(UINT nMsg) ;
};
同時寫一個衍生類別,
class CSubFun : public CFun
{
void Fun(UINT nMsg) ;
void SubFun1() ;
void SubFun2() ;
..
}
同時實現該函數如下
voidCSubFun::Fun(UINTnMsg)
{
switch(nMsg)
{
case 1:SubFun1() ; break;
case 2:SubFun2() ; break;
...
}
}
現在可以看到由於虛函數的使用,C ++ 的衍生類別也具有了函數處理的能力。也就是說,實現了一個
對回呼函數封裝的類,可以在基類中定義一些實現處理,但如果基類對回呼函數的傳入參數不符合要求,
可以隨時按照我們的要求修改它。
按照要求實現對基類處理的修改,上面的處理是有缺陷的。沒有調用到基類的處理函數,這樣丟棄
了基類的處理常式是非常不可取的。可以重新做一下類的設計。修改衍生類別的函數Fun 如下:
BOOL CSubFun: :Fun(UINT nMsg)
{
bHandle = FALSE;
switch(nMsg)
{
case1:
bHandle = SubFun1() ;
break;
case 2:
bHandle = SubFun2() ;
break;
. . .
}
if (bHandle)
return TRUE;
else
returnCFun::Fun(nMsg);
}
至此,C ++ 的類終於和回呼函數完美的結合了。所有C ++ 的組合繼承等等的優點全部重現。派生
類可以決定是否處理該訊息,甚至在該訊息處理後繼續要求基類去處理。
3 回呼函數分發的簡化
其次,如果nMsg 的數目是非常龐大的,那麼函數位函數處理看起來就不那麼愉快了。可以定義幾個
宏來處理:
# define DECLARE-FUN-TABLE() /
virtual void Fun(UINT nMsg) ;
# defineBEGIN-FUN-TABLE(theClass,baseClass)
BOOL theClass: :Fun(UINT nMsg){ /
bHandle = FALSE; /
switch(nMsg)
{
# define ON-FUN(nMsg,Function) /
case nMsg: bHandle = Funcion() ;break;
#define END-FUN-TABLE(theClass,baseClass) }/
if (bHandle)
return TRUE; /
else
return baseClass::Fun(nMsg);
}
如此一來,基類不變,衍生類別就改變如下:
class CSubFun : public CFun
{
BOOL SubFun1() ;
BOOL SunFun2() ;
. . .
DECLARE-FUN-TABLE()
}
BEGIN-FUN-TABLE(CSubFun,CFun)
ON-FUN(1 ,SubFun1)
ON-FUN(2,SubFun2)
END-FUN-TABLE(CSubFun ,CFun)
這種類似的宏結構在MFC 的訊息映射機制中可以看到,但它沒有採用虛函數實現。MFC 認為虛擬機器
制這種效率太低了,WINDOW 有數千個訊息,視窗訊息回呼函數的調用是非常頻繁的。但由於上述回調
函數的調用頻率比訊息映射機制少的多,訊息的數目也少的多,採用這種機制就可以了。如果想繼續提
升這種封裝機制,可以參考以下MFC 的訊息映射機制和ATL 的函數分發機制。