為了達到組件複用,讓我們看看為什麼COM才是最好的選擇.
1.寫成一個類,通過複製.h,.cpp檔案到達組件複用
缺點很明顯,如果該類有什麼變化,所有相關的組件都要複製一次,當相關的組件很多的時候,累人還容易出錯誤.
2.以.lib發布類庫
每次改變都需要相關組件重新編譯。當項目越來越大(編譯一次都要半個小時),會極大的影響效率.一般只需要把變更比較少,用的比較多的類做成靜態庫即可.
3.不需要編譯,動態連結,把實作類別匯出(_declspec(dllexport))
1) 各個c++編譯器所產生的dll並不通用(因為c++中允許操作符和函數重載,c/c++編譯器都有自己的規則去修改每一個進入點的符號(名字改編技術name mangling),以便允許同一個名字對應不同的函數調用)改善該問題,可以使用模組定義檔案.但是卻不能捕捉異常.
2) 類擴充的時候(即sizeof(class)增大的時候),用新的類聲明調用老版本的dll時,就會出現問題(極有可能出現操作不合法地址的情況).
也可以採用實現和介面分離的技術解決這個問題(即在介面類中定義個實作類別的指標)
.但是也有缺點:使代碼變的低效(多了一層函數調用),當方法很多時,增加了出錯的可能.
4.為瞭解決以上在實現組件複用面臨的困難,可以根據c/c++中vptr/vtbl結構(所有的編譯器都是一樣的),僅僅只是暴露介面(聲明為純虛函數),而讓衍生類別去實現.
比如:
interface ITest
{
virtual void doSomething()=0;
};
class TestClass: public ITest
{
void doSomething();
};
extern "C" _declspec(dllexport) ITest * GetInterface()
{
return dynamic_cast<ITest *>(new TestClass);
}
注:extern "C" 是表示該匯出函數採用c的規則(所有編譯器都一樣)去解析函數名
但當衍生類別中需要動態分配和釋放記憶體的時候,會造成記憶體泄露.如果把介面類的解構函式聲明為虛函數,又破壞了dll的通用性(虛解構函式在vtbl中的位置會隨著編譯器不同而不同).可以在介面類中聲明一個介面,讓衍生類別去實現釋放記憶體(IUnknown中Release()出現的原因).此時介面變成如下樣子:
interface ITest
{
virtual void Release() =0;
virtual void doSomething()=0;
};
當介面的方法增加後(寫的順序要一致,這關乎虛函數在vtbl裡的位置),新客戶在使用老對象去調用新方法的時候,又會出現問題(同前3(2)).這個時候可以根據使用者請求的介面去進行相應的轉化(使用dynamic_cast)
即:
interface ITestNew
{
virtual void Release() =0;
virtual void New()=0;
};
class TestClass :public ITest,public ITestNew
{
void doSomething();
void New();
void Release();
};
如果你想調用新的方法,可以這樣:
bool NewMethod(ITest *pOld)
{
ITestNew *pNew = dynamec_cast<ITestNew *>(pOld);
if(pNew) {pNew->New(); return true};
return false;
};
此時又出現了問題,對於RTTI每個編譯器的實現是不一樣的。解決辦法可以顯式的暴露一個介面的名字,由這個去完成RTTI,從而實現與編譯器無關.
interface ITestNew
{
virtual void Release() =0;
virtual void *Dynamic_Cast(const char *pszTypeName)=0;
virtual void New()=0;
};
既然ITest和ITestNew都有共同的方法,很自然的我們把這些方法提升到一個基底介面中,其他所有介面都從這個介面繼承,此時COM已經初局規模:).
interface IPulibcTest
{
virtual void Release() =0;
virtual void *Dynamic_Cast(const char *pszTypeName)=0;
};
interface ITest :public IPulibcTest
{
virtual void doSomething() = 0;
virtual void New() = 0;
};
class TestClassNew:public ITestNew
{
//具體去實現
...........
};
有了這樣的類層次之後,客戶就可以利用編譯器的獨立結構,動態查詢dll是否實現了某個指定的介面:
bool NewMethod(ITest *pTest)//COM中一般採用返回HRESULT值去確定方法是否可以調用
{
ITest *pNew = (ITest *)pTest->Dynamic_Cast("ITestNew ");
if(pNew){ pNew ->New();return true;} return false; }