:起步篇。
第三部分:实现IUnknown。
实现接口
现在你已经积累了一些关于ATL线程模型方面的知识,下面我们来讨论ATL如何实现IUnknown。ATL最不直观的(同时也是最强大的)一个方面就是你要实现的类事实上都是不能被直接实例化的抽象类。实现一个从通用的IUnknown派生的C++类。但是在确定对象的运行环境之前,QueryInterface,AddRef 和 Release是不会有实质性代码的。这种灵活性使开发人员能实现对象的关键功能,如COM的聚合支持,tear-offs,堆和栈分配,服务器锁定等等。下图展示了一个典型的基于ATL的类层次。
图四 典型的基于ATL的类层次
从下面的代码可以看出,ATL中实现IUnknown的关键在于CComObjectRootBase 和 CComObjectRootEx。
CComObjectRoot
01.
class
CComObjectRootBase {
02.
public
:
03.
// C++ 构造函数
04.
CComObjectRootBase() { m_dwRef = 0L; }
05.
06.
// ATL 伪构造函数和伪析构函数
07.
HRESULT
FinalConstruct() {
return
S_OK; }
08.
void
FinalRelease() {}
09.
10.
// 内部Unknown函数(由派生类提供的InternalAddRef/Release)
11.
static
HRESULT
WINAPI InternalQueryInterface(
void
* pThis,
12.
const
_ATL_INTMAP_ENTRY* pEntries, REFIID iid,
void
** ppvObject) {
13.
HRESULT
hRes = AtlInternalQueryInterface(pThis,pEntries,iid,ppvObject);
14.
return
_ATLDUMPIID(iid, pszClassName, hRes);
15.
}
16.
17.
// 外部Unknown函数
18.
ULONG
OuterAddRef() {
return
m_pOuterUnknown->AddRef(); }
19.
ULONG
OuterRelease() {
return
m_pOuterUnknown->Release(); }
20.
HRESULT
OuterQueryInterface(REFIID iid,
void
** ppvObject)
21.
{
return
m_pOuterUnknown->QueryInterface(iid, ppvObject); }
22.
23.
// ATL 创建者的钩子例程
24.
void
SetVoid(
void
*) {}
25.
void
InternalFinalConstructAddRef() {}
26.
void
InternalFinalConstructRelease() {}
27.
28.
// ATL 接口映射辅助函数
29.
static
HRESULT
WINAPI _Break(
void
*, REFIID,
void
**,
DWORD
);
30.
static
HRESULT
WINAPI _NoInterface(
void
*, REFIID,
void
**,
DWORD
);
31.
static
HRESULT
WINAPI _Creator(
void
*, REFIID,
void
**,
DWORD
);
32.
static
HRESULT
WINAPI _Delegate(
void
*, REFIID,
void
**,
DWORD
);
33.
static
HRESULT
WINAPI _Chain(
void
*, REFIID,
void
**,
DWORD
);
34.
static
HRESULT
WINAPI _Cache(
void
*, REFIID,
void
**,
DWORD
);
35.
36.
// 实际的引用计数或者指针返回到真实的Unknown
37.
union
{
38.
long
m_dwRef;
39.
IUnknown* m_pOuterUnknown;
40.
};
41.
};
42.
43.
template
44.
class
CComObjectRootEx :
public
CComObjectRootBase {
45.
public
:
46.
typedef
ThreadModel _ThreadModel;
47.
typedef
_ThreadModel::AutoCriticalSection _CritSec;
48.
49.
// 内部 Unknown 函数(InternalQueryInterface 由 CComObjectRootBase提供)
50.
ULONG
InternalAddRef() {
return
_ThreadModel::Increment(&m_dwRef); }
51.
ULONG
InternalRelease() {
return
_ThreadModel::Decrement(&m_dwRef); }
52.
53.
// 对象级的锁定操作
54.
void
Lock() {m_critsec.Lock();}
55.
void
Unlock() {m_critsec.Unlock();}
56.
private
:
57.
_CritSec m_critsec;
58.
};
这两个类提供了三个方法:OuterQueryInterface,OuterAddRef 和 OuterRelease,它们被用来将IUnknown的功能委派给外部实现。当实现COM的聚合和tear-offs时要用到这些方法。其它三个方法--InternalQueryInterface,InternalAddRef和 InternalRelease的作用是实现本身的引用计数以及对象接口的查询或导航。
CComObjectRootEx是个模板类,允许你针对这个类指定使用哪种ATL线程模型。(如果你想要进行条件编译,则使用CComObjectRoot就可以了,它是一个针对CComObjectRootEx的类型定义。)CComObjectRootEx从CComObjectRootBase中派生其大多数功能,它是个相当袖珍的类,只包含一个联合类型的数据成员:
1.
union
{
2.
long
m_dwRef;
3.
IUnknown *m_pOuterUnknown;
4.
};
根据使用这个类的实际方式,联合中的成员将被用于保存给定类实例的生命周期。大多数情况下要用到m_dwRef,m_pOuterUnknown只有在支持聚合或tear-offs时用到。CComObjectRootBase提供了OuterQueryInterface,OuterAddRef和OuterRelease方法,通过m_pOuterUnknown成员转发IUnknown请求。
反过来,CComObjectRootEx提供InternalAddRef 和InternalRelease方法根据模板参数传递的线程模型来实际增减m_dwRef变量得值。注意这些例程只是增减这个变量,而没有真正删除这个对象。这是因为此对象的分配策略将由派生类中提供,派生类将使用这些例程来调整引用计数。
CComObjectRoot层次最引人注目的是它的QueryInterface实现函数,它被作为CComObjectRootBase的方法(InternalQueryInterface)输出:
1.
static
HRESULT
WINAPI
2.
CComObjectRootBase::InternalQueryInterface(
void
*pThis,
3.
const
_ATL_INTMAP_ENTRY *pEntries,
4.
REFIID riid,
void
**ppv);
5.
使用ATL实现IUnknown的每一个类必须制定一个接口映射来提供InternalQueryInterface。ATL的接口映射是IID/DWORD/函数指针数组,它指示当QueryInterface请求一个给定的IID时要采取什么样的行动。其类型都是_ATL_INTMAP_ENTRY。
1.
struct
_ATL_INTMAP_ENTRY {
2.
const
IID* piid;
// 接口ID (IID)
3.
DWORD
dw;
// 多用途值
4.
HRESULT
(*pFunc)(
void
*, REFIID,
void
**,
DWORD
);
5.
};
这个结构的第三个成员pFunc的取值有三种情况。如果pFunc等于常量_ATL_SIMPLEMAPENTRY,则结构成员dw为对象偏移量,这时不需要函数调用,并且InternalQueryInterface完成下列操作:
1.
(*ppv =
LPBYTE
(pThis) + pEntries[n].dw)->AddRef();
这个偏移量的初始化通常参照基类接口的偏移量。如果pFunc非空且不等于_ATL_SIMPLEMAPENTRY,则它指向的函数将被调用,将这个指针作为第一个参数传递给对象而第二个参数是多用途值dw。
1.
return
pEntries[n].pFunc(pThis, riid, ppv,
2.
pEntries[n].dw);
这个接口映射的最后一个入口将使用pFunc值,指示映射的结束。 如果没有在映射中发现任何接口则InternalQueryInterface 会返回E_NOINTERFACE。 接口映射通常为ATL的接口映射宏。ATL提供17个不同的宏,它们支持大多数用于实现接口的普通技术(多继承,嵌套类,聚合或者tear-offs。)这些宏及其相应的原始代码请参见附表三。下面是一个使用CComObjectRootEx和接口映射实现IPager2 和IMessageSource类的例子:
01.
class
CPager
02.
:
public
IMessageSource,
public
IPager2,
03.
public
CComObjectRootEx{
04.
public
:
05.
CPager(
void
) {}
06.
virtual
~CPager(
void
) {}
07.
08.
BEGIN_COM_MAP(CPager)
09.
COM_INTERFACE_ENTRY(IMessageSource)
10.
COM_INTERFACE_ENTRY(IPager2)
11.
COM_INTERFACE_ENTRY(IPager)
12.
END_COM_MAP()
13.
14.
STDMETHODIMP GetNextMessage(OLECHAR **ppwsz);
15.
STDMETHODIMP SendMessage(
const
COLECHAR * pwsz);
16.
STDMETHODIMP SendUrgentMessage(
void
);
17.
};
前面的代码产生的接口映射如下:
1.
{ &IID_IMessageSource, 0, _ATL_SIMPLEMAPENTRY },
2.
{ &IID_IPager2, 4, _ATL_SIMPLEMAPENTRY },
3.
{ &IID_IPager, 4, _ATL_SIMPLEMAPENTRY},
4.
{ 0, 0, 0 }
在建立接口映射时,ATL假设映射中第一个入口是个简单映射入口并用它来满足对IID_IUnknown.的请求。 除了支持IUnknown外,ATL提供大量缺省的COM接口实现。ATL用一个简单的命名规范来为这些实现命名,它们大多数都是作为模板类来实现的,带有一个模板参数,而这些模板参数才是是既要实现的类。 一个简单的例子是IObjectWithSite接口,它一般用于为某个对象提供一个指向激活现场的指针。ATL为这个指针提供了一个缺省的实现:IObjectWithSiteImpl。此类提供了一个IObjectWithSite兼容的二进制签名并且实现了所有的IObjectWithSite方法。为了使用ATL内建的实现,你只需要添加基类实现(用适当的模板参数),然后在接口映射中添加一个入口输出经过QueryInterface实现的接口。 例如,为了使用ATL的IObjectWithSite实现,按照如下步骤来做:
01.
class
CPager
02.
:
public
CComObjectRootEx,
03.
public
IPager,
04.
public
IObjectWithSiteImpl
05.
{
06.
public
:
07.
BEGIN_COM_MAP(CPager)
08.
COM_INTERFACE_ENTRY(IPager)
09.
COM_INTERFACE_ENTRY_IMPL(IObjectWithSite)
10.
END_INTERFACE_MAP()
11.
STDMETHODIMP SendMessage(
const
COLECHAR * pwsz);
12.
};
由于使用了ATL内建的实现类,也就有了COM_INTERFACE_ENTRY_IMPL宏。之所以要用只个宏是因为许多ATL的缺省实现不从它们实现的接口派生。这样的话就导致标准的COM_ INTERFACE_ENTRY宏返回不正确的偏移量。例如,因为CPager不从IObjectWithSite派生,用于计算偏移量的强制类型转换就不会在对象中反映,而是用起始位置代替。 在这个例子中,IObjectWithSiteImpl没有基类。而是按照在IObjectWithSite中一样的顺序声明它的虚函数,产生全兼容的vtable(虚表)结构。ATL使用这个有点不可思议的技术,原因是它允许缺省实现支持接口的引用计数,这一点使用常规多继承技术是很难做到的。 IDispatchImpl也是一个常用的ATL缺省实现。这个类实现用于双接口的四个IDispatch方法,由你的类实现IDispatch::Invoke所不能完成的方法。不像大多数其它的ATL实现,这个类实际上是从一个COM接口派生的,有几个模板参数:
01.
template
<
02.
class
T,
// 双接口
03.
const
IID* piid,
// 双接口IID
04.
const
GUID* plibid,
// 包含类型库TypeLib
05.
WORD
wMajor = 1,
// 类型库的版本
06.
WORD
wMinor = 0,
//类型库的版本
07.
class
tihclass = CComTypeInfoHolder
08.
>
09.
class
IDispatchImpl :
public
T { ... };
10.
11.
假设两个接口是DIPager 和 DIMessageSource。这个类的使用如下:
12.
class
CPager
13.
:
public
CComObjectRootEx,
14.
public
IDispatchImpl,
15.
public
IDispatchImpl
16.
{
17.
public
:
18.
BEGIN_COM_MAP(CPager)
19.
COM_INTERFACE_ENTRY(DIMessageSource)
20.
COM_INTERFACE_ENTRY(DIPager)
21.
// 下一个接口指定DIPager为缺省 [default]
22.
COM_INTERFACE_ENTRY2(IDispatch, DIPager)
23.
END_INTERFACE_MAP()
24.
STDMETHODIMP SendMessage(BSTR pwsz);
25.
STDMETHODIMP GetNextMessage(BSTR *ppwsz);
26.
};
ATL的第一个版本使用CComDualImpl名字,现在它只是IDispatchImpl预处理的一个别名,以便允许1.x版本和2.x版本的工程能在一起编译。(待续)