`
huobengluantiao8
  • 浏览: 1029370 次
文章分类
社区版块
存档分类
最新评论

创建一个COM组件

 
阅读更多

1、从建工程到实现注册

在这一过程中我们将完成三个步骤:创建dll的入口函数,定义接口文件,实现注册功能

1.1创建一个类型为win32 dll工程

创建一个名为MathCOM的win32 dll工程。

在向导的第二步选择"A smiple dll project"选项。当然如果你选择一个空的工程,那你自己完成DllMain定义吧。

1.2定义接口文件

生成一个名为MathCOM.idl的接口文件。并将此文件加入到刚才创建的那个工程里。

// MathCOM.idl : IDL source for MathCOM.dll

//

// This file will be processed by the MIDL tool to

// produce the type library (MathCOM.tlb) and marshalling code.

import"oaidl.idl";

import"ocidl.idl";

[

uuid(FAEAE6B7-67BE-42a4-A318-3256781E945A),

helpstring("ISimpleMath Interface"),

object,

pointer_default(unique)

]

interfaceISimpleMath : IUnknown

{

HRESULTAdd([in]intnOp1,[in]intnOp2,[out,retval]int *pret);

HRESULTSubtract([in]intnOp1,[in]intnOp2,[out,retval]int *pret);

HRESULTMultiply([in]intnOp1,[in]intnOp2,[out,retval]int * pret);

HRESULTDivide([in]intnOp1,[in]intnOp2,[out,retval]int *pret);

};

[

uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E),

helpstring("IAdvancedMath Interface"),

object,

pointer_default(unique)

]

interfaceIAdvancedMath : IUnknown

{

HRESULTFactorial([in]intnOp1,[out,retval]int *pret);

HRESULTFabonacci([in]intnOp1,[out,retval]int *pret);

};

[

uuid(CA3B37EA-E44A-49b8-9729-6E9222CAE844),

version(1.0),

helpstring("MATHCOM 1.0 Type Library")

]

libraryMATHCOMLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

[

uuid(3BCFE27E-C88D-453C-8C94-F5F7B97E7841),

helpstring("MATHCOM Class")

]

coclassMATHCOM

{

[default]interface ISimpleMath;

interfaceIAdvancedMath;

};

};

在编译此工程之前请检查Project/Setting/MIDL中的设置。正确设置如下图:

图1.4 midl的正确设置

在正确设置后,如编译无错误,那么将在工程的目录下产生四个 

文件名

作用

MathCOM.h

接口的头文件,如果想声明或定义接口时使用此文件

MathCOM_i.c

定义了接口和类对象以及库,只有在要使用到有关与GUID有关的东西时才引入此文件,此文件在整个工程中只能引入一次,否则会有重复定义的错误

MathCOM_p.c

用于存根与代理

dlldata.c

不明

1.3增加注册功能

作为COM必须要注册与注销的功能。

1.3.1增加一个MathCOM.def文件

DEF文件是模块定义文件(Module Definition File)。它允许引出符号被化名为不同的引入符号。

; MathCOM.def : Declares the module parameters.

LIBRARY "MathCOM.DLL"

EXPORTS

DllCanUnloadNow @1 PRIVATE

DllGetClassObject @2 PRIVATE

DllRegisterServer @3 PRIVATE

DllUnregisterServer @4 PRIVATE

DllUnregisterServer 这是函数名称 @4<――这是函数序号 PRIVATE

接下来大致介绍一下DllRegisterServer()和DllUnregisterServer()。(其他两个函数的作用将在后面介绍)

1.3.2 DllRegisterServer()DllUnregisterServer()

DllRegisterServer() 函数的作用是将COM服务器注册到本机上。

DllUnregisterServer() 函数的作用是将COM服务器从本机注销。

1.4 MathCOM.cpp文件

现在请将 MathCOM.cpp 文件修改成如下:

// MATHCOM.cpp : Defines the entry point for the DLL application.

//

#include"stdafx.h"

#include<objbase.h>

#include<initguid.h>

#include"MathCOM.h"

#include"math.h"

#include"MathCOM_i.c"

//standard self-registration table

constchar * g_RegTable[][3]={

{"CLSID\\{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}",0,"MathCOM"},

{"CLSID\\{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}\\InprocServer32",0,(constchar * )-1 /*表示文件名的值*/},

{"CLSID\\{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}\\ProgID",0,"tulip.MathCOM.1"},

{"tulip.MathCOM.1",0,"MathCOM"},

{"tulip.MathCOM.1\\CLSID",0,"{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}"},

};

HINSTANCEg_hinstDll;

BOOLAPIENTRY DllMain(HANDLE hModule,

DWORDul_reason_for_call,

LPVOIDlpReserved

)

{

g_hinstDll=(HINSTANCE)hModule;

returnTRUE;

}

/*********************************************************************

* Function Declare : DllUnregisterServer

* Explain : self-unregistration routine

* Parameters :

* void --

* Return :

* STDAPI --

* Author : tulip

* Time : 2003-10-29 19:07:42

*********************************************************************/

STDAPIDllUnregisterServer(void)

{

HRESULThr=S_OK;

charszFileName [MAX_PATH];

::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);

intnEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);

for(inti =0;SUCCEEDED(hr)&&i<nEntries;i++)

{

constchar * pszKeyName=g_RegTable[i][0];

longerr=::RegDeleteKey(HKEY_CLASSES_ROOT,pszKeyName);

if(err!=ERROR_SUCCESS)

hr=S_FALSE;

}

returnhr;

}

/*********************************************************************

* Function Declare : DllRegisterServer

* Explain : self Registration routine

* Parameters :

* void --

* Return :

* STDAPI --

* Author : tulip

* Time : 2003-10-29 19:43:51

*********************************************************************/

STDAPIDllRegisterServer(void)

{

HRESULThr=S_OK;

charszFileName [MAX_PATH];

::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);

intnEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);

for(inti =0;SUCCEEDED(hr)&&i<nEntries;i++)

{

constchar * pszKeyName=g_RegTable[i][0];

constchar * pszValueName=g_RegTable[i][1];

constchar * pszValue=g_RegTable[i][2];

if(pszValue==(constchar *)-1)

{

pszValue=szFileName;

}

HKEYhkey;

longerr=::RegCreateKey(HKEY_CLASSES_ROOT,pszKeyName,&hkey);

if(err==ERROR_SUCCESS)

{

err=::RegSetValueEx(hkey,pszValueName,0,REG_SZ,(constBYTE*)pszValue,(strlen(pszValue)+1));

::RegCloseKey(hkey);

}

if(err!=ERROR_SUCCESS)

{

::DllUnregisterServer();

hr=E_FAIL;

}

}

returnhr;

}

/*********************************************************************

* Function Declare : DllGetClassObject

* Explain :

* Parameters :

* REFCLSID rclsid --

* REFIID riid --

* void **ppv --

* Return :

* STDAPI --

* Author : tulip

* Time : 2003-10-29 22:03:53

*********************************************************************/

STDAPIDllGetClassObject(REFCLSIDrclsid ,REFIID riid,void **ppv)

{

staticCMath *pm_math=newCMath;

if(rclsid==CLSID_MATHCOM)

returnpm_math->QueryInterface(riid,ppv);

returnCLASS_E_CLASSNOTAVAILABLE;

}

STDAPIDllCanUnloadNow(void)

{

returnS_OK;

}

我只是在此文件中加几个必要的头文件和几个全局变量。并实现了 DllRegisterServer()和DllUnregisterServer()。而对于其他两引出函数我只返回一个错误值罢了。

1.5小结

现在我们的工程中应该有如下文件: 

文件名

作用

Stdafx.h和stdafx.cpp

预编译文件

MathCOM.cpp

Dll入口函数及其他重要函数定义的地方

MathCOM.def

模块定义文件

MathCOM.idl

接口定义文件(在1.2后如果编译的话应该还有四个文件)

好了到现在,我的所谓COM已经实现注册与注销功能。

如果在命令行或"运行"菜单下项执行如下"regsvr32 绝对路径+MathCOM.dll"就注册此COM组件。在执行完此命令后,请查看注册表项的HKEY_CLASSES_ROOT\CLSID项看看3BCFE27E-C88D-453C-8C94-F5F7B97E7841这一项是否存在(上帝保佑存在)。

如同上方法再执行一下"regsvr32 -u 绝对路径+MathCOM.dll",再看看注册表。

其实刚才生成的dll根本不是COM组件,哈哈!!!因为他没有实现DllGetClassObject()也没有实现ISmipleMath和IAdvancedMath两个接口中任何一个。

让我们继续前行吧!!!

2、实现ISmipleMath,IAdvancedMath接口和DllGetClassObject()

2.1实现ISmipleMathIAdvancedMath接口

让我们将原来的 CMath修改来实现ISmipleMath接口和IAdvancedMath接口。

修改的地方如下:

2.1.1 math.h文件

/*@**#---2003-10-29 21:33:44 (tulip)---#**@

#include "interface.h"*/

#include"MathCOM.h"//新增加的,以替换上面的东东

classCMath : public ISimpleMath,

public IAdvancedMath

{

private:

ULONGm_cRef;

private:

intcalcFactorial(intnOp);

intcalcFabonacci(intnOp);

public:

CMath();

//IUnknown Method

STDMETHOD(QueryInterface)(REFIIDriid, void **ppv);

STDMETHOD_(ULONG,AddRef)();

STDMETHOD_(ULONG,Release)();

//ISimpleMath Method

STDMETHOD (Add)(intnOp1, int nOp2,int * pret);

STDMETHOD (Subtract)(intnOp1, int nOp2,int *pret);

STDMETHOD (Multiply)(intnOp1, int nOp2,int *pret);

STDMETHOD (Divide)(intnOp1, int nOp2,int * pret);

//IAdvancedMath Method

STDMETHOD (Factorial)(intnOp,int *pret);

STDMETHOD (Fabonacci)(intnOp,int *pret);

};

2.1.2 Math.cpp文件

/*@**#---2003-10-29 21:32:35 (tulip)---#**@

#include "interface.h"*/

#include"math.h"

STDMETHODIMPCMath::QueryInterface(REFIIDriid, void **ppv)

{// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与编译器相关。

if(riid ==IID_ISimpleMath)

*ppv =static_cast<ISimpleMath *>(this);

elseif(riid == IID_IAdvancedMath)

*ppv =static_cast<IAdvancedMath *>(this);

elseif(riid == IID_IUnknown)

*ppv =static_cast<ISimpleMath *>(this);

else {

*ppv =0;

returnE_NOINTERFACE;

}

reinterpret_cast<IUnknown *>(*ppv)->AddRef();//这里要这样是因为引用计数是针对组件的

returnS_OK;

}

STDMETHODIMP_(ULONG)CMath::AddRef()

{

return ++m_cRef;

}

STDMETHODIMP_(ULONG)CMath::Release()

{

ULONGres = --m_cRef;// 使用临时变量把修改后的引用计数值缓存起来

if(res ==0) // 因为在对象已经销毁后再引用这个对象的数据将是非法的

deletethis;

returnres;

}

STDMETHODIMPCMath::Add(intnOp1, int nOp2,int * pret)

{

*pret=nOp1+nOp2;

returnS_OK;

}

STDMETHODIMPCMath::Subtract(intnOp1, int nOp2,int * pret)

{

*pret=nOp1 - nOp2;

returnS_OK;

}

STDMETHODIMPCMath::Multiply(intnOp1, int nOp2,int * pret)

{

*pret=nOp1 *nOp2;

returnS_OK;

}

STDMETHODIMPCMath::Divide(intnOp1, int nOp2,int * pret)

{

*pret=nOp1 / nOp2;

returnS_OK;

}

intCMath::calcFactorial(intnOp)

{

if(nOp <=1)

return1;

returnnOp * calcFactorial(nOp -1);

}

STDMETHODIMPCMath::Factorial(intnOp,int * pret)

{

*pret=calcFactorial(nOp);

returnS_OK;

}

intCMath::calcFabonacci(intnOp)

{

if(nOp <=1)

return1;

returncalcFabonacci(nOp -1) + calcFabonacci(nOp -2);

}

STDMETHODIMPCMath::Fabonacci(intnOp,int * pret)

{

*pret=calcFabonacci(nOp);

returnS_OK;

}

CMath::CMath()

{

m_cRef=0;

}

2.2 COM组件调入大致过程

1) COM库初始化使用CoInitialize COM API函数(客户端)使组件加入套间,关于套间的概念我们以后再讲。(客户端)

2)激活COM(客户端)

3) 通过注册表项将对应的DLL调入COM库中(COM运行环境)

4) 调用COM组件内的DllGetClassObject()导出函数(COM组件)

5)通过类厂返回类厂的接口指针(如果客户端是使用CoCreateInstance创建组件时不需要缓存类厂接口指针,则此步在COM运行环境中完成不返回给客户端,同时马上进入第6步。与此相反,客户端会缓存组件类厂接口指针。此方法当客户需要创建多个实例时很有用。)(COM库)

6)通过类厂接口指针调用CreateInstance创建组件实例。创建过程示意图如下:

2.3 DllGetClassObject()实现

在MathCOM.cpp里加入下列语句,

#include "math.h"

#include "MathCOM_i.c"

并将MathCOM.cpp里的DllGetClassObject()修改成如下:

/*********************************************************************

* Function Declare : DllGetClassObject

* Explain :

* Parameters :

* REFCLSID rclsid --

* REFIID riid --

* void **ppv --

* Return :

* STDAPI --

* Author : tulip

* Time : 2003-10-29 22:03:53

*********************************************************************/

STDAPIDllGetClassObject(REFCLSIDrclsid ,REFIID riid,void **ppv)

{

staticCMath *pm_math=newCMath;

if(rclsid==CLSID_MATHCOM)

returnpm_math->QueryInterface(riid,ppv);

returnCLASS_E_CLASSNOTAVAILABLE;

}

2.4客户端

接下来我们写个客户端程序对此COM进行测试。
新建一个空的名为 TestMathCOM 的 win32 Console 工程,将它添加到 MathCOM workspace 中。
在 TestMathCOM 工程里添加一个名为 main.cpp 的文件,此文件的内容如下:

#include<windows.h>

#include"../MathCOM.h"

#include"../MathCOM_i.c"

#include<iostream>

usingnamespace std;

voidmain(void)

{

//初始化COM

HRESULThr=::CoInitialize(0);

ISimpleMath *pSimpleMath=NULL;

IAdvancedMath *pAdvancedMath=NULL;

intnReturnValue=0;

::CoCreateInstance

hr=::CoGetClassObject(CLSID_MATHCOM,CLSCTX_INPROC,

NULL,IID_ISimpleMath,

(void **)&pSimpleMath);

if(SUCCEEDED(hr))

{

hr=pSimpleMath->Add(10,4,&nReturnValue);

if(SUCCEEDED(hr))

cout <<"10 + 4 = " <<nReturnValue<<endl;

nReturnValue=0;

}

hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath); // 查询对象实现的接口IAdvancedMath

if(SUCCEEDED(hr))

{

hr=pAdvancedMath->Fabonacci(10,&nReturnValue);

if(SUCCEEDED(hr))

cout <<"10 Fabonacci is " << nReturnValue << endl;

}

pAdvancedMath->Release();

pSimpleMath->Release();

::CoUninitialize();

::system("pause");

return ;

}

关于如何调试dll请参阅附录A

2.5小结

到现在我们应该有 2 个工程和 8 个文件,具体如下: 

工程

文件

作用

MathCOM

Stdafx.h 和 stdafx.cpp

预编译文件

 

MathCOM.cpp

Dll入口函数及其他重要函数定义的地方

 

MathCOM.def

模块定义文件

 

MathCOM.idl

接口定义文件(在1.2后如果编译的话应该还有四个文件)

 

math.h和math.cpp

ISmipleMath,IadvancedMath接口的实现类

TestMathCOM

Main.cpp

MathCOM的客户端,用于测试MathCOM组件

在此部分中我们已经完成一个可以实用的接近于完整的COM组件。我们完成了此COM组件的客户端。如果你已经创建COM实例的话,你可能会发现在此部分的客户端并不是用CoCreateInstance()来创建COM实例,那是因为我们还没有在此COM组件里实现IClassFactory接口(此接口在下一部分实现)。通过这个例子,我希望大家明白以下几点:

  • 1) DllGetClassObject()的作用,请参看COM组件调入大致过程这一节,同时也请将断点打在DllGetClassObject()函数上,仔细看看他的实现(在没有实现IClassFactory接口的情况下)和他的传入参数。
  • 2) 为什么在这个客户端程序里不使用CoCreateInstance()来创建COM实例而使用CoGetClassObject()来创建COM实例。你可以试着用CoCreateInstance()来创建Cmath,看看DllGetClassObject()的第一参数是什么?
  • 3) 实现IClassFactory接口不是必需的,但应该说是必要的(如何实现请看下一章)
  • 4) 应掌握DllRegisterServer()和DllUnregisterServer()的实现。
  • 5) 客户端在调用COM组件时需要那几个文件(只要由idl文件产生的两个文件)

3、类厂

3.1回顾

在上节里,我们创建组件实例及得到接口指针的过程如下:首先在客户端的用户调用COM API CoCreateInstance,这个函数调用另一个COM API CoGetClassObject获得组件的类厂接口指针,此时COM库会加载组件DLL(EXE的处理方式稍有不同,我们会在以后的章节中讲解)中的导出函数DllGetClassObject(),获得类厂接口指针后马上调用类厂的方法CreateInstance创建对象实例并通过组件对象的QueryInterface()得到用需的接口指针。

此前我们所实现的并不是真正完整的COM组件,现在我们来实现一个真正的COM组件,实现组件创建机制的核心:类厂 - 创建组件对象的对象。COM为通用化以及在管理上统一和方便,COM规范要求所有标准COM组件都应实现IClassFactory接口(有关IClassFactory的功能各个方法的作用请参阅MSND/Welcome to the msnd library/msnd resource/selected online columns/Dr.GUI online/Dr. GUI on Components, COM, and ATL/part 5)

3.2增加IClassFactory的实现

此次我们将修改的文件如下

工程名

文件名

修改属性

MathCOM

MathCOM.cpp

修改

 

MathFactory.h和MathFactory.cpp

新增

TestMathCOM

Main.cpp

修改

3.2.1 MathCOM.cpp

#include"math.h"

#include"MathCOM_i.c"

#include"MathFactory.h"

//////////////////////////////////////////////////////

// 服务器锁, 如果标志为S_OK,就可以卸载当前组件的服务器,

// 关于引用计数及服务器跟COM对象的关系,后绪章节再讲解

//standard self-registration table

constchar * g_RegTable[][3]={………………….

…………………………

STDAPIDllGetClassObject(REFCLSIDrclsid ,REFIID riid,void **ppv)

{

//changed by venture at 2003-11-12 23:15:00

//start

if(rclsid==CLSID_MATHCOM)

{

CMathFactory *pFactory =new CMathFactory;

if(pFactory ==NULL)

returnE_OUTOFMEMORY;

HRESULThr = pFactory->QueryInterface(iid,ppv);

returnhr;

}

//end

returnCLASS_E_CLASSNOTAVAILABLE;

}

STDAPIDllCanUnloadNow(void)

{

return (g_cObjectAndLocks==0)?S_OK:E_FAIL;

}

3.2.2 MathFactory.h

// MathFactory.h: interface for the CMathFactory class.

//

//////////////////////////////////////////////////////////////////////

#ifndefMATHFACTORY_H

#defineMATHFACTORY_H

#include<unknwn.h>

classCMathFactory :publicIClassFactory

{

public:

CMathFactory():m_cRef(0){}

//IUnknow Method

STDMETHODIMPQueryInterface(REFIID,void**);

STDMETHODIMP_(ULONG)AddRef();

STDMETHODIMP_(ULONG)Release();

//IClassFactory Method

STDMETHODIMPCreateInstance(IUnknown * ,REFIID ,void **);

STDMETHODIMPLockServer(BOOLfLock);

protected:

LONGm_cRef;

};

#endif

3.2.3 MathFactory.cpp

// MathFactory.cpp: implementation of the CMathFactory class.

//

//////////////////////////////////////////////////////////////////////

#include"math.h"

#include"MathFactory.h"

externLONG g_cObjectAndLocks;

//////////////////////////////////////////////////////////////////////

// Construction/Destruction

//////////////////////////////////////////////////////////////////////

STDMETHODIMP_(ULONG)CMathFactory::AddRef(void)

{

returnInterlockedIncrement(&m_cRef);

}

STDMETHODIMP_(ULONG)CMathFactory::Release(void)

{

return ::InterlockedDecrement(&m_cRef);

}

STDMETHODIMPCMathFactory::QueryInterface(REFIIDriid,void ** ppv)

{

*ppv=NULL;

if(riid==IID_IUnknown||riid==IID_IClassFactory)

{

*ppv=static_cast<IClassFactory *>(this);

reinterpret_cast<IUnknown*>(*ppv)->AddRef();

returnS_OK;

}

else

return (*ppv=0),E_NOINTERFACE;

}

STDMETHODIMPCMathFactory::CreateInstance(IUnknown *pUnkOuter,REFIIDriid,void ** ppv)

{

*ppv=NULL;

//现在不支持聚合

if(pUnkOuter!=NULL)

returnCLASS_E_NOAGGREGATION;

CMath *pMath=new CMath;

if(pMath==NULL)

returnE_OUTOFMEMORY;

HRESULThr=pMath->QueryInterface(riid,ppv);

if(FAILED(hr))

deletepMath;

returnhr;

}

STDMETHODIMPCMathFactory::LockServer(BOOLfLock)

{

if(fLock)

::InterlockedIncrement(&g_cObjectAndLocks);

else

::InterlockedDecrement(&g_cObjectAndLocks);

returnNOERROR;

}

3.2.4 main.cpp

#include<windows.h>

#include"../MathCOM.h"

#include"../MathCOM_i.c"

#include<iostream>

usingnamespace std;

voidmain(void)

{

//初始化COM

HRESULThr=::CoInitialize(0);

ISimpleMath *pSimpleMath=NULL;

IAdvancedMath *pAdvancedMath=NULL;

intnReturnValue=0;

//如果还想用CoCreateInstance()得到接口指针,请如下使用先传一个IClassFactory接口

/***************这里请注意 *****************/

//方法一

hr = ::CoCreateInstance(CLSID_MATHCOM,

CLSCTX_INPROC,

NULL,

IID_ISimpleMath,

(void*)&pSimpleMath);

///////////////////////////////////////////////////////////////////////

//这个方法的好处是不需要太多的代码, 让COM来处理真正的类厂创建对象的过程.

//同时这个函数的改进版CoCreateInstanceEx在分布式对象应用中可以一次取回多

//个接口指针. 避免过多的在网络上浪费时间关于这个函数的用法我也会在以后的

//章节中讲解. 您也可以参考MSDN或相关书藉

//方法二

IClassFactory *pClassFactory=NULL;// 类厂接口指针

//获取对象的类厂接口指针

hr=::CoGetClassObject(CLSID_MATHCOM,

CLSCTX_INPROC,

NULL,

IID_IClassFactory,

(void**)&pClassFactory);

//真正创建对象

hr =pClassFactory->CreateInstance(NULL,IID_ISimpleMath,(void**)&pSimpleMath);

//此方法的好处在于可以一次创建多个对象, 不需要

//下面测试 IDispatch 接口

if(SUCCEEDED(hr))

{

hr=pSimpleMath->Add(10,4,&nReturnValue);

if(SUCCEEDED(hr))

cout <<"10 + 4 = " <<nReturnValue<<endl;

nReturnValue=0;

}

//查询对象实现的接口IAdvancedMath

hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);

if(SUCCEEDED(hr))

{

hr=pAdvancedMath->Fabonacci(10,&nReturnValue);

if(SUCCEEDED(hr))

cout <<"10 Fabonacci is " << nReturnValue << endl;

}

pAdvancedMath->Release();

pSimpleMath->Release();

::CoUninitialize();

return ;

}

3.3 小结


在此部分我们实现了IClassFactory接口,可以说实现了一个真正的COM对象了,我们可以在C++中使用一般的COM客户端方法创建对象,执行方法等。但是不知道用户有没有发现,COM的目标是语言无关,像VB中不支持指针,脚本是解释执行(但是COM需要在编译期间绑定接口调用)。怎么办呢?有没有办法实现呢?方法当然是有的。接下来的一节我不为大家讲解为不支持指针的语言而提供支持的特殊接口IDispatch - 派发接口(也称调度接口)。为上述语言提供一个效率上稍差但灵活性更高的解决方法。接下来这一章让我们实现IDispatch接口,以便在脚本环境中使用我们的COM组件。

4、实现派发接口(IDispatch)

为了在脚本语言环境中使用COM组件,COM规范规定要在脚本语言环境使用的COM必须实现IDispatch接口,此时的COM接口被称为派发接口。还记得我们在讲IDL的时个描述接口的内容吗?当时我们并没有设置这个关于接口的说明:custom和dual。前者是自定义接口,也就是从IUnknown派生的接口。后者是从IDispatch派生的接口,当然IDispatch也必须从IUnknown派生。只有当此接口从IDispatch派生时,此COM组件可在不支持指针及脚本语言环境下使用。现在让我们的组件也支持双接口。

4.1 IDispatch接口

IDispatch接口共有四个方法,其中只有比较重要(Invoke)。其它方法我们暂时用不到,就没有实现。如果读者有兴趣自己可以参考相关资料来实现一下。并不是很难。像GetIDsOfName等可以交给ITypeInfo等接口处理。讲到这里可能就复杂了。我们以后再为读者讲解。我们的MathCOM组件也只实现了Invoke方法,其他方法都返回E_NOTIMPL表示没有实现。

关于IDispatch接口请参阅MSND文档或<<COM本质论>>p295页

4.2支持派发接口的MathCOM组件

要让我们的MathCOM组件支持双接口,需要修改如下文件

工程名

文件名

MathCOM

MathCOM.idl

 

Math.h和Math.cpp

TestMathCOM

Main.cpp

4.2.1修改MathCOM.idl

MathCOM.idl文件需要修改三个地方。
1) 增加daul属性,以便表明我们的接口是双接口。
2) 将接口的基类由IUnknow接口变成IDispatch接口
3) 在接口的每个方法加上序号属性
修改后的MathCOM.idl如下(请自己找出修改的地方)

// MathCOM.idl : IDL source for MathCOM.dll

//

// This file will be processed by the MIDL tool to

// produce the type library (MathCOM.tlb) and marshalling code.

import "oaidl.idl";

import "ocidl.idl";

[

uuid(FAEAE6B7-67BE-42a4-A318-3256781E945A),

helpstring("ISimpleMath Interface"),

object,

dual, // 这个标识我们前面没遇到过。

pointer_default(unique)

]

interface ISimpleMath : IDispatch

{

[id(1)] HRESULT Add([in]int nOp1,[in]int nOp2,[out,retval]int * pret);

[id(2)] HRESULT Subtract([in]int nOp1,[in]int nOp2,[out,retval]int * pret);

[id(3)] HRESULT Multiply([in]int nOp1,[in]int nOp2,[out,retval] int * pret);

[id(4)] HRESULT Divide([in]int nOp1,[in]int nOp2,[out,retval]int * pret);

};

[

uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E),

helpstring("IAdvancedMath Interface"),

object,

dual,

pointer_default(unique)

]

interface IAdvancedMath : IDispatch

{

[id(1)] HRESULT Factorial([in]int nOp1,[out,retval]int * pret);

[id(2)] HRESULT Fabonacci([in]int nOp1,[out,retval]int * pret);

};

[

uuid(CA3B37EA-E44A-49b8-9729-6E9222CAE844),

version(1.0),

helpstring("MATHCOM 1.0 Type Library")

]

library MATHCOMLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

[

uuid(3BCFE27E-C88D-453C-8C94-F5F7B97E7841),

helpstring("MATHCOM Class")

]

coclass MATHCOM

{

[default] interface ISimpleMath;

interface IAdvancedMath;

};

};

4.2.2修改math.h文件


在math.h增加IDispatch接口的四个方法声明,并对三个进行简单的实现。修改后的math.h见下

//前面相同

CMath();

//IUnknown Method

STDMETHOD(QueryInterface)(REFIID riid, void **ppv);

STDMETHOD_(ULONG, AddRef)();

STDMETHOD_(ULONG, Release)();

//IDispatch Method

STDMETHOD(GetTypeInfoCount)(UINT * pit){ return E_NOTIMPL;}

STDMETHOD(GetTypeInfo)(UINT it,LCID lcid,ITypeInfo **ppti){ return E_NOTIMPL; }

STDMETHOD(GetIDsOfNames)(REFIID riid,

OLECHAR ** pNames,

UINT nNames,

LCID lcid,

DISPID * pdispids){ return E_NOTIMPL; }

STDMETHOD(Invoke)(DISPID id,

REFIID riid,

LCID lcid,

WORD wFlags,

DISPPARAMS *pd,

VARIANT * pVarResult,

EXCEPINFO * pe,

UINT *pu);

// ISimpleMath Method

STDMETHOD (Add)(int nOp1, int nOp2,int * pret);

STDMETHOD (Subtract)(int nOp1, int nOp2,int *pret);

STDMETHOD (Multiply)(int nOp1, int nOp2,int *pret);

STDMETHOD (Divide)(int nOp1, int nOp2,int * pret);

//后面相同

4.2.3修改math.cpp文件


增加IDispatch接口中的Invoke方法的实现。修改后的math.cpp文件见下

//前面相同

CMath::CMath()

{

m_cRef=0;

}

/*********************************************************************

* Function Declare : CMath::Invoke

* Explain : IDispatch接口的Invoke方法

* Parameters :

* DISPID id -- 方法的调度ID(请与idl中序号相比较)

* REFIID riid -- 接口IID

* LCID lcid -- 语言ID

* WORD wFlags --

* DISPPARAMS * pd -- 传入参数结构(具体结构请参阅MSDN或本质论p294)

* VARIANT * pVarResult -- 出参(VARIANT请参阅MSDN文档或<<深入解析ATL>>p56

* EXCEPINFO * pe -- 异常(一般为NULL)

* UINT * pu --

* Return :

* STDMETHODIMP --

* Author : tulip

* Time : 2003-10-30 15:56:37

*********************************************************************/

STDMETHODIMP CMath::Invoke(DISPID id,

REFIID riid,

LCID lcid,WORD wFlags,

DISPPARAMS * pd,

VARIANT * pVarResult,

EXCEPINFO * pe,

UINT * pu)

{

if(riid==IID_ISimpleMath)

{

if(1==id)

return Add(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);

else if (2==id)

return Subtract(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);

else if (3==id)

return Multiply(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);

else if (4==id)

return Divide(pd->rgvarg[0].intVal,pd->rgvarg[1].intVal,&pVarResult->intVal);

else

return E_FAIL;

}

else if (riid==IID_IAdvancedMath)

{

if(1 == id)

return Factorial(pd->rgvarg[0].intVal,&pVarResult->intVal);

else if (2 == id)

return Fabonacci(pd->rgvarg[0].intVal,&pVarResult->intVal);

else

return E_FAIL;

}

else

return E_NOINTERFACE;

}


在修改后上述三个文件,请重新编译,(如果你在上次注册前已经移动过过工程,请重新注册)

4.2.4 修改main.cpp文件


修改main.cpp文件的目的是通过IDispatch接口中的Invoke方法来测试我们的组件。
修改后的main.cpp文件见下

#include <windows.h>

#include "../MathCOM.h"

#include "../MathCOM_i.c"

#include <iostream>

using namespace std;

#include <atlbase.h>

void main(void)

{

/////////////////////////////////////////////////

// 初始化COM库,这里的真正含义是把COM加入适当的套间,

// 关于套间的概念及其它相关概念会在后绪文章中讲解。

HRESULT hr = ::CoInitialize(NULL);

ISimpleMath * pSimpleMath=NULL;

IAdvancedMath * pAdvancedMath=NULL;

IDispatch * pDispatch=NULL; // 派发接口指针

int nReturnValue=0;

hr = ::CoCreateInstance(CLSID_MATHCOM,

CLSCTX_INPROC,

NULL,

IID_ISimpleMath,

(void*)&pSimpleMath);

/////////////////////////

// 下面测试IDispatch接口

//

pSimpleMath->QueryInterface(IID_IDispatch,(void**)&pDispatch);

if(SUCCEEDED(hr))

{

cout<<"下面进行IDispatch接口"<<endl;

CComVariant varResult;

CComVariant varResult2;

CComVariant *pvars=new CComVariant[2];

CComVariant *pvars2=new CComVariant[1];

varResult.Clear();

pvars[1]=4;

pvars[0]=10;

DISPPARAMS disp = { pvars, NULL, 2, 0 };

hr=pDispatch->Invoke(0x1,

IID_ISimpleMath,

LOCALE_USER_DEFAULT,

DISPATCH_METHOD,

&disp,

&varResult,

NULL,

NULL);

if(SUCCEEDED(hr))

cout << "10 + 4 = " <<varResult.intVal<< endl;

varResult2.Clear();

pvars2[0]=10;

DISPPARAMS disp2={pvars2,NULL,1,0};

hr=pDispatch->Invoke(0x2,

IID_IAdvancedMath,

LOCALE_USER_DEFAULT,

DISPATCH_METHOD,

&disp,

&varResult2,

NULL,

NULL);

if(SUCCEEDED(hr))

cout << "10 Fabonacci is " <<varResult2.intVal << endl;

cout<<"IDispatch接口测试完毕"<<endl;

::system("pause");

}

/*

*IDispatch接口测试完毕

*/

if(SUCCEEDED(hr))

{

hr=pSimpleMath->Add(10,4,&nReturnValue);

if(SUCCEEDED(hr))

cout << "10 + 4 = " <<nReturnValue<< endl;

nReturnValue=0;

}

// 查询对象实现的接口IAdvancedMath

hr=pSimpleMath->QueryInterface(IID_IAdvancedMath, (void **)&pAdvancedMath);

if(SUCCEEDED(hr))

{

hr=pAdvancedMath->Fabonacci(10,&nReturnValue);

if(SUCCEEDED(hr))

cout << "10 Fabonacci is " << nReturnValue << endl;

}

pAdvancedMath->Release();

pSimpleMath->Release();

pDispatch->Release();

::CoUninitialize();

return ;

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics