首页 > 代码库 > COM组件(MFC篇)
COM组件(MFC篇)
目录
第1章创建进程内组件 1
1.1 目标 1
1.2 创建项目 3
1.2.1 VC++6.0 3
1.2.2 VC++2010 4
1.2.3 VC++6.0与VC++2010的区别 6
1.3 升级项目 6
1.3.1 增加接口定义文件 6
1.3.2 覆盖应用程序类的InitInstance函数 7
1.3.3 导出COM函数 7
1.3.4 修改rc文件 9
1.4 增加COM类 10
1.4.1 VC++6.0 10
1.4.2 VC++2010 11
1.4.3 项目结构 13
1.4.4 VC++6.0与VC++2010的区别 14
1.5 增加方法 14
1.5.1 VC++6.0 14
1.5.2 VC++2010 15
1.6 增加属性 17
1.6.1 VC++6.0 17
1.6.2 VC++2010 17
1.7 删除方法、属性 18
1.7.1 VC++6.0 18
1.7.2 VC++2010 19
1.8 编码 19
1.8.1 增加成员变量 19
1.8.2 初始化成员变量 19
1.8.3 实现Add 20
1.8.4 实现Reset 20
1.8.5 实现GetCount 20
1.8.6 实现GetAverage 20
1.8.7 实现GetStdDev 21
1.9 注册、注销 21
1.10 再论增加COM类 22
第2章创建进程外组件 23
2.1 创建项目 23
2.2 升级项目 23
2.2.1 增加接口定义文件 23
2.2.2 修改rc文件 24
2.2.3 修改应用程序类的InitInstance函数 24
2.2.4 实现COM类 25
2.3 注册、注销 27
第3章 VC++使用组件 29
3.1 #import 29
3.2 MFC包装类 30
3.2.1 VC++6.0生成包装类 30
3.2.2 VC++2010生成包装类 31
3.2.3 包装类的使用 33
3.3 C语言调用 33
第4章 VB6.0使用组件 37
4.1 前期绑定 37
4.1.1 引用类型库 37
4.1.2 查看类型库 38
4.1.3 编码 39
4.2 后期绑定 39
第1章创建进程内组件
1.1 目标
本章的目标是使用MFC创建一个进程内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,新建"MFC AppWizard(dll)"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图1.1
下图显示的界面中,选择"Regular DLL with MFC statically linked",表示创建一个MFC Regular DLL,这个DLL将静态链接MFC共享库。这就意味着,运行时这个DLL不再需要MFC42.dll。非MFC客户程序使用此DLL时,能够减少运行时的依赖项。
一定要勾中"Automation"复选框,它是实现COM组件的关键。
单击"Finish"按钮。
图1.2
显示界面如下,单击"OK"按钮,完成项目创建。
图1.3
1.2.2 VC++2010
运行VC++2010,新建"MFC DLL"项目,如下图所示。配置好项目名称、项目目录后,单击"OK"按钮。
图1.4
显示创建向导,界面如下面两张图所示:
图1.5 创建向导——页面一
页面二的配置与VC++6.0的完全相同。
图1.6 创建向导——页面二
单击上图的"Finish"按钮,完成项目的创建。
1.2.3 VC++6.0与VC++2010的区别
1、VC++6.0缺少DllUnregisterServer函数。这意味着VC++6.0编译生成的COM组件不能通过regsvr32 /u comDLLmfc.dll进行注销;
2、DllRegisterServer函数里,VC++6.0缺少对类型库的注册,即缺少代码AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid);
3、CcomDLLmfcApp::InitInstance函数里,VC++6.0没有调用CWinApp::InitInstance()。
建议:采取VC++2010生成的代码。
1.3 升级项目
假定有一个MFC Regular DLL项目,创建时未勾中"Automation"选项。如何将其转换为COM组件?
1.3.1 增加接口定义文件
增加<dspName>.odl文件至DLL项目,其内容如下:
//<dspName>.odl [uuid(71719C6D-3058-4B13-8C91-9DD49848FADF), version(1.0)] library <TypeLibName> { importlib("stdole32.tlb"); importlib("stdole2.tlb"); //{{AFX_APPEND_ODL}} //}}AFX_APPEND_ODL}} }; |
说明:
1、<dspName>是dsp文件名;
2、71719C6D-3058-4B13-8C91-9DD49848FADF是类型库的GUID,为避免重复,请替换成其它值。最简单的办法就是使用VC++6.0生成的头文件里的宏。如:#if !defined(AFX_DLGDLG_H__8715C0CF_88F1_4CD3_B8E5_64FBCF26B052__INCLUDED_)中的8715C0CF_88F1_4CD3_B8E5_64FBCF26B052就是一个随机的GUID,把下划线替换为减号即可使用;
3、version(1.0)是类型库的版本号。1是主版本号,0是次版本号;
4、<TypeLibName>是类型库的名称,请根据实际需要做相应的修改。
1.3.2 覆盖应用程序类的InitInstance函数
请覆盖应用程序类的InitInstance函数,并在该函数内增加代码COleObjectFactory::RegisterAll();
BOOL CcomDLLmfcApp::InitInstance() { CWinApp::InitInstance(); COleObjectFactory::RegisterAll(); return TRUE; } |
1.3.3 导出COM函数
请实现四个COM函数并导出:DllCanUnloadNow、DllGetClassObject、DllRegisterServer、DllUnregisterServer。代码如下:
const GUID CDECL _tlid = { 0x71719C6D, 0x3058, 0x4B13 , { 0x8C, 0x91, 0x9D, 0xD4, 0x98, 0x48, 0xFA, 0xDF } }; const WORD _wVerMajor = 1; const WORD _wVerMinor = 0;
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,LPVOID* ppv) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllGetClassObject(rclsid, riid, ppv); } STDAPI DllCanUnloadNow(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return AfxDllCanUnloadNow(); } STDAPI DllRegisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll()) return SELFREG_E_CLASS; return S_OK; } STDAPI DllUnregisterServer(void) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if (!AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor)) return SELFREG_E_TYPELIB; if (!COleObjectFactory::UpdateRegistryAll(FALSE)) return SELFREG_E_CLASS; return S_OK; } |
说明:
1、_tlid、_wVerMajor、_wVerMinor依次是类型库的GUID、主版本号、次版本号。请与接口定义文件内容保持一致;
2、VC++6.0的AfxOleUnregisterTypeLib函数只有一个参数,请借鉴VC++.NET的代码。
def文件里导出这四个函数:
EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE |
1.3.4 修改rc文件
rc文件里增加"1 TYPELIB "<dspName>.tlb"",如下表所示:
3 TEXTINCLUDE ... ... ... "#endif\r\n" "1 TYPELIB ""<dspName>.tlb""\r\n" "\0" END |
修改rc文件并保存,VC++会把3 TEXTINCLUDE与END之间的语句自动插入到rc文件的尾部。相当于在rc文件中增加了1 TYPELIB "<dspName>.tlb"。还有一种更为简便的方法:直接把1 TYPELIB "<dspName>.tlb"增加到rc2文件里。
注意:请将<dspName>替换为实际的名称。
对于VC++2010而言,需要设置资源编译器的"Additional Include Directeries"目录。在此,增加<dspName>.tlb所在的目录。如果不增加这个目录,编译rc文件时,将会因为找不到<dspName>.tlb而失败。
图1.7
1.4 增加COM类
现在,往项目里增加COM类。
1.4.1 VC++6.0
单击【Insert】【New Class...】菜单项
图1.8
显示界面如下(如果不显示如下界面,可能就是缺少clw文件或clw文件内容有误。请关闭项目,删除aps、clw、ncb、opt文件。再次打开项目,然后按下Ctrl+W,重新建立clw文件。)
"Class type"请选择"MFC Class"。
"Base class"请选择"CCmdTarget"或其派生类,因为COM组件功能就是由CCmdTarget实现的。
"Automation"必须选择"Createable by type ID"。这个就是COM类的ProgID。客户端程序可以根据这个ProgID实例化COM类。
单击"OK"按钮,完成COM类的创建。
图1.9
1.4.2 VC++2010
单击【Project】【Add Class...】菜单项
图1.10
选中"MFC Class",然后单击"Add"按钮
图1.11
增加MFC类的界面与VC++6.0的类似。配置好后,单击"Finish"按钮,完成COM类的创建。
图1.12
1.4.3 项目结构
VC++6.0的类视图里增加了"CStatistic"和"IStatistic"。
IStatistic是COM接口,客户端程序通过它访问COM组件。
CStatistic是COM类,真正的工作由它来完成。
图1.13
同时,odl文件也发生了变化,如下表所示。增加了接口IStatistic(dispinterface表示这个接口派生自IDispatch)。增加了COM类Statistic,这个COM类实现了接口IStatistic。
[ uuid(71719C6D-3058-4B13-8C91-9DD49848FADF), version(1.0) ] library ComDLLmfc { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(D5FC59B4-255F-415B-933F-08B97A23CD58) ] dispinterface IStatistic { properties: //{{AFX_ODL_PROP(CStatistic) //}}AFX_ODL_PROP methods: //{{AFX_ODL_METHOD(CStatistic) //}}AFX_ODL_METHOD }; [ uuid(E43D739C-1270-4B71-B9DB-D3C74FEDDA19) ] coclass Statistic { [default] dispinterface IStatistic; }; //{{AFX_APPEND_ODL}} //}}AFX_APPEND_ODL}} }; |
1.4.4 VC++6.0与VC++2010的区别
VC++6.0创建出来的COM类与VC++2010创建出来的COM类,其最大区别在于:VC++2010指明了COM类的线程模型。如下表所示:
VC++6.0 |
IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19) |
VC++2010 |
IMPLEMENT_OLECREATE_FLAGS(CStatistic, "comDLLmfc.Statistic", afxRegApartmentThreading, 0x2260a7da, 0xc066, 0x4dd2, 0xaa, 0x61, 0x67, 0xb7, 0xab, 0x7b, 0xca, 0x13) |
使用的宏也不同,一个是IMPLEMENT_OLECREATE,另一个是IMPLEMENT_OLECREATE_FLAGS。
1.5 增加方法
1.5.1 VC++6.0
鼠标右键单击接口IStatistic,弹出菜单中单击【Add Method...】菜单项
图1.14
下图就是增加方法的界面。这里增加了方法void Add(double dVal)。单击"OK"按钮,完成方法的增加。
图1.15
可使用同样的方法,增加方法void Reset()。
1.5.2 VC++2010
鼠标右键单击接口IStatistic,弹出菜单中单击【Add】【Add Method...】菜单项。
图1.16
增加方法的界面如下。与VC++6.0的大致相同。单击"Finish"按钮,完成方法void Add(DOUBLE dVal)的添加。
图1.17
可使用同样的方法,增加方法void Reset()。
1.6 增加属性
1.6.1 VC++6.0
在图1.14中,单击【Add Property...】菜单项。显示界面如下:
这里增加了属性long Count。注意:没有设置"Set function",说明这个属性是只读属性。
单击"OK"按钮,完成属性的增加。
图1.18
同样方法,可以增加属性double Average和double StdDev。
1.6.2 VC++2010
在图1.16中,单击【Add Property...】菜单项。显示界面如下:
这里增加了属性ULONG Count。注意:没有设置"Set function",说明这个属性是只读属性。
单击"Finish"按钮,完成属性的增加。
图1.19
同样方法,可以增加属性double Average和double StdDev。
1.7 删除方法、属性
添加方法、属性时,如果名称或参数输入错误,就需要删除它,然后重新添加。
1.7.1 VC++6.0
按下Ctrl+W,启动类向导界面。显示如下图所示。
进入"Automation"页面,"Class name"下拉列表里选择"CStatistic"(COM类)。"External names"里选择需要删除的属性或方法,单击"Delete"按钮,然后单击"OK"按钮即可删除选中的属性或方法。
"External names"列表里,"M"表示方法,"C"表示自定义属性。
图1.20
1.7.2 VC++2010
VC++2010里删除属性、方法,似乎只能手动进行,相当的麻烦。
1.8 编码
1.8.1 增加成员变量
请给CStatistic增加三个成员变量
private: ULONG m_nCount; double m_dSum; double m_dSum2; |
1.8.2 初始化成员变量
CStatistic::CStatistic() { EnableAutomation(); AfxOleLockApp(); m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; } |
1.8.3 实现Add
void CStatistic::Add(double dVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); ++m_nCount; //样本个数 m_dSum += dVal; //所有样本值的和 m_dSum2 += dVal * dVal; //所有样本值的平方和 } |
1.8.4 实现Reset
void CStatistic::Reset() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); m_nCount = 0; m_dSum = 0.0; m_dSum2 = 0.0; } |
1.8.5 实现GetCount
long CStatistic::GetCount() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); return m_nCount; } |
1.8.6 实现GetAverage
double CStatistic::GetAverage() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); if(m_nCount) { return m_dSum / m_nCount; } return 0.0; } |
1.8.7 实现GetStdDev
double CStatistic::GetStdDev() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); 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; } |
1.9 注册、注销
编译comDLLmfc,即可得到进程内COM组件comDLLmfc.dll。使用它之前,需要注册。
注册组件可使用如下任意一条命令。它们原理相同:都是载入comDLLmfc.dll,然后调用DllRegisterServer函数
regsvr32 comDLLmfc.dll |
Rundll32 comDLLmfc.dll,DllRegisterServer |
注销组件可使用如下任意一条命令。它们原理相同:都是载入comDLLmfc.dll,然后调用DllUnregisterServer函数
regsvr32 /u comDLLmfc.dll |
Rundll32 comDLLmfc.dll,DllUnregisterServer |
注意:VC++2010可以编译生成64位的COM组件。在64位操作系统上,regsvr32.exe和Rundll32.exe将自动识别COM组件是32位的还是64位的。注册信息会写入注册表的如下几个位置。注意这里的<ProgID>其实就是comDLLmfc.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上的,它们互不干涉。
1.10 再论增加COM类
在图1.9和图1.12中,"Automation"有三个选项:None、Automation、Createable by type ID。三者有何区别?
"None"表示创建一个普通的C++类,也就是说选择此项,创建出来的就不是COM类了。
"Createable by type ID"创建出来的是COM类,而且它可以根据输入的ProgID实例化。
"Automation"创建出来的虽然也是COM类,但是它缺少了下面这几条语句:
1、缺少IMPLEMENT_OLECREATE
缺少下面这条语句,意味着COM类CStatistic不能被注册、注销,也不可能被客户端程序实例化。
IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19)
2、因为COM类CStatistic不能被实例化,因此其构造函数里缺少了AfxOleLockApp(),析构函数里缺少了AfxOleUnlockApp()。
AfxOleLockApp()增加COM组件的引用计数,AfxOleUnlockApp()减小COM组件的引用计数。当引用计数为零时,DllCanUnloadNow函数里的AfxDllCanUnloadNow将返回TRUE,此时COM组件才能被FreeLibrary。
第2章创建进程外组件
2.1 创建项目
创建进程外组件的操作很简单:创建MFC EXE项目时,勾中"Automation"选项即可。本文就不进行说明了。
2.2 升级项目
本章的重点在于说明如何将一个MFC EXE项目升级为COM进程外组件。其要点就是用CCmdTarget的派生类做为COM类,供客户端程序调用。
2.2.1 增加接口定义文件
请增加<dspName>.odl至项目,其内容如下:
[ uuid(B4573BE3-F956-4B7D-86AD-7628AE22CD9A), version(1.0) ] library <TypeLibName> { importlib("stdole32.tlb"); importlib("stdole2.tlb"); //{{AFX_APPEND_ODL}} //}}AFX_APPEND_ODL}} }; |
注意:
1、<dspName>是dsp文件名;
2、B4573BE3-F956-4B7D-86AD-7628AE22CD9A是类型库的GUID。为避免重复,请替换成其它值。最简单的办法就是使用VC++6.0生成的头文件里的宏。如:#if !defined(AFX_DLGDLG_H__8715C0CF_88F1_4CD3_B8E5_64FBCF26B052__INCLUDED_)中的8715C0CF_88F1_4CD3_B8E5_64FBCF26B052就是一个随机的GUID,把下划线替换为减号即可使用;
3、version(1.0)是类型库的版本号。1是主版本号,0是次版本号;
4、<TypeLibName>是类型库的名称,请根据实际需要做相应的修改。
2.2.2 修改rc文件
为了把类型库信息嵌入exe,需要修改rc文件。使用记事本打开rc文件,找到3 TEXTINCLUDE,在END之前增加一条语句"1 TYPELIB ""<dspName>.tlb""\r\n"
3 TEXTINCLUDE BEGIN ... ... ... "#endif\r\n" "1 TYPELIB ""<dspName>.tlb""\r\n" "\0" END |
修改rc文件并保存,VC++会把3 TEXTINCLUDE与END之间的语句自动插入到rc文件的尾部。相当于在rc文件中增加了1 TYPELIB "<dspName>.tlb"。还有一种更为简便的方法:直接把1 TYPELIB "<dspName>.tlb"增加到rc2文件里。
注意:请将<dspName>替换为实际的名称。
2.2.3 修改应用程序类的InitInstance函数
修改应用程序类的InitInstance函数
//B4573BE3-F956-4B7D-86AD-7628AE22CD9A const GUID CDECL BASED_CODE _tlid = { 0xB4573BE3,0xF956,0x4B7D ,{0x86,0xAD,0x76,0x28,0xAE,0x22, 0xCD,0x9A}}; const WORD _wVerMajor = 1; const WORD _wVerMinor = 0;
BOOL CXXXApp::InitInstance() { ... ... ... AfxOleInit(); //增加此函数 ... ... ... CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); if(cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated) {//COM客户端启动本程序 COleTemplateServer::RegisterAll(); } else if(cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister) {//从注册表里注销 COleObjectFactory::UpdateRegistryAll(FALSE); AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor); return FALSE; } else { //下面两行代码用于注册本组件及类型库 COleObjectFactory::UpdateRegistryAll(); AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid); if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppRegister) {//注册之后不运行其它代码 return FALSE; } } } |
注意:
1、VC++6.0不支持CCommandLineInfo::AppRegister;
2、上面代码中的_tlid、_wVerMajor、_wVerMinor依次是类型库的GUID、主版本号、次版本号。它应与COM.odl文件的内容保持一致。
2.2.4 实现COM类
MFC实现的COM类必须派生自CCmdTarget。实现COM类有两种思路:一是新建一个类;二是修改已有的类,使其变成COM类。具体而言,如果EXE项目里包含文档类,就可以修改文档类为COM类,否则可以新建一个。
进程外组件项目里增加一个COM类与进程内组件项目里增加一个COM类的操作完全相同。请参考上一章的内容。本节重点讲述如何修改文档类,使之成为COM类。
1、修改文档类的头文件
DECLARE_MESSAGE_MAP()之后,添加如下代码:
// Generated OLE dispatch map functions //{{AFX_DISPATCH(CTestDoc) //}}AFX_DISPATCH DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() |
2、修改文档类的实现文件
构造函数前,增加如下内容
BEGIN_DISPATCH_MAP(CTestDoc, CDocument) //{{AFX_DISPATCH_MAP(CTestDoc) //}}AFX_DISPATCH_MAP END_DISPATCH_MAP()
// {07AD2F15-3DC9-4ED6-85E6-A42005A11978} static const IID IID_ITest = { 0x7AD2F15, 0x3DC9, 0x4ED6 , { 0x85, 0xE6, 0xA4, 0x20, 0x5, 0xA1, 0x19, 0x78 } };
BEGIN_INTERFACE_MAP(CTestDoc, CDocument) INTERFACE_PART(CTestDoc, IID_ITest, Dispatch) END_INTERFACE_MAP() |
构造函数和析构函数,增加如下代码:
CTestDoc::CTestDoc() { EnableAutomation(); AfxOleLockApp(); }
CTestDoc::~CTestDoc() { AfxOleUnlockApp(); } |
3、修改接口定义文件内容
增加COM接口和COM类
// Primary dispatch interface for CStatistic [ uuid(07AD2F15-3DC9-4ED6-85E6-A42005A11978) ] dispinterface IStatistic { properties: // NOTE - ClassWizard will maintain property information here. // Use extreme caution when editing this section. //{{AFX_ODL_PROP(CStatistic) //}}AFX_ODL_PROP methods: // NOTE - ClassWizard will maintain method information here. // Use extreme caution when editing this section. //{{AFX_ODL_METHOD(CStatistic) //}}AFX_ODL_METHOD }; // Class information for CStatistic [ uuid(4FE1F183-045A-4AA8-A35D-3C365C4679B7) ] coclass Statistic { [default] dispinterface IStatistic; }; |
4、应用程序类增加如下成员变量
COleTemplateServer m_server;
5、修改应用程序类的InitInstance函数
在AddDocTemplate(pDocTemplate);之后增加一行代码:
m_server.ConnectTemplate(clsid, pDocTemplate, TRUE);
clsid是COM类Statistic的GUID,请与接口定义文件内容保持一致,如:
// {4FE1F183-045A-4AA8-A35D-3C365C4679B7} static const CLSID clsid = { 0x4FE1F183, 0x45A, 0x4AA8 ,{0xA3,0x5D,0x3C,0x36,0x5C,0x46,0x79,0xB7}}; |
2.3 注册、注销
假定进程外组件的文件名为<ComExeName>,如:C:\App.exe,则:
注册组件可使用下表任意一条命令:
<ComExeName> /Register <ComExeName> /Regserver <ComExeName> /RegisterPerUser <ComExeName> /RegserverPerUser |
注销组件可使用下表任意一条命令:
<ComExeName> /Unregister <ComExeName> /Unregserver <ComExeName> /UnregisterPerUser <ComExeName> /UnregserverPerUser |
注意:
1、VC++6.0不支持命令开关/Register、/Regserver、/RegisterPerUser、/RegserverPerUser,也不支持CCommandLineInfo::AppRegister。直接运行程序即可完成注册;
2、VC++6.0不支持命令开关/RegisterPerUser、/RegserverPerUser、/UnregisterPerUser、/UnregserverPerUser。VC++2010支持,此时InitInstance函数里的cmdInfo.m_bRegisterPerUser 为 TRUE;
3、注册时InitInstance函数里的ParseCommandLine(cmdInfo);将解析命令行开关,发现/Register、/Regserver、/RegisterPerUser、/RegserverPerUser之一时,会设置cmdInfo.m_nShellCommand为CCommandLineInfo::AppRegister。此时,注册组件的代码被执行(其实每次正常运行程序,以下注册组件的代码也会被执行)。
COleObjectFactory::UpdateRegistryAll(); AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid); |
4、注销时InitInstance函数里的ParseCommandLine(cmdInfo);将解析命令行开关,发现/Unregister、/Unregserver、/UnregisterPerUser、/UnregserverPerUser之一时,会设置cmdInfo.m_nShellCommand为CCommandLineInfo::AppUnregister。此时,注销组件的代码被执行。
COleObjectFactory::UpdateRegistryAll(FALSE); AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor); |
5、COM客户端调用时,会启动进程外组件,并增加命令开关/Embedding或/Automation。此时,cmdInfo.m_bRunEmbedded 或 cmdInfo.m_bRunAutomated 为 TRUE。以下代码将被执行
COleTemplateServer::RegisterAll(); |
第3章 VC++使用组件
3.1 #import
如果客户端程序由C++编写而成,可以使用#import。代码如下:
#import "G:\VC\comDLLmfc\Release\comDLLmfc.dll" no_namespace
void Test() { CoInitialize(NULL); ULONG n = 0; double d = 0.0; try { IStatisticPtr s(__uuidof(Statistic)); 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)的标准差 }//运行到此,智能指针s将被析构,s->Relase将被自动调用 catch(_com_error&e) { AfxMessageBox(e.Description() + "\n" + e.ErrorMessage()); } CoUninitialize(); } |
#import根据comDLLmfc.dll里的类型库生成comDLLmfc.tlh和comDLLmfc.tli。查看comDLLmfc.tlh就可以知道COM组件(comDLLmfc.dll)的接口类IStatistic的定义。查看comDLLmfc.tli就能知道IStatistic的方法是如何实现的。
MFC编写的COM组件,均为自动化接口,即COM接口派生自IDispatch。对属性、方法的访问,均是通过IDispatch::Invoke函数实现。
3.2 MFC包装类
MFC包装类就是生成一个派生自COleDispatchDriver的C++类,通过这个类去访问COM组件的属性、方法。它只能包装自动化接口,因此MFC编写的COM组件都可以被MFC类包装起来。其操作如下:
3.2.1 VC++6.0生成包装类
按下Ctrl+W,启动类向导界面。进入Automation页面,单击"Add Class..."按钮。弹出菜单中,单击【From a type library...】菜单项。
图3.1
下图所示的界面里,选择COM组件,然后单击"打开"按钮。
图3.2
VC++6.0显示如下界面。
"Class name"上面的列表里,列出了组件里所有的COM接口;
"Class name"下面的文本框,是将要生成的包装类名称,它的基类注定是COleDispatchDriver。
接着指定包装类的头文件、实现文件。
最后单击"OK"按钮。VC++6.0将创建包装类。
图3.3
3.2.2 VC++2010生成包装类
单击【Project】【Add Class...】菜单项
图3.4
选中"MFC Class From TypeLib",然后单击"Add"按钮。
图3.5
"Available type libraries"下拉列表框里选择类型库。单击">>"按钮,再单击"Finish"按钮,将生成包装类。
图3.6
注意:单击上图的"File"单选框,就可以指定一个文件。通过这个文件生成包装类。
3.2.3 包装类的使用
void Test() { CoInitialize(NULL); ULONG n = 0; double d = 0.0; { IStatistic s; if(s.CreateDispatch(_T("comDLLmfc.Statistic"))) { s.Reset(); s.Add(1.0); s.Add(2.0); s.Add(3.0); s.Add(4.0); n = s.GetCount(); //(1,2,3,4)的个数 d = s.GetAverage(); //(1,2,3,4)的平均值 d = s.GetStdDev(); //(1,2,3,4)的标准差 s.Add(5.0); n = s.GetCount(); //(1,2,3,4,5)的个数 d = s.GetAverage(); //(1,2,3,4,5)的平均值 d = s.GetStdDev(); //(1,2,3,4,5)的标准差 } }//运行到此,s 被析构。IDispatch被自动Release CoUninitialize(); } |
注意:CreateDispatch函数的参数"comDLLmfc.Statistic"就是COM类的ProgID,也就是图1.9和图1.12里的"Createable by type ID"。
与import方法相比,MFC包装类只能用于MFC程序。
3.3 C语言调用
使用C语言也可以访问COM组件,下面是示例代码:
#include <windows.h> #include <MALLOC.H> void Reset(IDispatch*s) { DISPPARAMS dispparams; VARIANT vaResult; UINT nArgErr = (UINT)-1; memset(&dispparams,0,sizeof(dispparams)); VariantInit(&vaResult); s->lpVtbl->Invoke(s,0x5,&IID_NULL,0,DISPATCH_METHOD ,&dispparams,&vaResult, NULL, &nArgErr); VariantClear(&vaResult); } void Add(IDispatch*s,double dVal) { DISPPARAMS dispparams; VARIANT vaResult; UINT nArgErr = (UINT)-1; memset(&dispparams,0,sizeof(dispparams)); dispparams.cArgs = 1; dispparams.rgvarg = (VARIANTARG*)malloc(sizeof(VARIANTARG)); dispparams.rgvarg->vt = VT_R8; dispparams.rgvarg->dblVal = dVal; VariantInit(&vaResult); s->lpVtbl->Invoke(s,0x4,&IID_NULL,0,DISPATCH_METHOD ,&dispparams,&vaResult, NULL, &nArgErr); VariantClear(&vaResult); free(dispparams.rgvarg); } ULONG GetCount(IDispatch*s) { DISPPARAMS dispparams; VARIANT vaResult; UINT nArgErr = (UINT)-1; ULONG nCount = 0; memset(&dispparams,0,sizeof(dispparams)); VariantInit(&vaResult); s->lpVtbl->Invoke(s,1,&IID_NULL,0,DISPATCH_PROPERTYGET ,&dispparams,&vaResult, NULL, &nArgErr); nCount = vaResult.lVal; VariantClear(&vaResult); return nCount; } double GetAverage(IDispatch*s) { DISPPARAMS dispparams; VARIANT vaResult; UINT nArgErr = (UINT)-1; double d = 0.0; memset(&dispparams,0,sizeof(dispparams)); VariantInit(&vaResult); s->lpVtbl->Invoke(s,2,&IID_NULL,0,DISPATCH_PROPERTYGET ,&dispparams,&vaResult, NULL, &nArgErr); d = vaResult.dblVal; VariantClear(&vaResult); return d; } double GetStdDev(IDispatch*s) { DISPPARAMS dispparams; VARIANT vaResult; UINT nArgErr = (UINT)-1; double d = 0.0; memset(&dispparams,0,sizeof(dispparams)); VariantInit(&vaResult); s->lpVtbl->Invoke(s,3,&IID_NULL,0,DISPATCH_PROPERTYGET ,&dispparams,&vaResult, NULL, &nArgErr); d = vaResult.dblVal; VariantClear(&vaResult); return d; } void main() { const IID DIID_IStatistic = {0xD5FC59B4,0x255F,0x415B ,{0x93,0x3F,0x08,0xB9,0x7A,0x23,0xCD,0x58}}; const CLSID CLSID_Statistic = {0xE43D739C,0x1270,0x4B71 ,{0xB9,0xDB,0xD3,0xC7,0x4F,0xED,0xDA,0x19}}; IDispatch* s = NULL; ULONG n = 0; double d = 0.0; CoInitialize(NULL); CoCreateInstance(&CLSID_Statistic,NULL ,CLSCTX_INPROC_SERVER,&DIID_IStatistic,&s); Reset(s); Add(s,1.0); Add(s,2.0); Add(s,3.0); Add(s,4.0); n = GetCount(s); //(1,2,3,4)的个数 d = GetAverage(s); //(1,2,3,4)的平均值 d = GetStdDev(s); //(1,2,3,4)的标准差 Add(s,5.0); n = GetCount(s); //(1,2,3,4,5)的个数 d = GetAverage(s); //(1,2,3,4,5)的平均值 d = GetStdDev(s); //(1,2,3,4,5)的标准差 s->lpVtbl->Release(s); CoUninitialize(); } |
说明:
1、MFC编写的COM组件,其接口均派生自IDispatch,即自动化接口。对属性、方法的访问必须通过IDispatch::Invoke函数;
2、上述代码参考了MFC包装类。
第4章 VB6.0使用组件
4.1 前期绑定
4.1.1 引用类型库
单击【Project】【References...】菜单项
图4.1
列表中勾中COM组件,然后单击"OK"按钮。也可以单击"Browse..."按钮,选择一个文件,然后单击"OK"按钮。
图4.2
4.1.2 查看类型库
单击【View】【Object Browser】菜单项
图4.3
可以看到类型库"comDLLmfc"里的COM接口,及其方法、属性。
图4.4
4.1.3 编码
Dim n As Long Dim d As Double Dim s As New ComDLLmfc.Statistic ‘创建COM对象 s.Reset s.Add 1# s.Add 2# s.Add 3# s.Add 4# n = s.Count ‘(1,2,3,4)的个数 d = s.Average ‘(1,2,3,4)的平均值 d = s.StdDev ‘(1,2,3,4)的标准差 s.Add 5# n = s.Count ‘(1,2,3,4,5)的个数 d = s.Average ‘(1,2,3,4,5)的平均值 d = s.StdDev ‘(1,2,3,4,5)的标准差 Set s = Nothing ‘释放COM对象 |
4.2 后期绑定
后期绑定不用引用类型库,通过CreateObject函数创建COM类。示例代码如下:
Dim n As Long Dim d As Double Dim s As Object Set s = CreateObject("comDLLmfc.Statistic") ‘创建COM对象 s.Reset s.Add 1# s.Add 2# s.Add 3# s.Add 4# n = s.Count ‘(1,2,3,4)的个数 d = s.Average ‘(1,2,3,4)的平均值 d = s.StdDev ‘(1,2,3,4)的标准差 s.Add 5# n = s.Count ‘(1,2,3,4,5)的个数 d = s.Average ‘(1,2,3,4,5)的平均值 d = s.StdDev ‘(1,2,3,4,5)的标准差 Set s = Nothing ‘释放COM对象 |
注意:后期绑定的代码执行效率比前期绑定要低。
COM组件(MFC篇)