COM组件(ATL篇)
目录
第1章创建进程内组件
1.1 目标
本章的目标是使用ATL创建一个进程内COM组件。在此组件里,将实现COM类CStatistic及COM接口IStatistic,用来进行统计计算。IStatistic的详细信息如下:
1、方法
void Reset(); //重新开始统计计算
void Add(double dVal); //增加一个数据
2、属性
long Count; //返回数据个数
double Average; //返回平均值
double StdDev; //返回标准差
CStatistic的VC++代码如下:
#include <MATH.H>
class CStatistic { public: CStatistic() {Reset();} public: void Reset() {memset(this,0,sizeof(*this));} void Add(double dVal) { ++m_nCount; //样本个数 m_dSum += dVal; //所有样本值的和 m_dSum2 += dVal * dVal; //所有样本值的平方和 } public://属性定义(VC++的语法) __declspec(property(get=GetCount)) ULONG Count; __declspec(property(get=GetAverage)) double Average; __declspec(property(get=GetStdDev)) double StdDev; public: ULONG GetCount() const {return m_nCount;} double GetAverage() const { if(m_nCount) { return m_dSum / m_nCount; } return 0.0; } double GetStdDev() const { double d = 0.0; if(m_nCount > 1) { d = (m_nCount * m_dSum2 - m_dSum * m_dSum) / (m_nCount * (m_nCount - 1)); if(d > 0.0) { d = sqrt(d); } } return d; } private: ULONG m_nCount; //样本个数 double m_dSum; //所有样本值的和 double m_dSum2; //所有样本值的平方和 }; |
测试代码如下:
ULONG n = 0; double d = 0.0; CStatistic s; s.Reset(); s.Add(1.0); s.Add(2.0); s.Add(3.0); s.Add(4.0); n = s.Count; //(1,2,3,4)的个数 d = s.Average; //(1,2,3,4)的平均值 d = s.StdDev; //(1,2,3,4)的标准差 s.Add(5.0); n = s.Count; //(1,2,3,4,5)的个数 d = s.Average; //(1,2,3,4,5)的平均值 d = s.StdDev; //(1,2,3,4,5)的标准差 |
这个类的优点在于:它能实时获得样本数据的平均值、标准差,且不用把样本数据存入数组,因此可以连续的长时间工作。
1.2 创建项目
1.2.1 VC++6.0
运行VC++6.0,新建"ATL COM AppWizard"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图1.1
显示界面如下所示,直接单击"Finish"按钮。
图1.2
显示界面如下,单击"OK"按钮,完成项目创建。
图1.3
1.2.2 VC++2010
运行VC++2010,新建"ATL Project"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图1.4
显示创建向导,界面如下面两张图所示:
图1.5 创建向导——页面一
图1.6 创建向导——页面二
单击上图的"Finish"按钮,完成项目的创建。
1.3 增加COM类
现在,往项目里增加COM类。
1.3.1 VC++6.0
单击【Insert】【New ATL Object...】菜单项
图1.7
显示界面如下。请选中"Objects"里的"Simple Object",然后单击"Next"按钮。
图1.8
显示界面如下,一共有两个页面。
图1.9 增加COM类——页面一
下图所示界面里,Interface有两个选项:Dual表示双接口(也叫自动化接口),它派生自IDispatch;Custom表示自定义接口,它派生自IUnknown。自定义接口直接访问虚函数表,其效率较高。自动化接口效率较低,但是它支持的语言较多。
图1.10 增加COM类——页面二
1.3.2 VC++2010
单击【Project】【Add Class...】菜单项
图1.11
选中"ATL Simple Object",然后单击"Add"按钮
图1.12
显示界面如下,一共有三个页面
图1.13 增加COM类——页面一
图1.14 增加COM类——页面二
图1.15 增加COM类——页面三
1.3.3 项目结构
VC++6.0的类视图里增加了"CStatistic"和"IStatistic"。
IStatistic是COM接口,客户端程序通过它访问COM组件。
CStatistic是COM类,真正的工作由它来完成。
图1.16
同时,idl文件也发生了变化,如下表所示。增加了接口IStatistic,增加了COM类Statistic,这个COM类实现了接口IStatistic。
// comDLLatl.idl import "oaidl.idl"; import "ocidl.idl"; [ uuid(0FEBC618-38BC-4A5B-AE09-3C56635F4D73), version(1.0), helpstring("comDLLatl 1.0 Type Library") ] library COMDLLATLLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ object, uuid(7FB59FA6-E715-4C23-9520-DE93293E0B5F), helpstring("IStatistic Interface"), pointer_default(unique) ] interface IStatistic : IUnknown { }; [ uuid(471BF24D-B793-44A1-9205-7BF36A6EB698), helpstring("Statistic Class") ] coclass Statistic { [default] interface IStatistic; }; }; |
1.4 增加方法
1.4.1 VC++6.0
鼠标右键单击接口IStatistic,弹出菜单中单击【Add Method...】菜单项
图1.17
下图就是增加方法的界面。这里增加了方法void Add(double dVal)。单击"OK"按钮,完成方法的增加。注意:对于自动化接口,Return Type只能是HRESULT。
图1.18
可使用同样的方法,增加方法void Reset()。
1.4.2 VC++2010
鼠标右键单击接口IStatistic,弹出菜单中单击【Add】【Add Method...】。
图1.19
增加方法的界面如下。与VC++6.0的大致相同。单击"Finish"按钮,完成方法void Add(DOUBLE dVal)的添加。
图1.20
可使用同样的方法,增加方法void Reset()。
1.5 增加属性
1.5.1 VC++6.0
在图1.17中,单击【Add Property...】菜单项。显示界面如下:
这里增加了属性long Count。注意:没有设置"Put function",说明这个属性是只读属性。
单击"OK"按钮,完成属性的增加。
图1.21
同样方法,可以增加属性double Average和double StdDev。
1.5.2 VC++2010
在图1.19中,单击【Add Property...】菜单项。显示界面如下:
这里增加了属性ULONG Count。注意:没有设置"Put function",说明这个属性是只读属性。
单击"Finish"按钮,完成属性的增加。
图1.22
同样方法,可以增加属性double Average和double StdDev。
1.6 删除方法、属性
使用ATL,删除属性、方法,似乎只能手动进行,相当的麻烦。
1.7 编码
1.7.1 增加成员变量
请给CStatistic增加三个成员变量
private: ULONG m_nCount; double m_dSum; double m_dSum2; |
1.7.2 初始化成员变量
CStatistic的构造函数里,初始化这三个成员变量
CStatistic() { m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; } |
1.7.3 实现Add
STDMETHODIMP CStatistic::Add(double dVal) { ++m_nCount; //样本个数 m_dSum += dVal; //所有样本值的和 m_dSum2 += dVal * dVal; //所有样本值的平方和 return S_OK; } |
1.7.4 实现Reset
STDMETHODIMP CStatistic::Reset() { m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; return S_OK; } |
1.7.5 实现get_Count
STDMETHODIMP CStatistic::get_Count(long *pVal) { *pVal = m_nCount; return S_OK; } |
1.7.6 实现get_Average
STDMETHODIMP CStatistic::get_Average(double *pVal) { if(m_nCount) { *pVal = m_dSum / m_nCount; } else { *pVal = 0.0; } return S_OK; } |
1.7.7 实现get_StdDev
STDMETHODIMP CStatistic::get_StdDev(double *pVal) { *pVal = 0.0; if(m_nCount > 1) { *pVal = (m_nCount * m_dSum2 - m_dSum * m_dSum) / (m_nCount * (m_nCount - 1)); if(*pVal > 0.0) { *pVal = sqrt(*pVal); } } return S_OK; } |
1.8 注册、注销
编译comDLLatl,即可得到进程内COM组件comDLLatl.dll。使用它之前,需要注册。
注册组件可使用如下任意一条命令。它们原理相同:都是载入comDLLatl.dll,然后调用DllRegisterServer函数
regsvr32 comDLLatl.dll |
Rundll32 comDLLatl.dll,DllRegisterServer |
注销组件可使用如下任意一条命令。它们原理相同:都是载入comDLLatl.dll,然后调用DllUnregisterServer函数
regsvr32 /u comDLLatl.dll |
Rundll32 comDLLatl.dll,DllUnregisterServer |
注意:VC++2010可以编译生成64位的COM组件。在64位操作系统上,regsvr32.exe和Rundll32.exe将自动识别COM组件是32位的还是64位的。注册信息会写入注册表的如下几个位置。注意这里的<ProgID>其实就是comDLLatl.Statistic。
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib\ HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\ HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID HKEY_LOCAL_MACHINE\SOFTWARE\Classes\<ProgID> |
对于64位组件,注册程序直接访问上述注册表项;对于32位组件,注册程序会将上述注册表项映射至32位的注册表项。如此一来,同一个组件的32位、64位是可以同时注册在64位Windows上的,它们互不干涉。
第2章 VC++使用组件
2.1 #import
请参考《COM组件(MFC篇)》。需要注意的是:MFC创建的COM组件,其接口必定是Dual接口(图1.10、图1.15中的Interface选项),也就是派生自IDispatch的自动化接口。ATL可以创建Custom接口,这种接口将派生自IUnknown,tli文件里的函数通过虚函数实现,效率较高。Custom接口的不足之处在于:它可能不能被某些脚本语言(如:VBS)调用。
2.2 MFC包装类
如果接口是Dual接口,就可以生成MFC包装类。具体操作请参考《COM组件(MFC篇)》。
2.3 C语言调用
使用C语言也可以访问COM组件。
如果接口是Dual接口,请参考《COM组件(MFC篇)》。
如果接口是Custom接口,则可以使用编译类型库时产生的C/C++头文件。如下面这段代码:(节选自comDLLatl_i.h)
#define IStatistic_Add(This,dVal) ((This)->lpVtbl->Add(This,dVal)) #define IStatistic_get_Count(This,pVal) ((This)->lpVtbl->get_Count(This,pVal)) |
亦即:可以通过宏IStatistic_Add、IStatistic_get_Count去访问方法、属性。