首页 > 代码库 > 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.0VC++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.0VC++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组件。在此组件里,将实现COMCStatisticCOM接口IStatistic,用来进行统计计算。IStatistic的详细信息如下:

1、方法

void Reset();                //重新开始统计计算

void Add(double dVal);        //增加一个数据

2、属性

long    Count;                //返回数据个数

double    Average;            //返回平均值

double    StdDev;                //返回标准差

CStatisticVC++代码如下:

#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.0VC++2010的区别

1VC++6.0缺少DllUnregisterServer函数。这意味着VC++6.0编译生成的COM组件不能通过regsvr32 /u comDLLmfc.dll进行注销;

2DllRegisterServer函数里,VC++6.0缺少对类型库的注册,即缺少代码AfxOleRegisterTypeLib(AfxGetInstanceHandle(),_tlid)

3CcomDLLmfcApp::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文件名;

271719C6D-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,把下划线替换为减号即可使用;

3version(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函数并导出:DllCanUnloadNowDllGetClassObjectDllRegisterServerDllUnregisterServer。代码如下:

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、主版本号、次版本号。请与接口定义文件内容保持一致;

2VC++6.0AfxOleUnregisterTypeLib函数只有一个参数,请借鉴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 TEXTINCLUDEEND之间的语句自动插入到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文件内容有误。请关闭项目,删除apsclwncbopt文件。再次打开项目,然后按下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"。

IStatisticCOM接口,客户端程序通过它访问COM组件。

CStatisticCOM类,真正的工作由它来完成。

技术分享

1.13

同时,odl文件也发生了变化,如下表所示。增加了接口IStatisticdispinterface表示这个接口派生自IDispatch)。增加了COMStatistic,这个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.0VC++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 Averagedouble StdDev

1.6.2 VC++2010

在图1.16中,单击【Add Property...】菜单项。显示界面如下:

这里增加了属性ULONG Count。注意:没有设置"Set function",说明这个属性是只读属性。

单击"Finish"按钮,完成属性的增加。

技术分享

1.19

同样方法,可以增加属性double Averagedouble 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.exeRundll32.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位是可以同时注册在64Windows上的,它们互不干涉。

1.10 再论增加COM

在图1.9和图1.12中,"Automation"有三个选项:NoneAutomationCreateable by type ID。三者有何区别?

"None"表示创建一个普通的C++类,也就是说选择此项,创建出来的就不是COM类了。

"Createable by type ID"创建出来的是COM类,而且它可以根据输入的ProgID实例化。

"Automation"创建出来的虽然也是COM类,但是它缺少了下面这几条语句:

1、缺少IMPLEMENT_OLECREATE

缺少下面这条语句,意味着COMCStatistic不能被注册、注销,也不可能被客户端程序实例化。

IMPLEMENT_OLECREATE(CStatistic, "comDLLmfc.Statistic", 0xe43d739c, 0x1270, 0x4b71, 0xb9, 0xdb, 0xd3, 0xc7, 0x4f, 0xed, 0xda, 0x19)

2、因为COMCStatistic不能被实例化,因此其构造函数里缺少了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文件名;

2B4573BE3-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,把下划线替换为减号即可使用;

3version(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 TEXTINCLUDEEND之间的语句自动插入到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;

}

}

}

注意:

1VC++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);

clsidCOMStatisticGUID,请与接口定义文件内容保持一致,如:

// {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

注意:

1VC++6.0不支持命令开关/Register/Regserver/RegisterPerUser/RegserverPerUser,也不支持CCommandLineInfo::AppRegister。直接运行程序即可完成注册;

2VC++6.0不支持命令开关/RegisterPerUser/RegserverPerUser/UnregisterPerUser/UnregserverPerUserVC++2010支持,此时InitInstance函数里的cmdInfo.m_bRegisterPerUser TRUE

3、注册时InitInstance函数里的ParseCommandLine(cmdInfo);将解析命令行开关,发现/Register/Regserver/RegisterPerUser/RegserverPerUser之一时,会设置cmdInfo.m_nShellCommandCCommandLineInfo::AppRegister。此时,注册组件的代码被执行(其实每次正常运行程序,以下注册组件的代码也会被执行)。

COleObjectFactory::UpdateRegistryAll();

AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid);

4、注销时InitInstance函数里的ParseCommandLine(cmdInfo);将解析命令行开关,发现/Unregister/Unregserver/UnregisterPerUser/UnregserverPerUser之一时,会设置cmdInfo.m_nShellCommandCCommandLineInfo::AppUnregister。此时,注销组件的代码被执行。

COleObjectFactory::UpdateRegistryAll(FALSE);

AfxOleUnregisterTypeLib(_tlid, _wVerMajor, _wVerMinor);

5COM客户端调用时,会启动进程外组件,并增加命令开关/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.tlhcomDLLmfc.tli。查看comDLLmfc.tlh就可以知道COM组件(comDLLmfc.dll)的接口类IStatistic的定义。查看comDLLmfc.tli就能知道IStatistic的方法是如何实现的。

MFC编写的COM组件,均为自动化接口,即COM接口派生自IDispatch。对属性、方法的访问,均是通过IDispatch::Invoke函数实现。

3.2 MFC包装类

MFC包装类就是生成一个派生自COleDispatchDriverC++类,通过这个类去访问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();

}

说明:

1MFC编写的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篇)