5.7 使用ATL开发简单COM组件
由于COM技术的特殊要求,不管要开发什么样的COM组件,都必须实现一些基础功能,比如实现IUnknown、类工厂、向注册表中注册必要的信息、在组件载体中实现诸如DllGetObject这样的引出函数等。并且,这些功能的实现代码,在结构上不会随着组件功能的不同而不同,这样就会造成每写一个组件都要和诸如引用计数、CLSID、注册表等打交道的情况,既浪费时间又容易出错。因此,许多支持微软技术的厂商(包括微软本身)都在研发用于COM开发的快速开发工具,以使开发人员能从繁琐的重复劳动中解脱出来,将主要精力集中到解决实际业务问题上。
微软公司出品的ATL(ActiveX Template Library,活动模板库)就是这些工具中的佼佼者。它对COM组件的开发提供了非常完整的支持,大大简化了COM开发中的重复性工作。同时,由于应用了模板技术,这些简化并没有降低组件的运行效率。
在本小节中,我们将使用ATL技术来实现SimpleMath组件。该COM组件完整的源代码可以在配书光盘的/SourceList/_Chap05/SimpleMath目录下找到。
注意:
由于在新版本的Visual C++中,ATL进行了更深层次的改革,并可以用一种比以往更为简便的方式进行COM编程。因此,为了在新旧知识中间进行平稳过渡,以下演示的COM组件都是采用Visual C++ 6.0来实现的。如果读者对先前版本的ATL开发技术已经比较熟悉,可以直接跳至第6章,去了解新版本Visual C++在开发COM对象方面所具有的先进特性。
(1) 首先,启动Visual C++6,在集成开发环境中选择File→New命令,打开如图5-6所示的New对话框,然后,按照图中所示选择工程类型和模板,并在指定工程的名称为SimpleMath后,单击OK按钮。
图5-6 New对话框
单击OK按钮后,将会出现如图5-7所示的ATL COM AppWizard对话框。这时,一定要保证所做的选择和图5-7所示一致。
(2) 单击Finish按钮,在随后出现的一个总结性的对话框(未做图示)中单击OK按钮完成工作。随后ATL工程向导会为用户创建一些框架文件(有关这些文件的具体用处,过一会儿再解释)。
(3) 接下来,请使用如下方法来创建ISimpleMath接口。选择Insert→New ATL Object命令,将会看到如图5-8所示的对话框,按照图中所示选择种类和对象后,单击Next按钮。
图5-7 ATL COM AppWizard对话框
图5-8 ATL Object Wizard对话框
(4) 这时,就会出现ATLObject Wizard属性对话框。该对话框有两个选项卡,分别是Names和Attributes,按照如图5-9和5-10所示填写和选择对应项目后,单击“确定”按钮即可完成所有操作。
图5-9 ATL Object Wizard对话框的Names选项卡
注意:
图5-19中有的文本框不够宽,遮住了一些文字,完整的内容如表5-1所示。
图5-10 ATL Object Wizard属性对话框的Attributes选项卡
表5-1 Name选项卡中各选项内容
C++ |
COM |
||
Short Name |
Simple_Math |
CoClass |
CoSimple_Math |
Class |
Csimple_Math |
Interface |
ISimple_Math |
.H File |
Simple_Math.h |
Type |
Simple_Math |
.CPP File |
Simple_Math.cpp |
ProgID |
MyCOM.Simple |
到此为止,虽然没有编写一行代码,但是已经完成了SimpleMath组件所需工作的80%!为什么这样说呢?让我们回忆一下,创建一个简单的COM组件所需要做的工作:
(1) 要为COM对象确定载体(DLL或EXE) 并且,如果选择的是DLL,还应当实现几个分别用于实例化类工厂和注册组件信息的引出函数,分别是DllGetClassObject、DllRegisterServer、DllUnregisterServer和DllCanUnloadNow。
(2) 要实现IUnknown接口,因为该接口控制着接口的生命周期。
(3) 要实现类工厂,因为真正需要访问的COM对象要靠与之对应的类工厂来实例化。
(4) 要编写真正的接口,在本例中是ISimpleMath。
(5) 要实现接口中的函数。在本例中,应当给出Add、Sub、Mul和Div四个方法的真正实现代码。
而现在除了最后一步外,其他工作都已经完成了。大家是否对这个结论感到不可思议?下面我们就一一对应地从向导所创建的代码中找到ATL解决问题之道。
对于要求1,向导根据我们输入的工程名称,产生了一个名为SimpleMath.cpp的源程序文件,这个源程序文件的代码如下:
// SimpleMath.cpp : Implementation of DLL Exports.
#include "stdafx.h"
#include "resource.h"
#include <initguid.h>
#include "SimpleMath.h"
#include "SimpleMath_i.c"
#include "Simple_Math.h"
/////////////////////////////////////////////////////////
// 注意重要的全局对象 _Module
CComModule _Module;
// 此组宏为ATL实现组件实例化、更新注册表信息、创建类工厂
// 起着关键作用
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CoSimple_Math, CSimple_Math)
END_OBJECT_MAP()
/////////////////////////////////////////////////////////
// DLL入口点
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance,
DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance, &LIBID_SIMPLEMATHLib);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE; // ok
}
/////////////////////////////////////////////////////////
// 决定是否可以被OLE卸载的引出函数
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);
}
/////////////////////////////////////////////////////////
// 用于注册的两个引出函数
STDAPI DllRegisterServer(void)
{
// 注册对象、类型库和类型库中所有接口
return _Module.RegisterServer(TRUE);
}
STDAPI DllUnregisterServer(void)
{
return _Module.UnregisterServer(TRUE);
}
/////////////////////////////////////////////////////////
在代码中,可以很明显地看出,全局对象_Module完成了对载体四大核心函数的支持。
注意:
对于理解本书的读者来讲,只需要理解到这一步就可以了。更为详细的内部运作机制,已经超出了本书讨论的范畴,请感兴趣的读者参阅其他专著