COM是一個(gè)更好的C++
昨天看了《COM本質(zhì)論》的第一章”COM是一個(gè)更好的C++”,覺(jué)得很有必要做一些筆記,于是整理成這篇文章,我相信你值得擁有。
這篇文章主要講的內(nèi)容是:一個(gè)實(shí)現(xiàn)了快速查找功能的類(lèi)FastString,在一個(gè)小小的需求之后,慢慢的演變成一個(gè)COM組件的過(guò)程。
類(lèi)FastString實(shí)現(xiàn)了一個(gè)快速查找字符串的功能,快到時(shí)間復(fù)雜度是O(1),我們先不管作者是怎么實(shí)現(xiàn)的,估計(jì)是通過(guò)空間換時(shí)間。由于這個(gè)類(lèi)查找字符串很快,于是作者就把這個(gè)類(lèi)當(dāng)做一個(gè)產(chǎn)品,以源碼的方式賣(mài)給需要的廠商,廠商用后感覺(jué)很好,但有的廠商想要獲得字符串長(zhǎng)度的功能,他們覺(jué)得strlen(str)速度太慢,畢竟這個(gè)函數(shù)獲取字符串的長(zhǎng)度是線(xiàn)性的,時(shí)間復(fù)雜度是O(N),于是作者決定修改他的FastString,其內(nèi)心一直在告訴自己:我的FastString必須是Fast。
我們先來(lái)看看作者FastString的樣子:
class FastString { public: FastString(const char* str); FastString(void ); int Find (const char* str ); private: char* m_str ; };
可別小看這個(gè)類(lèi),它查找字符串可快了(我也不知道為什么它就他媽的這么快)。聰明的作者聽(tīng)了廠商的需求之后,很快的就想到了很好的解決方案,通過(guò)一個(gè)變量len來(lái)存字符串的長(zhǎng)度,通過(guò)一個(gè)函數(shù)Length返回變量len,時(shí)間復(fù)雜度可是O(0)哦,于是作者很快的實(shí)現(xiàn)了廠商的需求,大概如下:
class FastString { public: FastString(const char* str); FastString(void ); int Length ();//新增的 int Find (const char* str ); private: char* m_str ; int len ;//新增的 };
在經(jīng)過(guò)天衣無(wú)縫的測(cè)試之后,作者驕傲的將他的作品分發(fā)給了愿意再次掏錢(qián)的廠商,廠商用了很是火大,出現(xiàn)了各種莫名其妙的問(wèn)題,在被各個(gè)廠商咆哮之后,作者發(fā)現(xiàn)了他的作品的缺陷,于是決定走上COM之路。
我們先來(lái)看看廠商用了作者的FastString之后為什么就掛了呢?
廠商們拿了作者的源碼之后,就以源碼的方式和自己的其他代碼一起編譯成一個(gè)DLL文件,然后讓自己的產(chǎn)品升級(jí),升級(jí)就是簡(jiǎn)單的覆蓋這個(gè)DLL文件,于是廠商的產(chǎn)品升級(jí)之后就掛了。因?yàn)镕astString可能在多個(gè)DLL中多個(gè)文件都實(shí)例化了,在這些DLL中FastString占用4個(gè)字節(jié)的內(nèi)存,而新版本的FastString占用的是8個(gè)字節(jié)的內(nèi)存,廠商只覆蓋了FastString所在的DLL,而沒(méi)有覆蓋所有使用了FastString的DLL,由于FastString所在的DLL創(chuàng)建FastString是8個(gè)字節(jié),而其他DLL中是4個(gè)字節(jié),如果跨庫(kù)傳遞FastString,將一個(gè)4字節(jié)的對(duì)象當(dāng)做一個(gè)8字節(jié)的對(duì)象來(lái)用,這還不掛。
聰明的作者很快就實(shí)現(xiàn)了他的COM組件,源碼大概是下面這個(gè)樣子,不要奇怪為什么作者的COM之路這么順風(fēng)順?biāo)?,這么快就出了作品。
#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;//引用計(jì)數(shù) }; //導(dǎo)出函數(shù) extern "C" __declspec(dllimport) IFastString* CreateFastString(const char* psz);
作者的COM組件做到了一下幾點(diǎn),終于實(shí)現(xiàn)了增量更新。
1:作者不在以源碼的方式賣(mài)給廠商,而是以頭文件和庫(kù)的方式賣(mài)個(gè)廠商,廠商可以通過(guò)靜態(tài)/動(dòng)態(tài)的方式鏈接作者的庫(kù)。
2:作者不在讓廠商到處實(shí)例化他的FastString,我可愛(ài)的FastString。而是通過(guò)一個(gè)導(dǎo)出函數(shù)實(shí)例化FastString,并返回IFastString,這樣就不會(huì)出現(xiàn)不同DLL中FastString實(shí)例大小不一樣的問(wèn)題?,F(xiàn)在所有的實(shí)例都在作者的DLL中創(chuàng)建了。
3:關(guān)于回收FastString的問(wèn)題?作者剛開(kāi)始是想直接delete掉CreateFastString返回的指針,但為了實(shí)現(xiàn)COM組件,此時(shí)的FastString已經(jīng)不是彼時(shí)的自己了,他繼承并實(shí)現(xiàn)了多個(gè)接口,由于接口之間轉(zhuǎn)換來(lái)轉(zhuǎn)換去,都不知道刪除哪個(gè)指針了,于是作者決定通過(guò)使用引用計(jì)數(shù)的方式銷(xiāo)毀FastString。
4:為什么要自己實(shí)現(xiàn)Dynamic_Cast?
RTTI是一個(gè)與編譯器極為相關(guān)的特征,每個(gè)編譯器廠商對(duì)RTTI的實(shí)現(xiàn)是獨(dú)有的,這大大破壞了“以抽象基類(lèi)作為接口而獲得的編譯器獨(dú)立性”,既然每個(gè)編譯器可能有不同的實(shí)現(xiàn),即析構(gòu)函數(shù)不能定義成虛函數(shù),因?yàn)椴煌木幾g器,虛函數(shù)在虛方法表中的位置是不一樣的,有的編譯器放在最前面有的放在最后面,這會(huì)導(dǎo)致不同的編譯器編譯后虛方法在虛方法表中的位置是不一樣的。所以析構(gòu)函數(shù)不能定義成virtual,其他public接口都必須定義成virtual。其他虛方法在虛方法表中的位置和虛方法的聲明保持一致,即按照聲明的順序存放在虛方法中。
由于類(lèi)型轉(zhuǎn)換和引用計(jì)數(shù)是每個(gè)接口都需要的,于是把他們提出來(lái)放到最頂層,讓所有的接口繼承它。
5:新增的接口只能加在最后面,廢棄的接口不能刪除。
如果新增的接口插在中間,那么部分接口在虛方法表中的地址就會(huì)發(fā)生變化,新版本的DLL就不能與已經(jīng)發(fā)布的程序兼容,就不能實(shí)現(xiàn)增量升級(jí),即只用覆蓋某個(gè)DLL,而不需要全部都要更新,廢棄的接口刪除會(huì)導(dǎo)致同樣的問(wèn)題。
綜述:為什么作者的這個(gè)DLL能實(shí)現(xiàn)增量更新?
COM對(duì)象通過(guò)特定的導(dǎo)出方法在DLL中以new的方式創(chuàng)建,通過(guò)引用計(jì)數(shù)自動(dòng)析構(gòu),客戶(hù)端不能自己創(chuàng)建COM對(duì)象,COM對(duì)象的內(nèi)部結(jié)構(gòu)發(fā)生變化,對(duì)外部也沒(méi)有影響,如果新增了接口,就在最后加,之前的接口在虛方法表中的位置不會(huì)受到印象,即對(duì)別的接口沒(méi)有影響,廢棄的接口不能刪除,
改變對(duì)象的內(nèi)存結(jié)構(gòu)和新增virtual方法都沒(méi)關(guān)系,那不就成了。實(shí)現(xiàn)增量不在是問(wèn)題,我們?cè)诨氐紽astString這個(gè)問(wèn)題上,如果FastString一開(kāi)始是以上訴方式實(shí)現(xiàn)的,現(xiàn)在要新增一個(gè)len字段和一個(gè)Length接口,我就這樣增了,新出個(gè)版本,直接覆蓋以前的那個(gè)DLL,我直接可以用,一切都是OK的,外部的調(diào)用不會(huì)受到任何影響。為了證明這個(gè)FastString能實(shí)現(xiàn)增量升級(jí),我做了一個(gè)DEMO,大家可以試一下,我就是下載地址。
你或許會(huì)說(shuō)我這說(shuō)的都不是COM,但這的確是更好的C++。
浙公網(wǎng)安備 33010602011771號(hào)