跳到主要內容

C 和 C++ 的 Callback

C Callback Function with C++ Class
轉載於: http://lak4cyut.blogspot.tw/2008/08/c-callback-function-with-class.html


由於 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) <-- br=""> 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) <-- br=""> 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) <-- br=""> APIfunc(CBfunc, this) ;
}

而 Compiler 改寫為

void CMyClass::Mainfunc(CMyClass* const this)
{
// APIfunc( *pCallback, *pParam) <-- br=""> APIfunc(CBfunc, this) ;
}

如此便能順利的取用到 Class 內的 member。

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

留言

這個網誌中的熱門文章

難捨

每次星期天要和你道別的時候,心中總是有很多不捨。 只能告訴自己,很快的又能見面了。 在擁擠的火車上,思念還是停留在妳的身上。 那時深深的覺得,有一個能夠去呵護而值得愛的人,是一種幸福。

.NET操作EXCEL時出現的錯誤

  在免部署 Office PIAs (Primary Interop Assemblies) 的情況下存取 Office 物件模型 (以下轉載) Microsoft::Office::Interop::Excel::ApplicationClass ^app  = gcnew Microsoft::Office::Interop::Excel::ApplicationClass(); Microsoft::Office::Interop::Excel::WorkbookClass ^wrk = app->Workbooks->Add() ASP.NET操作EXCEL時出現的錯誤 Retrieving the COM class factory for component with CLSID(轉) 解決方案: 運行dcomcnfg打開組件服務 依次展開"組件服務"->"計算機"->"我的電腦"->"DCOM配置" 找到"Microsoft Excel應用程序" 右鍵打開屬性對話框,點擊"標識"選項卡 點"下列用戶",把管理員的用戶密碼正確填寫進去... 點擊"安全"選項卡, 依次把"啟動和激活權限","訪問權限","配置權限",都選擇為自定義, 然後依次點擊它們的編輯,把everyone添加進去,並加入所有的權限... OK,解決此問題! MMC是以x64來執行的,它會排除掉x32的DCOM伺服器,如下,看不到Microsoft Excel Application 解決方式也很容易,就是要以x32方式執行MMC已開啟元件服務,我們知道元件服務是"comexp.msc"這個描述檔後, 只要在開始工具列-->執行 如下命令: mmc comexp.msc /32