分享
 
 
 

用ATL建立轻量级的COM对象(四)

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

用ATL建立轻量级的COM对象

第四部分

作者:赵湘宁

第一部分:为什么要使用ATL。

第二部分:起步篇。

第三部分:实现IUnknown。

实现接口

现在你已经积累了一些关于ATL线程模型方面的知识,下面我们来讨论ATL如何实现IUnknown。ATL最不直观的(同时也是最强大的)一个方面就是你要实现的类事实上都是不能被直接实例化的抽象类。实现一个从通用的IUnknown派生的C++类。但是在确定对象的运行环境之前,QueryInterface,AddRef 和 Release是不会有实质性代码的。这种灵活性使开发人员能实现对象的关键功能,如COM的聚合支持,tear-offs,堆和栈分配,服务器锁定等等。下图展示了一个典型的基于ATL的类层次。

图四 典型的基于ATL的类层次

从下面的代码可以看出,ATL中实现IUnknown的关键在于CComObjectRootBase 和 CComObjectRootEx。

CComObjectRoot

class CComObjectRootBase {

public:

// C++ 构造函数

CComObjectRootBase() { m_dwRef = 0L; }

// ATL 伪构造函数和伪析构函数

HRESULT FinalConstruct() { return S_OK; }

void FinalRelease() {}

// 内部Unknown函数(由派生类提供的InternalAddRef/Release)

static HRESULT WINAPI InternalQueryInterface(void* pThis,

const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) {

HRESULT hRes =

AtlInternalQueryInterface(pThis,pEntries,iid,ppvObject);

return _ATLDUMPIID(iid, pszClassName, hRes);

}

// 外部Unknown函数

ULONG OuterAddRef() { return m_pOuterUnknown->AddRef(); }

ULONG OuterRelease() { return m_pOuterUnknown->Release();

}

HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject)

{ return m_pOuterUnknown->QueryInterface(iid, ppvObject);

}

// ATL 创建者的钩子例程

void SetVoid(void*) {}

void InternalFinalConstructAddRef() {}

void InternalFinalConstructRelease() {}

// ATL 接口映射辅助函数

static HRESULT WINAPI

_Break( void*, REFIID, void**, DWORD);

static HRESULT WINAPI _NoInterface( void*, REFIID, void**,

DWORD);

static HRESULT WINAPI _Creator(

void*, REFIID, void**, DWORD);

static HRESULT WINAPI _Delegate( void*,

REFIID, void**, DWORD);

static HRESULT WINAPI

_Chain( void*, REFIID, void**, DWORD);

static HRESULT WINAPI

_Cache( void*, REFIID, void**, DWORD);

// 实际的引用计数或者指针返回到真实的Unknown

union {

long m_dwRef;

IUnknown* m_pOuterUnknown;

};

};

template <class ThreadModel>

class CComObjectRootEx : public CComObjectRootBase {

public:

typedef ThreadModel _ThreadModel;

typedef _ThreadModel::AutoCriticalSection _CritSec;

// 内部 Unknown 函数(InternalQueryInterface 由

CComObjectRootBase提供)

ULONG InternalAddRef() { return _ThreadModel::Increment(&m_dwRef);

}

ULONG InternalRelease()

{ return _ThreadModel::Decrement(&m_dwRef); }

// 对象级的锁定操作

void Lock() {m_critsec.Lock();}

void Unlock() {m_critsec.Unlock();}

private:

_CritSec m_critsec;

};

这两个类提供了三个方法:OuterQueryInterface,OuterAddRef 和 OuterRelease,它们被用来将IUnknown的功能委派给外部实现。当实现COM的聚合和tear-offs时要用到这些方法。其它三个方法--InternalQueryInterface,InternalAddRef和

InternalRelease的作用是实现本身的引用计数以及对象接口的查询或导航。

CComObjectRootEx是个模板类,允许你针对这个类指定使用哪种ATL线程模型。(如果你想要进行条件编译,则使用CComObjectRoot就可以了,它是一个针对CComObjectRootEx<CComObjectThreadModel的类型定义。)CComObjectRootEx从CComObjectRootBase中派生其大多数功能,它是个相当袖珍的类,只包含一个联合类型的数据成员:

union {

long m_dwRef;

IUnknown *m_pOuterUnknown;

};

根据使用这个类的实际方式,联合中的成员将被用于保存给定类实例的生命周期。大多数情况下要用到m_dwRef,m_pOuterUnknown只有在支持聚合或tear-offs时用到。CComObjectRootBase提供了OuterQueryInterface,OuterAddRef和OuterRelease方法,通过m_pOuterUnknown成员转发IUnknown请求。

反过来,CComObjectRootEx提供InternalAddRef 和InternalRelease方法根据模板参数传递的线程模型来实际增减m_dwRef变量得值。注意这些例程只是增减这个变量,而没有真正删除这个对象。这是因为此对象的分配策略将由派生类中提供,派生类将使用这些例程来调整引用计数。

CComObjectRoot层次最引人注目的是它的QueryInterface实现函数,它被作为CComObjectRootBase的方法(InternalQueryInterface)输出:

static HRESULT WINAPI

CComObjectRootBase::InternalQueryInterface(void *pThis,

const _ATL_INTMAP_ENTRY *pEntries,

REFIID riid, void **ppv);

使用ATL实现IUnknown的每一个类必须制定一个接口映射来提供InternalQueryInterface。ATL的接口映射是IID/DWORD/函数指针数组,它指示当QueryInterface请求一个给定的IID时要采取什么样的行动。其类型都是_ATL_INTMAP_ENTRY。

struct _ATL_INTMAP_ENTRY {

const IID* piid; // 接口ID (IID)

DWORD dw; // 多用途值

HRESULT (*pFunc)(void*, REFIID, void**, DWORD);

};

这个结构的第三个成员pFunc的取值有三种情况。如果pFunc等于常量_ATL_SIMPLEMAPENTRY,则结构成员dw为对象偏移量,这时不需要函数调用,并且InternalQueryInterface完成下列操作:

(*ppv = LPBYTE(pThis) + pEntries[n].dw)->AddRef();

这个偏移量的初始化通常参照基类接口的偏移量。如果pFunc非空且不等于_ATL_SIMPLEMAPENTRY,则它指向的函数将被调用,将这个指针作为第一个参数传递给对象而第二个参数是多用途值dw。

return pEntries[n].pFunc(pThis, riid, ppv,

pEntries[n].dw);

这个接口映射的最后一个入口将使用pFunc值,指示映射的结束。

如果没有在映射中发现任何接口则InternalQueryInterface 会返回E_NOINTERFACE。

接口映射通常为ATL的接口映射宏。ATL提供17个不同的宏,它们支持大多数用于实现接口的普通技术(多继承,嵌套类,聚合或者tear-offs。)这些宏及其相应的原始代码请参见附表三。下面是一个使用CComObjectRootEx和接口映射实现IPager2 和IMessageSource类的例子:

class CPager

: public IMessageSource, public IPager2,

public CComObjectRootEx<CComMultiThreadModel>{

public:

CPager(void) {}

virtual ~CPager(void) {}

BEGIN_COM_MAP(CPager)

COM_INTERFACE_ENTRY(IMessageSource)

COM_INTERFACE_ENTRY(IPager2)

COM_INTERFACE_ENTRY(IPager)

END_COM_MAP()

STDMETHODIMP GetNextMessage(OLECHAR **ppwsz);

STDMETHODIMP SendMessage(const COLECHAR * pwsz);

STDMETHODIMP SendUrgentMessage(void);

};

前面的代码产生的接口映射如下:

{ &IID_IMessageSource, 0, _ATL_SIMPLEMAPENTRY },

{ &IID_IPager2, 4, _ATL_SIMPLEMAPENTRY },

{ &IID_IPager, 4, _ATL_SIMPLEMAPENTRY},

{ 0, 0, 0 }

在建立接口映射时,ATL假设映射中第一个入口是个简单映射入口并用它来满足对IID_IUnknown.的请求。

除了支持IUnknown外,ATL提供大量缺省的COM接口实现。ATL用一个简单的命名规范来为这些实现命名,它们大多数都是作为模板类来实现的,带有一个模板参数,而这些模板参数才是是既要实现的类。

一个简单的例子是IObjectWithSite接口,它一般用于为某个对象提供一个指向激活现场的指针。ATL为这个指针提供了一个缺省的实现:IObjectWithSiteImpl。此类提供了一个IObjectWithSite兼容的二进制签名并且实现了所有的IObjectWithSite方法。为了使用ATL内建的实现,你只需要添加基类实现(用适当的模板参数),然后在接口映射中添加一个入口输出经过QueryInterface实现的接口。

例如,为了使用ATL的IObjectWithSite实现,按照如下步骤来做:

class CPager

: public CComObjectRootEx,

public IPager,

public IObjectWithSiteImpl

{

public:

BEGIN_COM_MAP(CPager)

COM_INTERFACE_ENTRY(IPager)

COM_INTERFACE_ENTRY_IMPL(IObjectWithSite)

END_INTERFACE_MAP()

STDMETHODIMP SendMessage(const COLECHAR * pwsz);

};

由于使用了ATL内建的实现类,也就有了COM_INTERFACE_ENTRY_IMPL宏。之所以要用只个宏是因为许多ATL的缺省实现不从它们实现的接口派生。这样的话就导致标准的COM_ INTERFACE_ENTRY宏返回不正确的偏移量。例如,因为CPager不从IObjectWithSite派生,用于计算偏移量的强制类型转换就不会在对象中反映,而是用起始位置代替。

在这个例子中,IObjectWithSiteImpl没有基类。而是按照在IObjectWithSite中一样的顺序声明它的虚函数,产生全兼容的vtable(虚表)结构。ATL使用这个有点不可思议的技术,原因是它允许缺省实现支持接口的引用计数,这一点使用常规多继承技术是很难做到的。

IDispatchImpl也是一个常用的ATL缺省实现。这个类实现用于双接口的四个IDispatch方法,由你的类实现IDispatch::Invoke所不能完成的方法。不像大多数其它的ATL实现,这个类实际上是从一个COM接口派生的,有几个模板参数:

template <

class T, // 双接口

const IID* piid, // 双接口IID

const GUID* plibid, // 包含类型库TypeLib

WORD wMajor = 1, // 类型库的版本

WORD wMinor = 0, //类型库的版本

class tihclass = CComTypeInfoHolder

>

class IDispatchImpl : public T { ... };

假设两个接口是DIPager 和 DIMessageSource。这个类的使用如下:

class CPager

: public CComObjectRootEx<CComMultiThreadModel>,

public IDispatchImpl<DIMessageSource,

&IID_DIMessageSource, &LIBID_PagerLib>,

public IDispatchImpl<DIPager,

&IID_DIPager, &LIBID_PagerLib>

{

public:

BEGIN_COM_MAP(CPager)

COM_INTERFACE_ENTRY(DIMessageSource)

COM_INTERFACE_ENTRY(DIPager)

// 下一个接口指定DIPager为缺省 [default]

COM_INTERFACE_ENTRY2(IDispatch, DIPager)

END_INTERFACE_MAP()

STDMETHODIMP SendMessage(BSTR pwsz);

STDMETHODIMP GetNextMessage(BSTR *ppwsz);

};

ATL的第一个版本使用CComDualImpl名字,现在它只是IDispatchImpl预处理的一个别名,以便允许1.x版本和2.x版本的工程能在一起编译。(待续)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有