昨天看了《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++。