com口定义_com编程精彩实例有哪些软件

com口定义_com编程精彩实例有哪些软件COM编程攻略(四COM对象创建的原理及ATL实现)_com编程

特别说明 —— 该系列文章皆转自知乎作者 Froser 的COM编程攻略:https://www.zhihu.com/column/c_



前言

上一篇文章中说到,微软推出了ATL库实现了IUnknown接口,推出了基于继承(CComObject<>)的模板类和基于聚合(CComAggObject<>)的模板类。

这篇文章来将一下,如何创建一个COM对象,它的统一创建函数的签名为何是这样设计的。


一、传统的C++对象创建方式

假设客户的代码运行在A.exe,客户使用了开发商提供的一个库B.dll,B.dll中导出了一个类ClassB,那么在A.exe中,创建一个ClassB对象,假如ClassB是这样的:

interface IRead : IUnknown { 
    ... }; interface IWrite : IUnknown { 
    ... }; class ClassB : public IRead, public IWrite { 
    ... }; 

那么可以是这样来写:

ClassB* b = new ClassB(); 

这样,我们便可以将ClassB对象创建出来。不过,它有几个问题:

  1. new运算符跨越了边界。operator new的行为在A中被实现,例如虚表指针是在哪里,是否在结构中有额外的数据,而ClassB实现在B.dll。除非我们使用兼容的编译器,生成相同的ClassB结构,否则可能会引起一些二进制兼容问题。
  2. new对象和释放对象可能不在同一个模块。尤其是COM对象,分配在堆上面的COM对象,是通过Release()来释放自己的,如果在另外的模块new出来,在自己模块释放,在某些情况下(如mt, md不一致),会出现问题。为了解决这个问题,我们需要在模块B.dll中暴露一个创建对象的函数。
  3. 一个ClassB这样的COM对象,我们不关心它的实现类ClassB,而只需要拿它的某一个接口。例如,我们只需要在ClassB中拿到IRead或者IWrite接口,如果我们调用了ClassB中除了这两个接口之外的函数或者成员并调用了,那么它就和ClassB的实现耦合了起来,违背了COM对象解决二进制兼容的原则。所以,在创建对象的时候,我们要指明,我们需要拿哪个接口。

综上,我们设计出第一版创建ClassB的函数(伪代码),它实现在了B.dll:

void* CreateInstance(REFIID iid) { 
    ClassB* b = new ClassB(); if (iid是IID_IWrite) return static_cast<IWrite*>(b); if (iid是IID_IRead) return static_cast<IRead*>(b); return nullptr; } 

上面这一版是我们创建ClassB的雏形,它需要进一步优化。首先,按照COM标准,它需要返回一个值指示是否转换成功。其次,它需要获取什么接口,可以由ClassB::QueryInterface来得到。于是我们设计出了第二版创建函数:

HRESULT CreateInstance(REFIID iid, void** ppvObject) { 
    ClassB* b = new ClassB(); return b->QueryInterface(iid, ppvObject); } 

接下来我们再进行一次优化。回想起CComAggObject,它是一个聚合模型,它本身是IUnknown,它所包含的对象也是一个IUnknown。我们创建的IUnknown可能就是要塞入这个CComAggObject聚合对象中的,这种情况下,创建出来的对象必须要感知到,外层对象(也就是CComAggObject所包含的对象)的指针是什么,这样自己在QueryInterface, AddRef或Release的时候才能转调外层。为了支持这种形式的创建,我们最终将签名改为:

HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID iid, void** ppvObject); 

上面的例子创建的是继承关系下的reader对象,在ATL中,reader其实就是CComObject。如果是创建一个聚合对象,那么它其实就是创建的CComAggObject。

以上便是COM中最标准的创建函数的签名。当然HRESULT应该返回什么,MSDN上做了详细的说明,大家实现的时候记得要参考MSDN。

二、ATL中的CreateInstance的实现

在这里插入图片描述
我们再次看一下这个继承关系图。上一篇文章里说到,CComObjectRoot提供了内部的AddRef, Release和QueryInterface的实现。那么这一篇文章就介绍CComCoClass<>,它主要是在CYourClass中添加了CreateInstance静态方法,并且可以指定这个对象创建出来是继承模型(CComObject<>)还是聚合模型(CComAggObject<>)。

CComCoClass实现在atlcom.h:

template <class T, const CLSID* pclsid = &CLSID_NULL> class CComCoClass { 
    public: DECLARE_CLASSFACTORY() DECLARE_AGGREGATABLE(T) typedef T _CoClass; template <class Q> static HRESULT CreateInstance( _Inout_opt_ IUnknown* punkOuter, _COM_Outptr_ Q** pp) { 
    return T::_CreatorClass::CreateInstance(punkOuter, __uuidof(Q), (void**) pp); } template <class Q> static HRESULT CreateInstance(_COM_Outptr_ Q** pp) { 
    return T::_CreatorClass::CreateInstance(NULL, __uuidof(Q), (void**) pp); } 

上面源码省略了错误处理相关函数,且我们先不用关系第二个模板参数pclsdid的含义。

关注到CreateInstance的两个重载。它调用了模板T中的_CreatorClass类中的CreateInstance函数。区别在于,一个需要传入一个punkOuter,一个不需要,默认传的是NULL,我们便推测得出这两个方法,一个是用于创建聚合对象,聚合对象的QueryInterface将转调punkOuter->QueryInterface;一个是用于创建继承模型下的对象的。

那么,_CreatorClass到底是什么呢?我们看到有一个宏:

DECLARE_AGGREGATABLE(T) 

它定义在了altcom.h:

#define DECLARE_AGGREGATABLE(x) public:\ typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass; 

其中CComCreator用于创建一个对象:

template <class T1> class CComCreator { 
    public: static HRESULT WINAPI CreateInstance( _In_opt_ void* pv, _In_ REFIID riid, _COM_Outptr_ LPVOID* ppv) { 
    ATLASSERT(ppv != NULL); if (ppv == NULL) return E_POINTER; *ppv = NULL; HRESULT hRes = E_OUTOFMEMORY; T1* p = NULL; ATLPREFAST_SUPPRESS(6014 28197) /* prefast noise VSW */ ATLTRY(p = _ATL_NEW T1(pv)) ATLPREFAST_UNSUPPRESS() if (p != NULL) { 
    p->SetVoid(pv); p->InternalFinalConstructAddRef(); hRes = p->_AtlInitialConstruct(); if (SUCCEEDED(hRes)) hRes = p->FinalConstruct(); if (SUCCEEDED(hRes)) hRes = p->_AtlFinalConstruct(); p->InternalFinalConstructRelease(); if (hRes == S_OK) { 
    hRes = p->QueryInterface(riid, ppv); _Analysis_assume_(hRes == S_OK || FAILED(hRes)); } if (hRes != S_OK) delete p; } return hRes; } }; 

我们看到CComCreator,T1在DECLARE_AGGREGATABLE中无非就是CComObject或CComAggObject,说明T1是指定了它的聚合模式。如果T1是CComObject,那么就有

CComObject* p = new CComObject(pv); 

如果是T1是CComAggObject,那么

CComAggObject* p = new CComAggObject(pv); 

事实上,CComObject中拿到了pv根本没有用上,因为它不存在外部对象。pv只对于CComAggObject才是有效的。

CComCreator2的实现则显得非常鸡贼:

template <class T1, class T2> class CComCreator2 { 
    public: static HRESULT WINAPI CreateInstance( _In_opt_ void* pv, _In_ REFIID riid, _COM_Outptr_ LPVOID* ppv) { 
    ATLASSERT(ppv != NULL); return (pv == NULL) ? T1::CreateInstance(NULL, riid, ppv) : T2::CreateInstance(pv, riid, ppv); } }; 

它其实是一个选择器,根据是否有外部对象pv,来决定是调用哪一个的CreateInstance。

在:

#define DECLARE_AGGREGATABLE(x) public:\ typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass; 

定义了DECLARE_AGGREGATABLE的情况下,T1=CComCreator<CComObject>,T2=CComCreator<CComAggObject>,说明存在pv,则用T1创建CComObject,否则用T2创建CComAggObject。

除了这个宏,ATL还有另外几个宏DECLARE_NOT_AGGREGATABLE, DECLARE_ONLY_AGGREGATABLE, DECLARE_POLY_AGGREGATABLE:

#define DECLARE_NOT_AGGREGATABLE(x) public:\ typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass; #define DECLARE_ONLY_AGGREGATABLE(x) public:\ typedef ATL::CComCreator2< ATL::CComFailCreator<E_FAIL>, ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass; #define DECLARE_POLY_AGGREGATABLE(x) public:\ typedef ATL::CComCreator< ATL::CComPolyObject< x > > _CreatorClass; 

其中CComFailCreator实现为无论如何都会失败的Creator,并返回HRESULT T:

template <HRESULT hr> class CComFailCreator { 
    public: static _Always_(_Post_satisfies_(return == hr || return == E_POINTER)) HRESULT WINAPI CreateInstance( _In_opt_ void*, _In_ REFIID, _COM_Outptr_result_maybenull_ LPVOID* ppv) { 
    if (ppv == NULL) return E_POINTER; *ppv = NULL; return hr; } }; 

通过以上分析,我们就知道了,CComCoClass需要和DECLARE_AGGREGATABLE, DECLARE_NOT_AGGREGATABLE, DECLARE_ONLY_AGGREGATABLE, DECLARE_POLY_AGGREGATABLE其中一个一起使用,调用它的CreateInstance方法创建对象。它们的区别在于:

  1. DECLARE_AGGREGATABLE: 如果有外部对象pv,创建CComAggObject;否则创建CComObject
  2. DECLARE_NOT_AGGREGATABLE: 如果有外部对象pv,创建失败;否则创建CComAggObject
  3. DECLARE_ONLY_AGGREGATABLE: 如果有外部对象pv,创建CComAggObject,否则创建失败。
  4. DECLARE_POLY_AGGREGATABLE: 创建CComPolyObject

以上4个宏,指定的就是创建COM对象的模型。

就这样,我们便可以通过CComCoClass来为CYourClass添加一个标准的创建函数了。

下一篇将讲类厂及如何用COM API来创建COM对象。

今天的文章
com口定义_com编程精彩实例有哪些软件分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/80610.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注