COM技术初探(一)
下载源代码
目录
一. COM是一个更好的 C++
1. COM 是什么
2. 从 C++ 到 DLL 再到 COM
2.1 C++
2.2 DLL
2.3 COM
二. COM基础
1. COM基本知识
1.1 返回值HRESULT
1.2 初识idl
1.3 IUnkown接口
2. 一个比较简单的COM
2.1 interface.h文件
2.2 math.h文件
2.3 math.cpp文件
2.4 simple.cpp文件
2.5 Math组件的二进制结构图
2.6 小结
三. 纯手工创建一个COM组件
1. 从建工程到实现注册
1.1 创建一个类型为win32 dll工程
1.2 定义接口文件
1.3 增加注册功能
1.3.1 增加一个MathCOM.def文件
1.3.2 DllRegisterServer()和DllUnregisterServer()
1.4 MathCOM.cpp文件
1.5 小结
2. 实现ISmipleMath,IAdvancedMath接口和DllGetClassObject()
2.1 实现ISmipleMath和IAdvancedMath接口
2.2 COM组件调入大致过程
2.3 DllGetClassObject()实现
2.4 客户端
2.5 小结
3. 类厂
附录
A 我对dll的一点认识
一. 没有lib的dll
1.1 建一个没有lib的dll
1.2 调试没有lib的dll
二. 带有lib的dll
2.1 创建一个带有lib的dll
2.2 调试带有引用但没有头文件的dll
三. 带有头文件的dll
3.1 创建一个带有引出信息头文件的dll
3.2 调试带有头文件的dll
四. 小结
一、COM是一个更好的C++
1、COM 是什么
Don Box 说"COM IS LOVE"。COM 的全称是 Component Object Model 组件对
象模型。
2、从 C++ 到 DLL 再到 COM
2.1 C++
如某一软件厂商发布一个类库(CMath四则运算),此时类库的可执行代码将成为
客户应用中不可分割的一部分。假设此类库的所产生的机器码在目标可执行文件
中占有4MB的空间。当三个应用程序都使用CMath库时,那么每个可执行文件都
包含4MB的类库代码(见图1.1)。当三个应用程序共同运行时,他们将会占用
12MB的虚拟内存。问题还远不于此。一旦类库厂商发现CMath类库有一个缺陷
后,发布一个新的类库,此时需要要求所有运用此类库的应用程序。此外别无他
法了。
图1.1 CMath 的三个客户
2.2 DLL
解决上面问题的一个技术是将CMath类做成动态链接库(DLL ,Dynamic Link
Library)的形式封装起来 。
在使用这项技术的时候,CMath的所有方法都将被加到 CMath dll 的引出表
(export list)中,而且链接器将会产生一个引入库(import library)。这个库暴露
了CMath的方法成员的符号 。当客户链接引入库时,有一些存根会被引入到可
执行文件中,它在运行时通知装载器动态装载 。
当 CMath 位于dll中时,他的运行模型见图1.2
图1.2 CMath引入库
2.3 COM
"简单地把C++类定义从dll中引出来"这种方案并不能提供合理的二进制组件结
构。因为C++类那既是接口也是实现。这里需要把接口从实现中分离出来才能提
供二进制组件结构。此时需要有二个C++类,一个作为接口类另一个作为实现类。
让我们开始COM之旅吧。
二、COM基础
1、 COM基本知识
1.1 返回值HRESULT
COM要求所有的方法都会返回一个HRESULT类型的错误号。HRESULT 其实就一
个类型定义:
CMath Dll
typedef LONG HRESULT;
有关HRESULT的定义见 winerror.h 文件
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2
1 0
//
+-+----+-------------------------+---------------------------
------+
// |S| Res| Facility | Code
|
//
+-+----+-------------------------+---------------------------
------+
//
// where
//
// S - is the severity code
//
// 0 - Success
// 1 - Error
//
// Res- is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility''s status code
我们一般下面的宏来判断方法是否成功:
#define SUCCEEDED(hr)(long(hr)>=0)
#define FAILED(hr)(long(hr)<0)
1.2 初识 IDL
每个标准的COM组件都需要一个接口定义文件,文件的扩展名为IDL。让我们看
IUnknow接口的定义文件是怎样的。
[
local,
object,
uuid(00000000-0000-0000-C000-000000000046),
pointer_default(unique)
]
interface IUnknown
{
typedef [unique] IUnknown *LPUNKNOWN;
cpp_quote("//
")
cpp_quote("// IID_IUnknown and all other system IIDs are provided
in UUID.LIB")
cpp_quote("// Link that library in with your proxies, clients and
servers")
cpp_quote("//
")
HRESULT QueryInterface(
[in] REFIID riid,
[out, iid_is(riid)] void **ppvObject);
ULONG AddRef();
ULONG Release();
}
[local]属性禁止产生网络代码。
[object]属性是表明定义的是一个COM接口,而不是DEC风格的接口。
[uuid]属性给接口一个GUID。
[unique]属性表明null(空)指针为一个合法的参数值。
[pointer_defaul]属性所有的内嵌指针指定一个默认指针属性
typedef [unique] IUnknown *LPUNKNOWN;这是一个类型定义
cpp_quote这个比较有趣,这是一个在idl文件写注解的方法。这些注
解将保存到***.h和***_i.c文件中
[in]表示这个参数是入参
[out]表示这个参数是出参
[iid_is(riid)]表示这个参数需要前一个的riid 参数。
注意:所有具有out属性的参数都需要是指针类型。
1.3 IUnkown接口
在整个例子除了IUnkown这个东西,其他应该不会感到陌生吧!COM要求(最基
本的要求)所有的接口都需要从IUnknown接口直接或间接继承,所以IUnknown
接口有"万恶之源"之称。
IUnkown接口定义了三个方法。
HRESULT QueryInterface([in] REFIID riid,[out] void **ppv);
ULONG AddRef();
ULONG Release();
其中 AddReft() 和Release()负责对象引用计数用的,而 QueryInterface()方法
是用于查询所实现接口用的。每当COM组件被引用一次就应调用一次AddRef()
方法。而当客户端在释放COM组件的某个接口时就需要调用Release()方法。
这里所讲的请在下面的例子仔细体会。
2、一个比较简单的COM
此例子共有四个文件组成:
文件名
说明
Interface.h
接口类定义文件
Math.h和Math.cpp
实现类文件
Simple.cpp 主函数文件
这里用来当作COM的客户端
2.1 interface.h 文件
#ifndef INTERFACE_H
#define INTERFACE_H
#include <unknwn.h>
//{7C8027EA-A4ED-467c-B17E-1B51CE74AF57}
static const GUID IID_ISimpleMath =
{ 0x7c8027ea, 0xa4ed, 0x467c, { 0xb1, 0x7e, 0x1b, 0x51, 0xce, 0x74,
0xaf, 0x57 } };
//{CA3B37EA-E44A-49b8-9729-6E9222CAE84F}
static const GUID IID_IAdvancedMath =
{ 0xca3b37ea, 0xe44a, 0x49b8, { 0x97, 0x29, 0x6e, 0x92, 0x22, 0xca,
0xe8, 0x4f } };
interface ISimpleMath : public IUnknown
{
public:
virtual int Add(int nOp1, int nOp2) = 0;
virtual int Subtract(int nOp1, int nOp2) = 0;
virtual int Multiply(int nOp1, int nOp2) = 0;
virtual int Divide(int nOp1, int nOp2) = 0;
};
interface IAdvancedMath : public IUnknown
{
public:
virtual int Factorial(int nOp1) = 0;
virtual int Fabonacci(int nOp1) = 0;
};
#endif
此文件首先 #include <unknwn.h> 将 IUnknown 接口定义文件包括进来。
接下来定义了两个接口,GUID(Globally Unique Identifier全局唯一标识符)它
能保证时间及空间上的唯一。
ISmipleMath接口里定义了四个方法,而IAdvancedMath接口里定义了二个方法。
这些方法都是虚函数,而整个 ISmipleMath 与 IAdvancedMath 抽象类就作为
二进制的接口。
2.2 math.h文件
#include "interface.h"
class CMath : public ISimpleMath,
public IAdvancedMath
{
private:
ULONG m_cRef;
private:
int calcFactorial(int nOp);
int calcFabonacci(int nOp);
public:
//IUnknown Method
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// ISimpleMath Method
int Add(int nOp1, int nOp2);
int Subtract(int nOp1, int nOp2);
int Multiply(int nOp1, int nOp2);
int Divide(int nOp1, int nOp2);
// IAdvancedMath Method
int Factorial(int nOp);
int Fabonacci(int nOp);
};
此类为实现类,他实现了ISmipleMath和IAdvancedMath两个接口类(当然也可
以只实现一个接口类)。
请注意:m_cRef 是用来对象计数用的。当 m_cRef 为0组件对象应该自动删
除。
2.3 math.cpp文件
#include "interface.h"
#include "math.h"
STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与
编译器相关。
if(riid == IID_ISimpleMath)
*ppv = static_cast(this);
else if(riid == IID_IAdvancedMath)
*ppv = static_cast(this);
else if(riid == IID_IUnknown)
*ppv = static_cast(this);
else {
*ppv = 0;
return E_NOINTERFACE;
}
reinterpret_cast(*ppv)->AddRef(); //这里要这样是因为引
用计数是针对组件的
return S_OK;
}
STDMETHODIMP_(ULONG) CMath::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CMath::Release()
{
ULONG res = --m_cRef; // 使用临时变量把修改后的引
用计数值缓存起来
if(res == 0) // 因为在对象已经销毁后再引用这个
对象的数据将是非法的
delete this;
return res;
}
int CMath::Add(int nOp1, int nOp2)
{
return nOp1+nOp2;
}
int CMath::Subtract(int nOp1, int nOp2)
{
return nOp1 - nOp2;
}
int CMath::Multiply(int nOp1, int nOp2)
{
return nOp1 * nOp2;
}
int CMath::Divide(int nOp1, int nOp2)
{
return nOp1 / nOp2;
}
int CMath::calcFactorial(int nOp)
{
if(nOp <= 1)
return 1;
return nOp * calcFactorial(nOp - 1);
}
int CMath::Factorial(int nOp)
{
return calcFactorial(nOp);
}
int CMath::calcFabonacci(int nOp)
{
if(nOp <= 1)
return 1;
return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}
int CMath::Fabonacci(int nOp)
{
return calcFabonacci(nOp);
}
CMath::CMath()
{
m_cRef=0;
}
此文件是CMath类定义文件。
2.4 simple.cpp文件
#include "math.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
ISimpleMath *pSimpleMath = NULL;//声明接口指针
IAdvancedMath *pAdvMath = NULL;
//创建对象实例,我们暂时这样创建对象实例,COM有创建对象
实例的机制
CMath *pMath = new CMath;
//查询对象实现的接口ISimpleMath
pMath->QueryInterface(IID_ISimpleMath, (void
**)&pSimpleMath);
if(pSimpleMath)
cout << "10 + 4 = " << pSimpleMath->Add(10, 4) <<
endl;
//查询对象实现的接口IAdvancedMath
pSimpleMath->QueryInterface(IID_IAdvancedMath, (void
**)&pAdvMath);
if(pAdvMath)
cout << "10 Fabonacci is " <<
pAdvMath->Fabonacci(10) << endl;
pAdvMath->Release();
pSimpleMath->Release();
return 0;
}
此文件相当于客户端的代码,首先创建一个CMath对象,再根据此对象去查询所
需要的接口,如果正确得到所需接口指针,再调用接口的方法,最后再将接口的
释放掉。
2.5 Math组件的二进制结构图
图1.3 Math组件二进制结构图
2.6 小结
此例子从严格意义上来并不是真正的COM组件(他不是dll),但他已符合COM
的最小要求(实现IUnknown接口)。接下来我们来做一COM dll(但还不用ATL)。
三、纯手工创建一个COM组件
1、从建工程到实现注册
在这一过程中我们将完成三个步骤:创建dll的入口函数,定义接口文件,实现
注册功能
1.1创建一个类型为win32 dll工程
创建一个名为MathCOM的win32 dll工程。
在向导的第二步选择"A smiple dll project"选项。当然如果你选择一个空的工程,
那你自己完成DllMain定义吧。
1.2 定义接口文件
生成一个名为MathCOM.idl的接口文件。并将此文件加入到刚才创建的那个工程
里。
//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)
]
interface ISimpleMath : IUnknown
{
HRESULT Add([in]int nOp1,[in]int
nOp2,[out,retval]int * pret);
HRESULT Subtract([in]int nOp1,[in]int
nOp2,[out,retval]int * pret);
HRESULT Multiply([in]int nOp1,[in]int
nOp2,[out,retval] int * pret);
HRESULT Divide([in]int nOp1,[in]int
nOp2,[out,retval]int * pret);
};
[
uuid(01147C39-9DA0-4f7f-B525-D129745AAD1E),
helpstring("IAdvancedMath Interface"),
object,
pointer_default(unique)
]
interface IAdvancedMath : IUnknown
{
HRESULT Factorial([in]int nOp1,[out,retval]int *
pret);
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;
};
};
在编译此工程之前请检查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文件
; 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"
//standard self-registration table
const char * g_RegTable[][3]={
{"CLSID//{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}",0,"MathCOM"},
{"CLSID//{3BCFE27E-C88D-453C-8C94-F5F7B97E7841}//InprocServer32",
0,
(const char * )-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}"},
};
HINSTANCE g_hinstDll;
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hinstDll=(HINSTANCE)hModule;
return TRUE;
}
/************************************************************
*********
* Function Declare : DllUnregisterServer
* Explain : self-unregistration routine
* Parameters :
* void --
* Return :
* STDAPI --
* Author : tulip
* Time : 2003-10-29 19:07:42
*************************************************************
********/
STDAPI DllUnregisterServer(void)
{
HRESULT hr=S_OK;
char szFileName [MAX_PATH];
::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);
int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
{
const char * pszKeyName=g_RegTable[i][0];
long
err=::RegDeleteKey(HKEY_CLASSES_ROOT,pszKeyName);
if(err!=ERROR_SUCCESS)
hr=S_FALSE;
}
return hr;
}
/************************************************************
*********
* Function Declare : DllRegisterServer
* Explain : self Registration routine
* Parameters :
* void --
* Return :
* STDAPI --
* Author : tulip
* Time : 2003-10-29 19:43:51
*************************************************************
********/
STDAPI DllRegisterServer(void)
{
HRESULT hr=S_OK;
char szFileName [MAX_PATH];
::GetModuleFileName(g_hinstDll,szFileName,MAX_PATH);
int nEntries=sizeof(g_RegTable)/sizeof(*g_RegTable);
for(int i =0;SUCCEEDED(hr)&&i<nEntries;i++)
{
const char * pszKeyName=g_RegTable[i][0];
const char * pszValueName=g_RegTable[i][1];
const char * pszValue=g_RegTable[i][2];
if(pszValue==(const char *)-1)
{
pszValue=szFileName;
}
HKEY hkey;
long
err=::RegCreateKey(HKEY_CLASSES_ROOT,pszKeyName,&hkey);
if(err==ERROR_SUCCESS)
{
err=::RegSetValueEx( hkey,
pszValueName,
0,
REG_SZ,
( const BYTE*)pszValue,
( strlen(pszValue)+1 ) );
::RegCloseKey(hkey);
}
if(err!=ERROR_SUCCESS)
{
::DllUnregisterServer();
hr=E_FAIL;
}
}
return hr;
}
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void
**ppv)
{
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
return E_FAIL;
}
我只是在此文件中加几个必要的头文件和几个全局变量。并实现了
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 实现ISmipleMath和IAdvancedMath接口
让我们将原来的 CMath 类修改来实现ISmipleMath接口和IAdvancedMath接
口。
修改的地方如下:
1) Math.h文件
/*@**#---2003-10-29 21:33:44 (tulip)---#**@
#include "interface.h"*/
#include "MathCOM.h"//新增加的,以替换上面的东东
class CMath : public ISimpleMath,
public IAdvancedMath
{
private:
ULONG m_cRef;
private:
int calcFactorial(int nOp);
int calcFabonacci(int nOp);
public:
CMath();
//IUnknown Method
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// 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);
// IAdvancedMath Method
STDMETHOD (Factorial)(int nOp,int *pret);
STDMETHOD (Fabonacci)(int nOp,int *pret);
};
2) Math.cpp文件
/*@**#---2003-10-29 21:32:35 (tulip)---#**@
#include "interface.h" */
#include "math.h"
STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与
编译器相关。
if(riid == IID_ISimpleMath)
*ppv = static_cast<ISimpleMath *>(this);
else if(riid == IID_IAdvancedMath)
*ppv = static_cast<IAdvancedMath *>(this);
else if(riid == IID_IUnknown)
*ppv = static_cast<ISimpleMath *>(this);
else {
*ppv = 0;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown *>(*ppv)->AddRef(); //这里
要这样是因为引用计数是针对组件的
return S_OK;
}
STDMETHODIMP_(ULONG) CMath::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CMath::Release()
{
ULONG res = --m_cRef; // 使用临时变量把修改后的引
用计数值缓存起来
if(res == 0) // 因为在对象已经销毁后再引用这个
对象的数据将是非法的
delete this;
return res;
}
STDMETHODIMP CMath::Add(int nOp1, int nOp2,int * pret)
{
*pret=nOp1+nOp2;
return S_OK;
}
STDMETHODIMP CMath::Subtract(int nOp1, int nOp2,int * pret)
{
*pret= nOp1 - nOp2;
return S_OK;
}
STDMETHODIMP CMath::Multiply(int nOp1, int nOp2,int * pret)
{
*pret=nOp1 * nOp2;
return S_OK;
}
STDMETHODIMP CMath::Divide(int nOp1, int nOp2,int * pret)
{
*pret= nOp1 / nOp2;
return S_OK;
}
int CMath::calcFactorial(int nOp)
{
if(nOp <= 1)
return 1;
return nOp * calcFactorial(nOp - 1);
}
STDMETHODIMP CMath::Factorial(int nOp,int * pret)
{
*pret=calcFactorial(nOp);
return S_OK;
}
int CMath::calcFabonacci(int nOp)
{
if(nOp <= 1)
return 1;
return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}
STDMETHODIMP CMath::Fabonacci(int nOp,int * pret)
{
*pret=calcFabonacci(nOp);
return S_OK;
}
CMath::CMath()
{
m_cRef=0;
}
2.2 COM组件调入大致过程
. 1) COM库初始化 使用CoInitialize序列函数(客户端)
. 2)激活COM(客户端)
. 3) 通过注册表项将对应的dll调入COM库中(COM库)
. 4) 调用COM组件内的DllGetClassObject()函数(COM组件)
. 5)通过类厂返回接口指针(COM库)这一步不是必需的
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
*************************************************************
********/
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void
**ppv)
{
static CMath *pm_math=new CMath;
if(rclsid==CLSID_MATHCOM)
return pm_math->QueryInterface(riid,ppv);
return CLASS_E_CLASSNOTAVAILABLE;
}
2.4 客户端
接下来我们写个客户端程序对此COM进行测试。
新建一个空的名为 TestMathCOM 的 win32 Console 工程,将它添加到
MathCOM workspace 中。
在 TestMathCOM 工程里添加一个名为 main.cpp 的文件,此文件的内容如下:
//main.cpp文件
#include <windows.h>
#include "../MathCOM.h"//这里请注意路径
#include "../MathCOM_i.c"//这里请注意路径
#include <iostream>
using namespace std;
void main(void)
{
//初始化COM库
HRESULT hr=::CoInitialize(0);
ISimpleMath * pSimpleMath=NULL;
IAdvancedMath * pAdvancedMath=NULL;
int nReturnValue=0;
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;
}
// 查询对象实现的接口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();
::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、类厂
附录
A 我对 dll 的一点认识
目标:写几个比较简单的dll并了解**.dll与**.lib的关系。
一:没有lib的dll
1.1建一个没有lib的dll
. 1) 新建一个com_1.cpp文件(注意此dll根本没有什么用)
. 2) 在com_1.cpp写下下面的代码
. 3) 按下F5运行,所有的东西都按确定。
. 4) 应该出现如下错误:
. Linking...
. Creating library Debug/COM_1.lib and object
Debug/COM_1.exp
. LIBCD.lib(crt0.obj) : error LNK2001: unresolved external
symbol _main
Debug/COM_1.exe : fatal error LNK1120: 1 unresolved
externals
. 5)进入 project|setting,在 "C/C++" 属性框的 "project Options" 里把
"/D ''_console''" 修改成"/D ''_WINDOWS''"。
. 6)进入project|setting,在 "link" 属性框的 "project Options" 里增加下
面的编译开关 "/dll "
增加的编译开关大致如下:
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib
advapi32.lib shell32.lib
ole32.lib oleaut32.lib uuid.lib odbc32.lib odb**32.lib /nologo
/dll /incremental:yes
/pdb:"Debug/COM_1.pdb" /debug /machine:I386
/out:"Debug/COM_1.dll" /implib:"Debug/COM_1.lib"
/pdbtype:sept
注意:"/dll"应该与后面的开关之间有一个空格
//com_1.cpp
#include <objbase.h>
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void*
lpReserved)
{
HANDLE g_hModule;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
g_hModule=NULL;
break;
}
}
现在可以编译了,这小片段代码将会生成一个dll,但这个dll是没有用的。没有
引出函数和变量。
1.2 调试没有 lib 的 dll
1) 新建一个工程 Client,工程类型为 console,将上面创建的 dll copy 到
client 工程目录下
2) 增加 Client.cpp(代码见下)到工程 Client 中去
3) 选中 Client 工程,并在 project|setting|debug|Category 下拉框,如图:
图1.4 调试
注意这是一种调试 dll 的方法
5) 现在可以在Client和COM_1.dll里打断点调试了。
在这里我们只能调试DllMain()函数,因为那个dll里除了就没别的东西了,下面
我开始 增加一点东西。
二:带有lib的dll
2.1 创建一个带有lib的dll
我们在原来的基础上让上面的代码产生一个lib了。新的代码如下:
#include <objbase.h>
extern "C" __declspec(dllexport) void tulip (void)
{
::MessageBox(NULL,"ok","I''am fine",MB_OK);
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void*
lpReserved)
{
HANDLE g_hModule;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
g_hModule=NULL;
break;
}
return TRUE;
}
在这个dll里,我们引出一个tulip函数。如果此时我们想要在客户调用此函数应
该用什么方法呢?
上面的代码除了生成dll外,他比第一个程序多产生一个lib文件,现在应该知
道dll与lib的关系吧。Lib文件是dll输出符号文件。如果一个dll没有任何东西
输出那么不会有对应的lib文件,但只要一个dll输出一个变量或函数就会相应
的lib文件。总的说来,dll与lib是相互配套的。
当某个dll他有输出函数(或变量)而没有lib文件时,我们应该怎么调用 dll 的
函数呢?请看下面的方法。
2.2 调试带有引用但没有头文件的 dll
注意:本方法根本没有用 COM_1.lib 文件,你可以把 COM_1.lib 文件删除而
不影响。
此时的客户端代码如果下:
#include <windows.h>
int main(void)
{
//定义一个函数指针
typedef void ( * TULIPFUNC )(void);
//定义一个函数指针变量
TULIPFUNC tulipFunc;
//加载我们的dll
HINSTANCE hinst=::LoadLibrary("COM_1.dll");
//找到dll的tulip函数
tulipFunc=(TULIPFUNC)GetProcAddress(hinst,"tulip");
//调用dll里的函数
tulipFunc();
return 0;
}
对于调用系统函数用上面的方法非常方便,因为对于User32.dll,GUI32.dll这
种dll,我没有对应的lib,所以一般用上面的方法。
三:带有头文件的dll
3.1 创建一个带有引出信息头文件的dll
如果用上面的方法调用我们自己创建的dll那太烦了!因为我们的dll可能没有
像window这样标准化的文档。可能过了一段时间后,我们都会忘记dll内部函
数的格式。再如当我们把此dll发布客户时,那个客户肯定会在背后骂你的!
这时我们需要一个能了解dll引出信息途径。我创建一个.h文件。继续我们旅途。
我们的dll代码只需要修改一点点,代码如下:
#include <objbase.h>
#include "header.h"//看到没有,这就是我们增加的头文件
extern "C" __declspec(dllexport) void tulip (void)
{
::MessageBox(NULL,"ok","I''am fine",MB_OK);
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void*
lpReserved)
{
HANDLE g_hModule;
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hModule = (HINSTANCE)hModule;
break;
case DLL_PROCESS_DETACH:
g_hModule=NULL;
break;
}
return TRUE;
}
而 header.h文件只有一行代码:
extern "C" __declspec(dllexport) void tulip (void);
3.2 调试带有头文件的dll
而此时我们的客户程序应该变成如下样子:(比第二要简单多了)
#include <windows.h>
#include "../header.h"//注意路径
//注意路径,加载 COM_1.lib 的另一种方法是 Project | setting |
link 设置里
#pragma comment(lib,"COM_1.lib")
int main(void)
{
tulip();//只要这样我们就可以调用dll里的函数了
return 0;
}
四:小结
今天讲了三种 dll 形式,第一种是没有什么实用价值的,但能讲清楚 dll 与 lib
的关系。我们遇到的情况大多数是第三种,dll 的提供一般会提供 **.lib 和
**.h 文件,而第二种方法适用于系统函数。
希望各位高手指正与交流,
注:今天一时兴起,写了上面的东西,本来我是总结一下有关 COM 的东西,
但写着写着就成这个样子,COM 也是从 dll 起步的。
COM技术初探(三)-- 一个真正的COM
作者: venture
下载源代码
一、实现ISmipleMath,IAdvancedMath接口和DllGetClassObject()
1.1 实现ISmipleMath和IAdvancedMath接口
让我们将原来的CMath 类(CMath其实就是"COM技术初探(二)COM基础知识"
里的那个CMath类)修改来实现ISmipleMath接口和IAdvancedMath接口。
修改的地方如下:
1) Math.h文件
/*@**#---2003-10-29 21:33:44 (tulip)---#**@
#include "interface.h"*/
#include "MathCOM.h"//新增加的,以替换上面的东东
class CMath : public ISimpleMath,
public IAdvancedMath
{
private:
ULONG m_cRef;
private:
int calcFactorial(int nOp);
int calcFabonacci(int nOp);
public:
CMath();
//IUnknown Method
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// 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);
// IAdvancedMath Method
STDMETHOD (Factorial)(int nOp,int *pret);
STDMETHOD (Fabonacci)(int nOp,int *pret);
};
2) Math.cpp文件
/*@**#---2003-10-29 21:32:35 (tulip)---#**@
#include "math.h"
STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{// 这里这是实现dynamic_cast的功能,但由于dynamic_cast与
编译器相关。
if(riid == IID_ISimpleMath)
*ppv = static_cast<ISimpleMath *>(this);
else if(riid == IID_IAdvancedMath)
*ppv = static_cast<IAdvancedMath *>(this);
else if(riid == IID_IUnknown)
*ppv = static_cast<ISimpleMath *>(this);
else {
*ppv = 0;
return E_NOINTERFACE;
}
//这里要这样是因为引用计数是针对组件的
reinterpret_cast<IUnknown *>(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) CMath::AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CMath::Release()
{
// 使用临时变量把修改后的引用计数值缓存起来
ULONG res = --m_cRef;
// 因为在对象已经销毁后再引用这个对象的数据将是非法的
if(res == 0)
delete this;
return res;
}
STDMETHODIMP CMath::Add(int nOp1, int nOp2,int * pret)
{
*pret=nOp1+nOp2;
return S_OK;
}
STDMETHODIMP CMath::Subtract(int nOp1, int nOp2,int * pret)
{
*pret= nOp1 - nOp2;
return S_OK;
}
STDMETHODIMP CMath::Multiply(int nOp1, int nOp2,int * pret)
{
*pret=nOp1 * nOp2;
return S_OK;
}
STDMETHODIMP CMath::Divide(int nOp1, int nOp2,int * pret)
{
*pret= nOp1 / nOp2;
return S_OK;
}
int CMath::calcFactorial(int nOp)
{
if(nOp <= 1)
return 1;
return nOp * calcFactorial(nOp - 1);
}
STDMETHODIMP CMath::Factorial(int nOp,int * pret)
{
*pret=calcFactorial(nOp);
return S_OK;
}
int CMath::calcFabonacci(int nOp)
{
if(nOp <= 1)
return 1;
return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}
STDMETHODIMP CMath::Fabonacci(int nOp,int * pret)
{
*pret=calcFabonacci(nOp);
return S_OK;
}
CMath::CMath()
{
m_cRef=0;
}
1.2 COM组件调入大致过程
创建过程示意图如下:
1) COM库初始化 使用CoInitialize COM API函数(客户端)使组件加入套间,关
于套间的概念我们以后再讲。(客户端)
2)激活COM(客户端)
3) 通过注册表项将对应的DLL调入COM库中(COM运行环境)
4) 调用COM组件内的DllGetClassObject()导出函数(COM组件)
5)通过类厂返回类厂的接口指针(如果客户端是使用CoCreateInstance创建组
件时不需要缓存类厂接口指针,则此步在COM运行环境中完成不返回给客户端,
同时马上进入第6步。与此相反,客户端会缓存组件类厂接口指针。此方法当客
户需要创建多个实例时很有用。)(COM库)
6)通过类厂接口指针调用CreateInstance创建组件实例。
1.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
*************************************************************
********/
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void
**ppv)
{
if(rclsid == CLSID_MathCom)
{
return pm_math->QueryInterface(riid,ppv);
}
return CLASS_E_CLASSNOTAVAILABLE;
}
1.4 客户端
接下来我们写个客户端程序对此COM进行测试。
新建空的名为TestMathCOM一个win32 Console工程加入到MathCOM
workspace中。
在TestMathCOM工程里添加一个名为main.cpp的文件,此文件的内容如下:
//main.cpp文件
#include <windows.h>
#include "../MathCOM.h"//这里请注意路径
#include "../MathCOM_i.c"//这里请注意路径
#include <iostream>
using namespace std;
void main(void)
{
//初始化COM库
HRESULT hr=::CoInitialize(0);
ISimpleMath * pSimpleMath=NULL;
IAdvancedMath * pAdvancedMath=NULL;
int nReturnValue=0;
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;
}
// 查询对象实现的接口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();
::system("pause");
return ;
}
关于如何调试dll请参阅附录A
1.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组件( 没有完整的地
方是还没有实现DllCanUnloadNow() )。我们完成了此COM组件的客户端。
如果你已经创建COM实例的话,你可能会发现在此部分的客户端并不是用
CoCreateInstance()来创建COM实例,那是因为我们还没有在此COM组件里实
现IClassFactory接口(此接口在下一部分实现)。
通过这个例子,我希望大家明白以下几点:
1) DllGetClassObject()的作用(此函数的详细说明请参阅<<COM本质论
>>p86) ,请参看COM组件调入大致过程这一节,同时也请将断点打在
DllGetClassObject()函数上,仔细看看他的实现(在没有实现IClassFactory接口
的情况下)和他的传入参数。
2) 为什么在这个客户端程序里不使用CoCreateInstance()来创建COM实例而使
用CoGetClassObject()来创建COM实例。你可以试着用CoCreateInstance()来
创建Cmath,看看DllGetClassObject()的第一参数是什么?
3) 实现IClassFactory接口不是必需的,但应该说是必要的(如何实现请看下一
章)
4) 应掌握DllRegisterServer()和DllUnregisterServer()的实现。
5) 客户端在调用COM组件时需要那几个文件(只要由idl文件产生的两个文件)
二、类厂
2.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)
2.2 增加IClassFactory的实现
此次我们将修改的文件如下
工程名
文件名
修改属性
MathCOM
MathCOM.cpp
修改
MathFactory.h和
MathFactory.cpp
新增
TestMathCOM
Main.cpp
修改
2.2.1 MathCOM.cpp
#include "math.h"
#include "MathCOM_i.c"
#include "MathFactory.h"
//
// 服务器锁, 如果标志为S_OK,就可以卸载当前组件的服务器,
// 关于引用计数及服务器跟COM对象的关系,后绪章节再讲解
LONG g_cObjectAndLocks=0;
//standard self-registration table
const char * g_RegTable[][3]={………………….
…………………………
STDAPI DllGetClassObject(REFCLSID rclsid ,REFIID riid,void
**ppv)
{
if(rclsid==CLSID_MATHCOM)
{
CMathFactory *pFactory = new CMathFactory;
// 创建类厂对象
if(pFactory == NULL)
return E_OUTOFMEMORY;
///
// 一般情况下COM希望用户只查询IClassFactory,
// IClassFactory2及IUnknow
HRESULT hr = pFactory->QueryInterface(iid, ppv);
return hr;
}
return CLASS_E_CLASSNOTAVAILABLE;
}
STDAPI DllCanUnloadNow(void)
{
return (g_cObjectAndLocks==0)?S_OK:E_FAIL;
}
2.2.2 MathFactory.h
// MathFactory.h: interface for the CMathFactory class.
//
/
/
#ifndef MATHFACTORY_H
#define MATHFACTORY_H
#include <unknwn.h>
class CMathFactory :public IClassFactory
{
public:
CMathFactory():m_cRef(0){}
//IUnknow Method
STDMETHODIMP QueryInterface(REFIID,void**);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
//IClassFactory Method
STDMETHODIMP CreateInstance(IUnknown * ,REFIID ,void **);
STDMETHODIMP LockServer(BOOL fLock);
protected:
LONG m_cRef;
};
#endif
2.2.3 MathFactory.cpp
// MathFactory.cpp: implementation of the CMathFactory class.
//
/
/
#include "math.h"
#include "MathFactory.h"
extern LONG g_cObjectAndLocks;
/
/
// Construction/Destruction
/
/
STDMETHODIMP_(ULONG) CMathFactory::AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CMathFactory::Release(void)
{
return ::InterlockedDecrement(&m_cRef);
}
STDMETHODIMP CMathFactory::QueryInterface(REFIID riid,void **
ppv)
{
*ppv=NULL;
if(riid==IID_IUnknown||riid==IID_IClassFactory)
{
*ppv=static_cast<IClassFactory *>(this);
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
else
return (*ppv=0),E_NOINTERFACE;
}
STDMETHODIMP CMathFactory::CreateInstance(IUnknown *
pUnkOuter,REFIID riid,void ** ppv)
{
*ppv=NULL;
// 现在不支持聚合
if(pUnkOuter!=NULL)
return CLASS_E_NOAGGREGATION;
CMath * pMath=new CMath;
if(pMath==NULL)
return E_OUTOFMEMORY;
HRESULT hr=pMath->QueryInterface(riid,ppv);
if(FAILED(hr))
delete pMath;
return hr;
}
STDMETHODIMP CMathFactory::LockServer(BOOL fLock)
{
if(fLock)
::InterlockedIncrement(&g_cObjectAndLocks);
else
::InterlockedDecrement(&g_cObjectAndLocks);
return NOERROR;
}
2.2.4 main.cpp
#include <windows.h>
#include "../MathCOM.h"
#include "../MathCOM_i.c"
#include <iostream>
using namespace std;
void main(void)
{
//初始化COM库
HRESULT hr=::CoInitialize(0);
ISimpleMath * pSimpleMath=NULL;
IAdvancedMath * pAdvancedMath=NULL;
int nReturnValue=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 ;
}
2.3 小结
在此部分我们实现了IClassFactory接口,可以说实现了一个真正的COM对象了,
我们可以在C++中使用一般的COM客户端方法创建对象,执行方法等。但是
不知道用户有没有发现,COM的目标是语言无关,像VB中不支持指针,脚本是
解释执行(但是COM需要在编译期间绑定接口调用)。怎么办呢?有没有办法
实现呢?方法当然是有的。接下来的一节我不为大家讲解为不支持指针的语言而
提供支持的特殊接口IDispatch - 派发接口(也称调度接口)。为上述语言提
供一个效率上稍差但灵活性更高的解决方法。接下来这一章让我们实现
IDispatch接口,以便在脚本环境中使用我们的COM组件。
三、实现派发接口
为了在脚本语言环境中使用COM组件,COM规范规定要在脚本语言环境使用的
COM必须实现IDispatch接口,此时的COM接口被称为派发接口。还记得我们在
讲IDL的时个描述接口的内容吗?当时我们并没有设置这个关于接口的说明:
custom和dual。前者是自定义接口,也就是从IUnknown派生的接口。后者是
从IDispatch派生的接口,当然IDispatch也必须从IUnknown派生。只有当此
接口从IDispatch派生时,此COM组件可在不支持指针及脚本语言环境下使用。
现在让我们的组件也支持双接口。
3.1 IDispatch接口
IDispatch接口共有四个方法,其中只有比较重要(Invoke)。其它方法我们暂时
用不到,就没有实现。如果读者有兴趣自己可以参考相关资料来实现一下。并不
是很难。像GetIDsOfName等可以交给ITypeInfo等接口处理。讲到这里可能就
复杂了。我们以后再为读者讲解。我们的MathCOM组件也只实现了Invoke方
法,其他方法都返回E_NOTIMPL表示没有实现。
关于IDispatch接口请参阅MSND文档或<<COM本质论>>p295页
3.2 支持派发接口的MathCOM组件
要让我们的MathCOM组件支持双接口,需要修改如下文件
工程名
文件名
MathCOM
MathCOM.idl
Math.h和Math.cpp
TestMathCOM
Main.cpp
3.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;
};
};
3.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);
//后面相同
3.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;
}
在修改后上述三个文件,请重新编译,(如果你在上次注册前已经移动过过工程,
请重新注册)
3.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 ;
}
四 总结
真的好高兴啊,终于要写总结了。
我们再回顾一下,我们到底干了些什么,我们实现了类厂,实现了一个真正的
COM对象,同时为了在不支持指针及脚本环境中使用COM我们又实现了
IDispatch派发接口。至于,为什么要实现IDispach,IDispatch是怎么实现的,
也就是早期捆绑和晚期捆绑的概念。我们将在下一章中讲解。希望大家看到这里
不至于没方向了。不过里面尚有不少你们看到但不知道什么意义,不急。慢慢来,
我会在以后各章详细讲述这些概念。现在我想让你们知道一个COM组件大致的
创建过程,对COM有一定宏观上的认识。毕竟我的水平有限,没有大师级讲解
透彻。我只是将我对COM的一点认识与大家共享一下。希望大家一起进步。
在第一部分,我想大家了解COM的好处。接下来第二部分,我们看到COM核心
结构图(接下来所有的例子都是以图1.3为中心的)。在第三部分,我step by step
讲如何实现一个完整的COM组件。
COM原理比较简单,但在实现时比较灵活,这就造成了大多数人感到难以掌握,
其实只要抓住COM的本质,其他的东西都比较简单。如果你了解了”**”这个东
东,那你懂大部分了。如果你完全搞明白了什么是接口类,实现类,类对象以及
他们之间的关系,那你又前进了一大步。
最后希望大家在学习COM的道路能够感受到快乐。以后有空我们一起交流,哈
哈!!
“COM IS LOVE”。
五 参考资料
COM方面 (在COM方面除潘爱明的作著可以看看外,国内其他作者的东西都是
垃圾。)
<<COM本质论>> Don Box (如果没有看过这本书那你的COM就那个样了^_^)
<<COM技术内幕>> (比较浅,适合入门)
<<COM原理实现与应用>> (潘爱明,本书比较详细讲解了COM的几大核心应
用)
ATL方面
<<深入解析ATL>> (相当于COM界里的<<COM本质论>>)
MSDN
MSJ 杂志的<<House of COM>>由Don Box主持 (现在MSDN杂志已经没有此
专栏,但你还是可以在MSDN许多相当文章)
MSDN Dr.GUI online 具体位置在MSND/Welcome to the msnd library/
msnd resource/selected online columns/Dr.GUI online/
MSDN SDK 文档 具体位置 MSDN/Platform SDK Documentation/Component
Servies
MSDN 的技术文章 位置 MSDN/Technical Articles/Component Object Model
MSDN BackGrounders 有一些相当于白皮书之类的东西。