ATL中IUnknown接口的实现(一)
ATL和MFC都能作为开发COM的工具,而ATL是一个Template Library,MFC却是一个FrameWork,作为Template Library,其优势在于其只是提供给程序员形式各异,可以用来构建软件的小积木,由程序员的意愿架构出任意形状的软件大厦;而FrameWork却是已经把大厦的主体建立好,我们只需要往里面添砖加瓦,做一些细节上的修整,以让其符合我们的要求。两者各有千秋。不过由于Template拥有区别于OO的弹性(通过指定特定的模板参数产生符合要求的模板类和模板函数)以及Template的具现化是在编译期间实现,运行效率高,所以与FrameWork相比,Template Library给人的感觉更加灵活、精致、有效。
COM技术的核心是IUnknown接口,说具体点,就是AddRef()、Release()、QueryInterface()三个方法的具体实现。前两个接口的复杂性在于COM拥有不同的线程模型:微软定义的COM线程模型有3种:STA、MTA以及Free,三种模型下,引用计数的处理均有不同;最后一个接口的作用是接口查询——给我一个riid,我能给出这个riid的指针供你调用。它的复杂性主要体现在COM对象被聚合的时候如何能得到正确的接口指针。虽然情况有很多种,可是一旦确定了线程模型和COM对象的实体身份,我们就能实作出这三个接口的通用的动作方法。ATL正是帮我们完成了这个工作,我们只需要将COM对象的线程模型、实体身份作为模板参数传递给ATL,ATL就会自动为我们产生出正确的代码——而这一切均是在编译期间确定的。
对于引用计数的处理,无非是两个动作:引用计数的加一和减一。多线程时加减的操作由API函数InterlockedIncrement()和InterlockedDecrement()完成;单线程时,简单地++m_cRef和—m_Ref即可。作为一个Library,对这些操作进行一定的包装是非常必要的,我们需要通过这个包装,给外界提供一致的接口。同时,由于对象被聚合时,引用计数的操作又不一样,所以,我们所需要的包装不只一层,不只需要InternalAddRef(),还应该需要OutAddRef()。下面先来看InternalAddRef()的实现。
ATL中把不同线程模型下引用计数的实作封装到同一个函数中:加法是Increment(),减法是Decrement()。三个代表不同线程模型的类:CComSingleThreadModel、CComMultiThreadModel 、CComMultiThreadModelNoCS中均实作了这两个函数。这样,我们只需要把线程模型当作模板参数传递给对象,对象就能获得正确的引用计数操作动作。
同时,既然已经有了线程模型的定义,那么“临界区”也就顺理成章地应该在这些线程模型的类中得到定义——这样也给“临界区”的操作提供了统一的界面。我们只需要在任何操作变量的时候,都使用这个界面操作,那么,只需要更改一个线程模型参数,我们的程序就可
以由单线程支持变成多线程支持。这三个类的实作如下:
//From ATLbase.h
class CComMultiThreadModelNoCS
{
public:
static ULONG WINAPI Increment(LPLONG p) throw()
{return InterlockedIncrement(p);}
static ULONG WINAPI Decrement(LPLONG p) throw()
{return InterlockedDecrement(p);}
typedef CComFakeCriticalSection AutoCriticalSection;
typedef CComFakeCriticalSection CriticalSection;
typedef CComMultiThreadModelNoCS ThreadModelNoCS;
};
class CComMultiThreadModel
{
public:
static ULONG WINAPI Increment(LPLONG p) throw()
{return InterlockedIncrement(p);}
static ULONG WINAPI Decrement(LPLONG p) throw()
{return InterlockedDecrement(p);}
typedef CComAutoCriticalSection AutoCriticalSection;
typedef CComCriticalSection CriticalSection;
typedef CComMultiThreadModelNoCS ThreadModelNoCS;
};
class CComSingleThreadModel
{
public:
static ULONG WINAPI Increment(LPLONG p) throw() {return ++(*p);}
static ULONG WINAPI Decrement(LPLONG p) throw() {return --(*p);}
typedef CComFakeCriticalSection AutoCriticalSection;
typedef CComFakeCriticalSection CriticalSection;
typedef CComSingleThreadModel ThreadModelNoCS;
};
ATL中有3种临界区CComCriticalSection、CComAutoCriticalSection、CComFakeCriticalSection。每一个临界区都提供了统一的4种操作来操作临界区:Lock()、Unlock()、Init()、Term() 。我们在程序中使用“临界区”时,只需要调用这四种操作就可以达到保护数据成员不被其他线程修改的目的。调用方法如下:
ThreadingModel::AutoCriticalSection m_cs ;
m_cs.Lock();
m_cs.Unlock();
三种临界区的实作如下:
//From ATLCore.h
class CComCriticalSection
{
public:
CComCriticalSection() throw()
{
memset(&m_sec, 0, sizeof(CRITICAL_SECTION));
}
HRESULT Lock() throw()
{
EnterCriticalSection(&m_sec);
return S_OK;
}
HRESULT Unlock() throw()
{
LeaveCriticalSection(&m_sec);
return S_OK;
}
HRESULT Init() throw()
{
HRESULT hRes = S_OK;
__try
{
InitializeCriticalSection(&m_sec);
}
// structured exception may be raised in low memory situations
__except(EXCEPTION_EXECUTE_HANDLER)
{
if (STATUS_NO_MEMORY == GetExceptionCode())
hRes = E_OUTOFMEMORY;
else
hRes = E_FAIL;
}
return hRes;
}
HRESULT Term() throw()
{
DeleteCriticalSection(&m_sec);
return S_OK;
}
CRITICAL_SECTION m_sec;
};
class CComAutoCriticalSection : public CComCriticalSection
{
public:
CComAutoCriticalSection()
{
HRESULT hr = CComCriticalSection::Init();
if (FAILED(hr))
AtlThrow(hr);
}
~CComAutoCriticalSection() throw()
{
CComCriticalSection::Term();
}
private :
HRESULT Init(); // Not implemented. CComAutoCriticalSection::Init should never be called
HRESULT Term(); // Not implemented. CComAutoCriticalSection::Term should never be called
};
class CComFakeCriticalSection
{
public:
HRESULT Lock() throw() { return S_OK; }
HRESULT Unlock() throw() { return S_OK; }
HRESULT Init() throw() { return S_OK; }
HRESULT Term() throw() { return S_OK; }
};
ATL提供了CComObjectRootEx模板类,来封装上面关于引用计数的操作以及临界区的操作。只要提供给CComObjectRootEx一个代表其线程模型的模板参数,它就能实作出InternalAddRef()、InternalRelease()以及Lock()和UnLock()四个统一的操作界面。
经过了这么多层封装,ATL已经把AddRef()和Release()所需要的操作全部实作出来了,可是ATL还是没有迈出最后的一步——没有把这些操作整合进AddRef()和Release(),这是因为ATL还要考虑聚合的因素。在聚合的情况下,COM对象的AddRef()和Release()操作都和独立激活时候完全不同。这是后话,下面来看看独立激活时QueryInterface()的实现。
有先贤曾云:表驱动、Hash表、Cache技术是计算机技术3个最重要的核心。微软对于表驱动技术的发挥在其类库是无所不用其极,MFC就是一个最典型的例子,就连孟岩这样的C++老手,也丝毫不掩饰其对MFC中“稀奇古怪”的宏的头疼。ATL中,微软又一次向我们展示了他令人眩目的表驱动实作手法:接口有接口映射表、对象有对象映射表、类别有类别映射表、连接点有连接点映射表、消息有消息映射表。每一个表带来的是什么?是宏,是静态的函数绑定,是执行期效率的显著提升,是跨越不同编译器ABI(应用程序二进制界面)的能力;当然,同时也是对程序代码的恶性膨胀,对C++多态特性的视而不见,对代理、委托等设计模式的退避三舍。(由此观之,是否觉得宏和模板有些类似?)
尽管表驱动有方方面面的优点和缺点,但是不可否认,QueryInterface()的运作机理很适合用表驱动来实现,从而达到美化代码,提高工作效率的目的。我们只需要把我们想要暴露的接口用如下的形式提供给ATL,ATL就会为我们实作出准确的QueryInterface():
BEGIN_COM_MAP(CMyClass)
COM_INTERFACE_ENTRY(IMyInterface)
END_COM_MAP
想暴露多少个接口,就写多少个COM_INTERFACE_ENTRY,ATL会自动用这些声明生成一个名为_ATL_INTMAP_ENTRY的接口表,然后在CComObjectRootBase类中提供一个InternalQueryInterface()的函数用这张表为参数,实作出正确的QueryInterface().当然,COM对象提供接口的方法有很多,COM_INTERFACE_ENTRY提供的只是最基本的输出接口的方法,其它的还有双接口、Tear-Off接口、聚合接口(又分为“有计划的”和“盲”的)、通过CHAIN得到基类的接口等,这些内容等以后再逐一写出。
除开上面这张表,BEGIN_COM_MAP另一个重要的作用是提供了一个GetUnknown()函数,这让我们在聚合、继承的时候,我们不会丢掉我们的IUnknown指针!
前面的讨论都是COM对象为普通对象,独立激活的时候的情况,当发生聚合等关系的时候,我们的类的实作会有另一番风景。期待下文吧!
由于本人学识所限,文章内容错在所难免,如有批评指正之词,请mail :firingme@sina.com