COM对象是给用户提供服务的封装的实体。这个应该和C++中类的对象理解起来是相似的。但是有时候也把COM对象当作提供服务的那个类。COM对象也对数据进行了封装,然后也提供了接口。不过和类还是有一些不一样的。类中的数据可以申明为public,然后让用户能够直接访问这些数据成员。但是用户不能对COM对象的数据进行直接访问,只能通过接口(如果有提供这种接口的话)来对数据进行间接的访问。一般COM接口指的是一组提供服务的接口,刚开始看这个定义很不习惯。因为C++中根本没有接口的概念,但是像Java这些语言就有接口的概念。关于COM接口可以按照Java的接口来理解(多懂几门语言真心是好事啊)。C++中的定义方式一般是用抽象类来实现,其中所有的函数全都是纯需函数,全部交给子类来实现。如某个接口的定义如下:
1 struct ICalc 2 { 3 virtual long __stdcall add(long a, long b) = 0; 4 virtual long __stdcall minus(long a, long b) = 0; 5 virtual long __stdcall times(long a, long b) = 0; 6 virtual long __stdcall devide(long a, long b) = 0; 7 };
客户使用COM组件的时候,是不知道COM组件的确切对象的。所以COM组件需要注册注册表。但是客户还是需要一点什么标识来访问COM组件,使用COM对象,第一个直接的想法就是取个名字。但是会出现这种情况,A公司做个插件叫做Calculator,B公司做个插件也可以叫做Calculator,甚至家里的旺财写个插件都可以叫做Calculator。所以这个方法不是很好。为了解决这个重复的问题。微软决定使用GUID来对插件进行标识。GUID全称是Globally Unique Identifier,是一个128位的随机数。大家都随机,不怕随到一样的数吗?恩,确实有那个可能,但是这个概率太低了。理论上,如果一台计算机每秒产生10 000 000个GUID,则可以保证3240年不重复(当然是概率意义上,怎么算的了,我也不清楚啦)。反正GUID重复的概率是非常低的,可以放心使用。每个COM对象都有一个GUID来进行标识,这个GUID一般叫做CLSID。每一个COM接口也有一个GUID来进行标识,这个GUID一般叫做IID。在C++中,GUID定义如下:
1 typedef struct _GUID 2 { 3 DWORD Data1; 4 WORD Data2; 5 WORD Data3; 6 BYTE Data4[8]; 7 } GUID;
按照COM规范,只要用户获得了一个COM对象的接口,那么用户可以通过它获取到该COM对象的其他接口(如果有的话)。一个用户在使用COM对象以前,用户需要建立COM对象(我觉得COM对象这个叫法真心的很讨厌,书上说COM对象既指那个提供服务的类,也指根据这个类实例化的对象)。建立COM对象当然是要分配一些资源的,在用完这些资源以后当然也是需要释放的,所以每一个COM对象都有一个计数。计数值为0的时候,那么对象就销毁这个COM对象。当有另外一个指针获得这个COM对象的话,那么计数就会增加一个,有一个指针释放了对这个COM对象的控制了,那么计数就减一。这种计数的方式类似于智能指针。但是COM对象的计数也没有智能指针那么智能,有时还是需要手动来释放的,所以用户承担着释放资源的重任。基于以上的说法,我们就需要一个获取其他所有接口的接口,增加计数的接口和减少计数的接口。COM提供的IUnknown接口已经提供了这三个接口(注意:COM接口一般指的是一组接口,而不是一个接口)。实际上所有的COM接口都必须从IUnknown继承而来。IUnknown提供了QueryInterface、AddRef和Release这三个接口。按C++的定义如下,但是实际定义的写法比较麻烦,这里只是比较简单的写法。
1 class IUnknown 2 { 3 public: 4 virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv) =0; 5 virtual ULONG __stdcall AddRef() =0; 6 virtual ULONG __stdcall Release() = 0; 7 }
IUnknown是一个接口当然也有一个IID,IUnknown的IID为IID_IUnknown=00000000-0000-0000-C000-000000000046。然后剩下就是用户自己定义接口了。用户也可以定义多个接口。最后提供服务的类集成这些接口就组成了一个能够被用来创建COM对象的类了。比如下面的ICalc是一个接口,CCalc就是一个实现服务的类。
1 class ICalc : public IUnknown 2 { 3 public: 4 virtual long __stdcall add(long a, long b) = 0; 5 virtual long __stdcall minus(long a, long b) = 0; 6 virtual long __stdcall times(long a, long b) = 0; 7 virtual long __stdcall devide(long a, long b) = 0; 8 }; 9 10 class CCalc : public ICalc 11 { 12 public: 13 CCalc(); 14 ~CCalc() = default; 15 16 public: 17 //IUnknown interface: 18 virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv) override; 19 virtual ULONG __stdcall AddRef() override; 20 virtual ULONG __stdcall Release() override; 21 22 //ICalc interface: 23 virtual long __stdcall add(long a, long b) override; 24 virtual long __stdcall minus(long a, long b) override; 25 virtual long __stdcall times(long a, long b) override; 26 virtual long __stdcall devide(long a, long b) override; 27 28 private: 29 long m_lRef; 30 };
其中override是C++11标准所提供的关键字。
这一篇博客就先写到这吧,后面还有需要继续写的,但是现在要回寝室了,后续的明天或者后天继续写。
PS:博主也是一个菜鸟,最近才开始写技术博客,如果各位发现了什么错误,欢迎拍砖,指出错误。