可以看出,CBeepCnt类基于三个模板类。CComObjectRootEx操作了组件的引用计数;CComCoClass用来定义该对象的默认类工厂和聚合模型;IDispatchImpl,提供了一个双重接口IBeepCnt(接口ID是IID_IbeepCnt,支持自动化接口和自定义接口)。
CBeepCnt类同样提供了一组宏,ATL_NO_VTABLE会告诉编译器不要再为CBeepCnt类生成虚函数,因为ATL_NO_VTABLE必须只能与一个无法直接创建的基类一起使用。在项目中一定不能将declspec(novtable)与基本上是导出的类一起使用,因为该类(通常是 CComObject、CComAggObject 或 CComPolyObject)为项目初始化 vtable 指针。一定不能从任何使用declspec(novtable)的对象的构造函数调用虚函数。应该将那些调用移动到 FinalConstruct(大家要记着这个方法)方法。需要我们切记的是,我们可以尝试着添加一个虚函数,但是编译器会编译错误,切记ATL_NO_VTABLE意味着没有虚函数,不要为基于ATL_NO_VTABLE的类添加虚函数。
当对象创建时,对象实际上是CComObject
图2.7 CcomObject派生图
CComObject的主要工作是提供IUnknown方法的实现。这些必须由最低层的派生类提供,以便所有由IUnknown派生的类可以共享IUnknown方法的实现。CComObject的方法仅仅调用CComObjectRootEx中的实现。
另外需要注意的是COM接口映射,COM接口映射包含着我们实现自定义接口和IDispatch的宏。COM接口映射中包含的接口是可以通过QueryInterface返回的接口指针的接口。 这里还有一对宏。一个用来根据资源ID定义注册入口,一个用来改变对象创建的方法来保证对象不会被意外的删除。
与非采用ATL方式的COM相比,可以看到采用ATL开发的组件中没有引用计数的代码,是因为引用计数的工作由CComObjectRootEx类操作,而不需要我们来操作了。
接着讨论的新文件,beepcnt.rgs,包含了为ATL处理的代码的源脚本。大部分直接反映了为了COM运行时能够找到组件的注册入口。文件如下:
HKCR {
BeepCntMod.BeepCnt.1 = s 'BeepCnt Class' {
CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}' }
BeepCntMod.BeepCnt = s 'BeepCnt Class' {
CLSID = s '{AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF}'
CurVer = s 'BeepCntMod.BeepCnt.1' }
NoRemove CLSID {
ForceRemove {AE73F2F8-4E95-11D2-A2E1-00C04F8EE2AF} = s 'BeepCnt C
lass'
{
ProgID = s 'BeepCntMod.BeepCnt.1'
VersionIndependentProgID = s 'BeepCntMod.BeepCnt'
ForceRemove 'Programmable' InprocServer32 = s '%MODULE%' {
val ThreadingModel = s 'Apartment' }
'TypeLib' = s '{170BBD8D-4DE8-11D2-A2E0-00C04F8EE2AF}'
} }
}
这段脚本用来注册和注销组件。默认注册时,所有的键都会添加到注册表,不管这些键是否在注册表中。当注销时,默认的方式是删除所有在脚本中列出的键。一般情况下,你不需要编辑这个文件,当你添加对象和接口时,向导会自动的实现它。
最后讨论的文件是“BeepCntMod_i.c”,在此文件中,定义了组件的CLSID。CLSID是唯一地标识类或组件的GUID,传统地,CLSID的一般形式为CLSID_
#ifdef __cplusplus extern \#endif
#ifndef __IID_DEFINED__ #define __IID_DEFINED__ typedef struct _IID {
unsigned long x; unsigned short s1; unsigned short s2; unsigned char c[8];
} IID;
#endif // __IID_DEFINED__ #ifndef CLSID_DEFINED #define CLSID_DEFINED typedef IID CLSID; #endif // CLSID_DEFINED
const IID LIBID_BEEPCNTMODLib =
{0x170BBD8D,0x4DE8,0x11D2,{0xA2,0xE0,0x00,0xC0,0x4F,0x8E,0xE2,0xAF}};
#ifdef __cplusplus
} #endif
与添加组件对象前相比,已经存在的文件也有一些小的变化。
?BeepCntMod.cpp现在添加了#includes BeepCnt.h,并且为CbeepCnt添加了一个对象映射入口,每一个COM对象都有一个对象映射入口:
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_BeepCnt, CBeepCnt) END_OBJECT_MAP()
?.idl文件里现在有了IDispatch派生的IBeepCnt接口入口,一个为BeepCnt 类的在类型库的Coclass入口。
当重新编译程序时,MIDL生成了一个新的反映新建组件对象的BeepCntMod.h文件。编译后就生成了组件的DLL文件,在编译的同时完成组件的注册。 2.5 添加组件对象的属性和方法(函数)
上面已经介绍了如何用ATL组件。然而生成的对象是个空的对象,属于这个对象的属性和方法都是空的。如何添加组件对象的属性和方法是本节要讨论的内容。
从Class View中右击IbeepCnt,选择“Add Method…”如图2所示的对话框出现。在Method Name编辑框中,选择返回值类型为HRESULT,方法名输入Beep,参数一项为空,单击OK,完成Beep方法的添加,向导会把所有Beep的定义的代码自动添加到BeepCnt.cpp和 BeepCnt.h文件中。
图2.8向接口添加方法
添加一个属性也是类似的。右击接口,选择“Add Property…”,如图2.9所示的对话框出现。选择合适的类型,输入属性的名称,单击OK完成添加。
相关推荐: