昨天看了《COM本質論》的第一章」COM是一個更好的C++」,以爲頗有必要作一些筆記,因而整理成這篇文章,我相信你值得擁有。函數
這篇文章主要講的內容是:一個實現了快速查找功能的類FastString,在一個小小的需求以後,慢慢的演變成一個COM組件的過程。測試
類FastString實現了一個快速查找字符串的功能,快到時間複雜度是O(1),咱們先無論做者是怎麼實現的,估計是經過空間換時間。因爲這個類查找字符串很快,因而做者就把這個類當作一個產品,以源碼的方式賣給須要的廠商,廠商用後感受很好,但有的廠商想要得到字符串長度的功能,他們以爲strlen(str)速度太慢,畢竟這個函數獲取字符串的長度是線性的,時間複雜度是O(N),因而做者決定修改他的FastString,其心裏一直在告訴本身:個人FastString必須是Fast。spa
咱們先來看看做者FastString的樣子:指針
class FastString { public: FastString(const char* str); FastString(void ); int Find (const char* str ); private: char* m_str ; };
可別小看這個類,它查找字符串可快了(我也不知道爲何它就他媽的這麼快)。聰明的做者聽了廠商的需求以後,很快的就想到了很好的解決方案,經過一個變量len來存字符串的長度,經過一個函數Length返回變量len,時間複雜度但是O(0)哦,因而做者很快的實現了廠商的需求,大概以下:code
class FastString { public: FastString(const char* str); FastString(void ); int Length ();//新增的 int Find (const char* str ); private: char* m_str ; int len ;//新增的 };
在通過完美無缺的測試以後,做者驕傲的將他的做品分發給了願意再次掏錢的廠商,廠商用了非常火大,出現了各類莫名其妙的問題,在被各個廠商咆哮以後,做者發現了他的做品的缺陷,因而決定走上COM之路。對象
咱們先來看看廠商用了做者的FastString以後爲何就掛了呢?blog
廠商們拿了做者的源碼以後,就以源碼的方式和本身的其餘代碼一塊兒編譯成一個DLL文件,而後讓本身的產品升級,升級就是簡單的覆蓋這個DLL文件,因而廠商的產品升級以後就掛了。由於FastString可能在多個DLL中多個文件都實例化了,在這些DLL中FastString佔用4個字節的內存,而新版本的FastString佔用的是8個字節的內存,廠商只覆蓋了FastString所在的DLL,而沒有覆蓋全部使用了FastString的DLL,因爲FastString所在的DLL建立FastString是8個字節,而其餘DLL中是4個字節,若是跨庫傳遞FastString,將一個4字節的對象當作一個8字節的對象來用,這還不掛。繼承
聰明的做者很快就實現了他的COM組件,源碼大概是下面這個樣子,不要奇怪爲何做者的COM之路這麼順風順水,這麼快就出了做品。接口
#pragma once class IExtensibleObject { public: virtual void* Dynamic_Cast(const char* str)=0; virtual void AddRef()=0; virtual void Release()=0; }; class IFastString:public IExtensibleObject { public: virtual int Length(void)=0; virtual int Find(const char* str)=0; }; class FastString:public IFastString { public: FastString(const char* str=NULL); virtual void* Dynamic_Cast(const char* str); virtual void AddRef() ; virtual void Release(); virtual int Length(); virtual int Find(const char* str); ~FastString(); private: char* m_str; int len; int m_cPtrs;//引用計數 }; //導出函數 extern "C" __declspec(dllimport) IFastString* CreateFastString(const char* psz);
做者的COM組件作到了一下幾點,終於實現了增量更新。ip
1:做者不在以源碼的方式賣給廠商,而是以頭文件和庫的方式賣個廠商,廠商能夠經過靜態/動態的方式連接做者的庫。
2:做者不在讓廠商處處實例化他的FastString,我可愛的FastString。而是經過一個導出函數實例化FastString,並返回IFastString,這樣就不會出現不一樣DLL中FastString實例大小不同的問題。如今全部的實例都在做者的DLL中建立了。
3:關於回收FastString的問題?做者剛開始是想直接delete掉CreateFastString返回的指針,但爲了實現COM組件,此時的FastString已經不是彼時的本身了,他繼承並實現了多個接口,因爲接口之間轉換來轉換去,都不知道刪除哪一個指針了,因而做者決定經過使用引用計數的方式銷燬FastString。
4:爲何要本身實現Dynamic_Cast?
RTTI是一個與編譯器極爲相關的特徵,每一個編譯器廠商對RTTI的實現是獨有的,這大大破壞了「以抽象基類做爲接口而得到的編譯器獨立性」,既然每一個編譯器可能有不一樣的實現,即析構函數不能定義成虛函數,由於不一樣的編譯器,虛函數在虛方法表中的位置是不同的,有的編譯器放在最前面有的放在最後面,這會致使不一樣的編譯器編譯後虛方法在虛方法表中的位置是不同的。因此析構函數不能定義成virtual,其餘public接口都必須定義成virtual。其餘虛方法在虛方法表中的位置和虛方法的聲明保持一致,即按照聲明的順序存放在虛方法中。
因爲類型轉換和引用計數是每一個接口都須要的,因而把他們提出來放到最頂層,讓全部的接口繼承它。
5:新增的接口只能加在最後面,廢棄的接口不能刪除。
若是新增的接口插在中間,那麼部分接口在虛方法表中的地址就會發生變化,新版本的DLL就不能與已經發布的程序兼容,就不能實現增量升級,即只用覆蓋某個DLL,而不須要所有都要更新,廢棄的接口刪除會致使一樣的問題。
綜述:爲何做者的這個DLL能實現增量更新?
COM對象經過特定的導出方法在DLL中以new的方式建立,經過引用計數自動析構,客戶端不能本身建立COM對象,COM對象的內部結構發生變化,對外部也沒有影響,若是新增了接口,就在最後加,以前的接口在虛方法表中的位置不會受到印象,即對別的接口沒有影響,廢棄的接口不能刪除,
改變對象的內存結構和新增virtual方法都不要緊,那不就成了。實現增量不在是問題,咱們在回到FastString這個問題上,若是FastString一開始是以上訴方式實現的,如今要新增一個len字段和一個Length接口,我就這樣增了,新出個版本,直接覆蓋之前的那個DLL,我直接能夠用,一切都是OK的,外部的調用不會受到任何影響。爲了證實這個FastString能實現增量升級,我作了一個DEMO,你們能夠試一下,我就是下載地址。
你或許會說我這說的都不是COM,但這的確是更好的C++。