IDL文件时COM技术实现的一个关键,在IDL当中定义接口的实现。每一个接口包含两个部分,第一个部分是用方括号括起来的属性。在所有的属性里面比较重要的是接口的IID,这是唯一标识COM接口的标识符,紧接着接口属性的定义,是整个接口的定义,所有的借口都是从IUnknown继承而来的——这样才能利用IUnknown的一些已经定义的特性,典型的定义如下所示:
<span style="font-size:14px;"> [ object, uuid(CCA7F35D-DAF3-11D0-8C53-0080C73925BA), oleautomation, helpstring("IPoint Interface"), pointer_default(unique) ] interface IPoint : IUnknown { HRESULT GetCoords([out] long *px, [out] long *py); HRESULT SetCoords([in] long x, [in] long y); };</span>
在定义了接口之后,就需要定义接口的实现了,也就是所谓的类的定义。类也有自身的特殊的ID被称为CLSID,不过属性的定义和接口属性的定义是类似的。当然所有的类的实现都有一个静态库文件的定义所包含,因为最后生成的COM组件毕竟是一个可加载的模块。静态库的属性定义如下面所示,其中有一点需要注意的是,所以在同一个静态库当中实现的类都和静态库的ID号码是一样的。
<span style="font-size:14px;">[ uuid(CCA7F350-DAF3-11D0-8C53-0080C73925BA), version(1.0), helpstring("RectPoint 1.0 Type Library") ] library RECTPOINTLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(CCA7F35E-DAF3-11D0-8C53-0080C73925BA), helpstring("Point Class") ] coclass Point { [default] interface IPoint; }; };</span>
IDL文件只是COM实现的一半,另一半是真正的实现。而这两者维系起来的桥梁就是上面无处不在的UUID属性——对应不同的事物有不同的名称。作为一个可加载的模块,最重要的是找到两样东西,第一是模块的加载入口;第二是模块当中的全局变量——这些全局变量会在模块初始化最开始的时候就初始化了。所以,先看两个全局的变量,第一个是IGlobalInterfaceTable*g_pGIT,这个指针提供进程当中套间之间的相互访问;另一个是CComModule_Module,这个全局变量使用一个数组来维护类的管理。当然这个数组和MFC当中的数组定义一样也是通过一组宏来实现的:
<span style="font-size:14px;">BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Point, CPoint) OBJECT_ENTRY(CLSID_Rect, CRect) END_OBJECT_MAP()</span>至于这个宏的展开分析,可以参考MFC当中宏的分析,然后在入口函数当中_Module会调用Init函数手工的将数组ObjectMap给填充到自身的数组当中。经过上面的处理之后,基本就可以从这个模块当中获取接口的信息了。然而,这时的COM还不具备自动配置的能力,程序员必须知道COM模块在那个地方才能得到模块当中的接口。所以还需要进一步的加工才能实现与机器无关性,这个时候就需要我们之前在IDL当中定义的一些ID了。通过相应的模块导出的DllRegisterServer函数实现将相应的ID和DLL模块的路径给加载到注册表当中,这个操作可以通过regsvr32小程序进行这个操作。在DllRegisterServer函数当中调用_Module类的RegisterServer函数完成——毕竟_Module类别包含一个数组,这个数组又具备相应的类别的信息。可以讲模块信息给保存到注册表,当然也希望有一个函数进行逆操作,这个函数就是DllUnregisterServer,当然这个函数也是通过调用_Module的UnregisterServer函数实现的。除此之外,COM接口还必须实现两个主要的功能,第一个是根据相应的调用获取实现接口的类,第二个是选择在适当的时机卸载这个模块。而这个功能的实现同样需要_Module的辅助:
<span style="font-size:14px;">STDAPI DllCanUnloadNow(void) { return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.GetClassObject(rclsid, riid, ppv); }</span>到目前为止,还只是讲解了一个辅助类别的作用。另一个类的指针g_pGIT主要用于处理套间的管理:
<span style="font-size:14px;"> HRESULT Globalize(Itf *pItf) { assert(g_pGIT != 0 && "GIT::Init not called"); assert(m_dwCookie == 0 && "Attempt to Globalize invalidCookie"); return g_pGIT->RegisterInterfaceInGlobal(pItf, *piid, &m_dwCookie); }</span><pre name="code" class="cpp"> HRESULT Unglobalize(void) { assert(g_pGIT != 0 && "GIT::Init not called"); assert(m_dwCookie != 0 && "Attempt to Unglobalize invalid cookie"); HRESULT hr = g_pGIT->RevokeInterfaceFromGlobal(m_dwCookie); m_dwCookie = 0; return hr; }