使用ATL开发简单COM组件(1)

时间:2022-04-04 02:37:17

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按钮。

 

使用ATL开发简单COM组件(1)

 

 

图5-6 New对话框

单击OK按钮后,将会出现如图5-7所示的ATL COM AppWizard对话框。这时,一定要保证所做的选择和图5-7所示一致。

(2) 单击Finish按钮,在随后出现的一个总结性的对话框(未做图示)中单击OK按钮完成工作。随后ATL工程向导会为用户创建一些框架文件(有关这些文件的具体用处,过一会儿再解释)。

(3) 接下来,请使用如下方法来创建ISimpleMath接口。选择Insert→New ATL Object命令,将会看到如图5-8所示的对话框,按照图中所示选择种类和对象后,单击Next按钮。

 

使用ATL开发简单COM组件(1)

 

 

图5-7 ATL COM AppWizard对话框

 

使用ATL开发简单COM组件(1)

 

 

图5-8 ATL Object Wizard对话框

(4) 这时,就会出现ATLObject Wizard属性对话框。该对话框有两个选项卡,分别是Names和Attributes,按照如图5-9和5-10所示填写和选择对应项目后,单击“确定”按钮即可完成所有操作。

 

使用ATL开发简单COM组件(1)

 

 

图5-9 ATL Object Wizard对话框的Names选项卡

注意:

图5-19中有的文本框不够宽,遮住了一些文字,完整的内容如表5-1所示。

 

使用ATL开发简单COM组件(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完成了对载体四大核心函数的支持。

注意:

对于理解本书的读者来讲,只需要理解到这一步就可以了。更为详细的内部运作机制,已经超出了本书讨论的范畴,请感兴趣的读者参阅其他专著