站內搜尋

2008/8/21

C Callback Function with C++ Class

由於 C/C++ 語言的特性,許多 API 是必需要利用 Callback function 才能完成作用
在 C 的時代這是很合理也很直覺化的設計,但若將場景拉到 C++ 呢....
此篇除了介紹 How 之外,也將對 Why 進行簡單的介紹,希望能夠令不知其所以然的人有所幫助。


Effective C++m 一書將 C++ 視為一種語言聯邦,而其中最簡單及常被討論到的,我想大概是屬於 OOP 的部份。

在 C++ 中利用 Class 來實現 OO 的許多精神,許多的程式設計都會採用 OO 的封裝特性,也因此當你需要使用某些 API 時,會面臨到 callback function 設計不易的問題。包括如何取用物件內的屬性、如何將物件傳入等問題,假設利用 non-member function 來設計 callback function,為了能令 callback function 順利的存取相關的物件內的資料,可能會在某種程度上破壞掉物件的封裝性。
因此,若以封裝性的角度來看,我們當然會希望能夠直覺的以 class member function 來做為 callback function,如此一來,便可以在不破壞封裝性的情況下,依然順利的使用物件內的屬性。
在這個前提下,便會很直覺的寫出以下的程式段:

class CMyClass
{
public:
    void Mainfunc() ;
    void CBfunc(void *) ;
private:
    int iData ;
    int iInc ;
}

int CMyClass::CBfunc(void *iIncLoc)
{
    int _i = (int)iIncLoc ;
    return iData+_i ;
}

void CMyClass::Mainfunc()
{
    // APIfunc( *pCallback, *pParam) <-- 原型
    APIfunc(CBfunc, iInc) ; // error !!
} 

但卻也可以發現 compiler 很不給面子的吐了一大串的 error,這些 error 是為什麼產生呢?
讓我們回頭再來檢視一下 C++ 中 Class 及 Member function 的生成關係,由於 C++ 中只會對類別生成一份共用的 Member function ,但為了能夠令所有的 instance 都能使用到正確的成員資料,於是 C++ Compiler 便有了 this 這個特別的 pointer,它會自動的將 this 加於使用到成員函式的地方,於是, member function 便被改寫成:
int CMyClass::CBfunc(CMyClass* this, void *iIncLoc) // 原型轉變了!
{
    int _i = (int)iIncLoc ;
    return this->iData+_i ; // 利用 this 來取得成員變數
}

void CMyClass::Mainfunc(CMyClass *this)
{
    // APIfunc( *pCallback, *pParam) <-- 原型
    APIfunc(this->CBfunc, iInc) ; // error !!
}
但 callback 是由系統叫起,在編譯時期便要將 function pointer 建立,可是此時 this 指標並未有明確的指定,這當然會造成錯誤。而另一個也會發生錯誤的地方是 Callback function 的 type 也和 APIfunc 所要求的 function prototype 完全不同,自然也會發生錯誤。
再從邏輯上的角度來看,non-member function pointer 和 member function pointer 自然也就是完全不同的東西了,當然不可能讓它被順利的編譯成功。
因此,我們必需要考量的便是如何令此 member function pointer 不需包含 this 指標,而 static 此時便成為了我們最好的解決方式。
class CMyClass
{
public:
    void Mainfunc() ;
    static void CBfunc(void *) ;
private:
    int iData ;
    int iInc ;
} 

static 在意義上,能令 CBfunc 成為 Class's Function,而不屬於任何 instance,在實際上,因在不屬於任何物件,因此它可以被視為一個 Global function 來存取,但在語法上,它依然屬於 Class,因此,它依然擁有存取 member variable 的能力,但這時卻又引發了一個問題。
因為 static member function 於 instance 生成前產生,於是,雖然有存取成員的能力,卻沒有去存取的路,這就像是你有銀行的密碼,但你卻不知道帳號一樣。
因此,我們便可以利用 APIfunc 的 pParam 來傳入 this 指標,如此便可以名正言順的使用其成員了:
class CMyClass
{
public:
    void Mainfunc() ;
    static void CBfunc(void *) ;
private:
    int iData ;
    int iInc ;
}

int CMyClass::CBfunc(void *param)
{
    int i = (CMyClass*)param->iInc ;
    return (CMyClass*)param->iData+i ;
}

void CMyClass::Mainfunc()
{
    // APIfunc( *pCallback, *pParam) <-- 原型
    APIfunc(CBfunc, this) ;
} 

而 Compiler 改寫為
void CMyClass::Mainfunc(CMyClass* const this)
{
    // APIfunc( *pCallback, *pParam) <-- 原型
    APIfunc(CBfunc, this) ;
}
如此便能順利的取用到 Class 內的 member。

利用上面的方式,我們便可以實現在保有封裝性的情況下,使用 C-style 的 callback API。

熱門文章