目录
1.3.2 覆盖应用程序类的InitInstance函数 7
2.2.3 修改应用程序类的InitInstance函数 24
第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 " "1 TYPELIB ""<dspName>.tlb"" " " " 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_MACHINESOFTWAREClassesTypeLib HKEY_LOCAL_MACHINESOFTWAREClassesInterface HKEY_LOCAL_MACHINESOFTWAREClassesCLSID HKEY_LOCAL_MACHINESOFTWAREClasses<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"" "
3 TEXTINCLUDE BEGIN ... ... ... "#endif " "1 TYPELIB ""<dspName>.tlb"" " " " 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:VCcomDLLmfcReleasecomDLLmfc.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() + " " + 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对象 |
注意:后期绑定的代码执行效率比前期绑定要低。