最近需要用到COM知识,总结一下刚学到的知识。
1. 什么是COM
COM全称是Component Object Model,中文译为组件对象模型。COM组件在物理上是一些DLL或EXE文件;COM组件实现二进制级别的代码重用;COM是与程序设计语言无关,理论上任何语言都可以开发和调用COM组件;COM组件用引用计数实现生命周期的自我管理;COM组件调用者能够查询它所支持的接口;COM组件的位置对调用者是透明的;COM组件依赖于注册表;COM组件都要直接或间接的实现IUnknown接口……
2. IUnknown接口
所有COM组件都直接或间接实现IUnknown接口,IUnknown接口是COM的根接口,其声明为:
标准C++中没有interface关键字,这里的interface是typedef struct interface。
ULONG,VOID等都是windows对C/C++中标准数据类型的宏定义,目的是屏蔽不同平台的差异,并且使编码风格统一。
STDMETHODIMP等价于HRESULT __stdcall
STDMETHODIMP_(DataType)等价于DataType __stdcall
COM规定,调用函数的方式必须为__stdcall,这是pascal语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时自己清空占用的堆栈。与之相对的为__cdecl,这是C/C++语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时,由调用者清空函数占用的堆栈。
QueryInterface函数,客户通过该函数查询COM实现的接口;riid是接口的标识,为GUID形式;返回值标识查询的接口是否实现,如果实现了,则返回S_OK,并且ppv指向接口的实例,否则返回E_NOINTERFACE,ppv指向的内容无效。QueryInterface函数隔离了不同编程语言构造对象实例的差异。
AddRef和Release函数实现了COM对象生命周期的自我管理。实现该接口的类需要有一个ULONG型成员记录其实例的引用计数,如果AddRef一次,引用计数加1,否则引用计数减1,如果引用计数为0时,就释放该实例。这两个函数隔离了不同变成语言释放对象实例的差异。
3. 实现一个最最简单的COM组件
BeginningCOM.h中,先定义一个GUID作为实现接口的class的ID。BeginningCOM.cpp中实现IUnknown接口。
DllGetClassObject是客户调用COM组件的入口,需要导出这个函数:
4. 注册COM组件
在HKEY_CLASSES_ROOT\CLSID键下注册COM组件的class信息。创建一个reg文件,导入注册表即可。
@="BeginningCOM"是class的描述信息,可忽略。
"ThreadingModel"="Both"是COM组件的套件类型,后面介绍。
相应的卸载COM组件的reg文件内容应该为:
5. 调用COM组件
要调用COM组件,必须向客户公布接口ID,class ID等,我们这里把需要公布的信息放到BeginningCOM.h中。
下面的代码用于调用刚创建的COM组件。
在使用COM组件前,需要初始化COM调用环境,CoInitialize初始化单线程套间,只有一个保留的参数,CoInitializeEx除了一个保留的参数,还可以指定初始化的套件类型。
1. 什么是COM
COM全称是Component Object Model,中文译为组件对象模型。COM组件在物理上是一些DLL或EXE文件;COM组件实现二进制级别的代码重用;COM是与程序设计语言无关,理论上任何语言都可以开发和调用COM组件;COM组件用引用计数实现生命周期的自我管理;COM组件调用者能够查询它所支持的接口;COM组件的位置对调用者是透明的;COM组件依赖于注册表;COM组件都要直接或间接的实现IUnknown接口……
2. IUnknown接口
所有COM组件都直接或间接实现IUnknown接口,IUnknown接口是COM的根接口,其声明为:
interface
IUnknown
{
virtual STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv) = 0 ;
virtual STDMETHODIMP_(ULONG) AddRef(VOID) = 0 ;
virtual STDMETHODIMP_(ULONG) = 0 ;
};
{
virtual STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv) = 0 ;
virtual STDMETHODIMP_(ULONG) AddRef(VOID) = 0 ;
virtual STDMETHODIMP_(ULONG) = 0 ;
};
标准C++中没有interface关键字,这里的interface是typedef struct interface。
ULONG,VOID等都是windows对C/C++中标准数据类型的宏定义,目的是屏蔽不同平台的差异,并且使编码风格统一。
STDMETHODIMP等价于HRESULT __stdcall
STDMETHODIMP_(DataType)等价于DataType __stdcall
COM规定,调用函数的方式必须为__stdcall,这是pascal语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时自己清空占用的堆栈。与之相对的为__cdecl,这是C/C++语言缺省的调用函数的方式:函数的参数从右到左依次压栈,函数退出时,由调用者清空函数占用的堆栈。
QueryInterface函数,客户通过该函数查询COM实现的接口;riid是接口的标识,为GUID形式;返回值标识查询的接口是否实现,如果实现了,则返回S_OK,并且ppv指向接口的实例,否则返回E_NOINTERFACE,ppv指向的内容无效。QueryInterface函数隔离了不同编程语言构造对象实例的差异。
AddRef和Release函数实现了COM对象生命周期的自我管理。实现该接口的类需要有一个ULONG型成员记录其实例的引用计数,如果AddRef一次,引用计数加1,否则引用计数减1,如果引用计数为0时,就释放该实例。这两个函数隔离了不同变成语言释放对象实例的差异。
3. 实现一个最最简单的COM组件
//
BeginningCOM.h
#ifndef __BEGINNINGCOM_H__
#define __BEGINNINGCOM_H__
#pragma once
#include < windows.h >
// {7BB69A25-68E4-427a-BE4B-B06ED17531AA}
CLSID CLSID_BeginningCOM =
{ 0x7bb69a25 , 0x68e4 , 0x427a , { 0xbe , 0x4b , 0xb0 , 0x6e , 0xd1 , 0x75 , 0x31 , 0xaa } };
#endif // __BEGINNINGCOM_H__
// BeginningCOM.cpp
#include " BeginningCOM.h "
class BeginningCOM : public IUnknown
{
public :
BeginningCOM(VOID);
STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv);
STDMETHODIMP_(ULONG) AddRef(VOID);
STDMETHODIMP_(ULONG) Release(VOID);
protected :
ULONG m_ulRefCount;
};
BeginningCOM::BeginningCOM(VOID) : m_ulRefCount( 0 )
{
}
STDMETHODIMP BeginningCOM::QueryInterface(REFIID riid, VOID ** ppv)
{
if (riid == IID_IUnknown)
{
* ppv = static_cast < IUnknown *> ( this );
}
else
{
* ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast < IUnknown *> ( * ppv) -> AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) BeginningCOM::AddRef(VOID)
{
return InterlockedIncrement( & m_ulRefCount);
}
STDMETHODIMP_(ULONG) BeginningCOM::Release(VOID)
{
ULONG tmp = InterlockedDecrement( & m_ulRefCount);
if (tmp == 0 )
{
delete this ;
}
return tmp;
}
// DLL entry point.
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid == CLSID_BeginningCOM)
{
BeginningCOM * pbc = new BeginningCOM;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
return pbc -> QueryInterface(riid, ppv);
}
* ppv = 0 ;
return CLASS_E_CLASSNOTAVAILABLE;
}
#ifndef __BEGINNINGCOM_H__
#define __BEGINNINGCOM_H__
#pragma once
#include < windows.h >
// {7BB69A25-68E4-427a-BE4B-B06ED17531AA}
CLSID CLSID_BeginningCOM =
{ 0x7bb69a25 , 0x68e4 , 0x427a , { 0xbe , 0x4b , 0xb0 , 0x6e , 0xd1 , 0x75 , 0x31 , 0xaa } };
#endif // __BEGINNINGCOM_H__
// BeginningCOM.cpp
#include " BeginningCOM.h "
class BeginningCOM : public IUnknown
{
public :
BeginningCOM(VOID);
STDMETHODIMP QueryInterface(REFIID riid, VOID ** ppv);
STDMETHODIMP_(ULONG) AddRef(VOID);
STDMETHODIMP_(ULONG) Release(VOID);
protected :
ULONG m_ulRefCount;
};
BeginningCOM::BeginningCOM(VOID) : m_ulRefCount( 0 )
{
}
STDMETHODIMP BeginningCOM::QueryInterface(REFIID riid, VOID ** ppv)
{
if (riid == IID_IUnknown)
{
* ppv = static_cast < IUnknown *> ( this );
}
else
{
* ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast < IUnknown *> ( * ppv) -> AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) BeginningCOM::AddRef(VOID)
{
return InterlockedIncrement( & m_ulRefCount);
}
STDMETHODIMP_(ULONG) BeginningCOM::Release(VOID)
{
ULONG tmp = InterlockedDecrement( & m_ulRefCount);
if (tmp == 0 )
{
delete this ;
}
return tmp;
}
// DLL entry point.
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break ;
}
return TRUE;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppv)
{
if (rclsid == CLSID_BeginningCOM)
{
BeginningCOM * pbc = new BeginningCOM;
if (pbc == NULL)
{
return E_OUTOFMEMORY;
}
return pbc -> QueryInterface(riid, ppv);
}
* ppv = 0 ;
return CLASS_E_CLASSNOTAVAILABLE;
}
BeginningCOM.h中,先定义一个GUID作为实现接口的class的ID。BeginningCOM.cpp中实现IUnknown接口。
DllGetClassObject是客户调用COM组件的入口,需要导出这个函数:
//
BeginningCOM.def
LIBRARY " BeginningCOM "
EXPORTS
DllGetClassObject private
LIBRARY " BeginningCOM "
EXPORTS
DllGetClassObject private
4. 注册COM组件
在HKEY_CLASSES_ROOT\CLSID键下注册COM组件的class信息。创建一个reg文件,导入注册表即可。
//
regsvr.reg
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}]
@ = " BeginningCOM "
[HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}\InprocServer32]
@ = " E:\\Projects\\BeginningCOM\\Debug\\BeginningCOM.dll "
" ThreadingModel " = " Both "
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}]
@ = " BeginningCOM "
[HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}\InprocServer32]
@ = " E:\\Projects\\BeginningCOM\\Debug\\BeginningCOM.dll "
" ThreadingModel " = " Both "
@="BeginningCOM"是class的描述信息,可忽略。
"ThreadingModel"="Both"是COM组件的套件类型,后面介绍。
相应的卸载COM组件的reg文件内容应该为:
//
unregsvr.reg
Windows Registry Editor Version 5.00
[ - HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}]
@ = " BeginningCOM "
需要注意的是,在Win64中,如果COM组件编译选择的是X86,那么注册表会进行重定向,读取位置变为HKEY_CLASSES_ROOT\Wow6432Node\CLSID,因此,注册时,键值应该写在这个路径下。见《
Win64 注册表重定机制向导致程序运行异常》
Windows Registry Editor Version 5.00
[ - HKEY_CLASSES_ROOT\CLSID\{7BB69A25 - 68E4 - 427a - BE4B - B06ED17531AA}]
@ = " BeginningCOM "
5. 调用COM组件
要调用COM组件,必须向客户公布接口ID,class ID等,我们这里把需要公布的信息放到BeginningCOM.h中。
下面的代码用于调用刚创建的COM组件。
#include
<
Windows.h
>
#include < tchar.h >
#include < iostream >
#include " ../BeginningCOM/BeginningCOM.h "
using namespace std;
int _tmain( int argc, _TCHAR * argv[])
{
CoInitialize(NULL);
HRESULT hr = NULL;
IUnknown * puk;
hr = CoGetClassObject(CLSID_BeginningCOM, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, ( void ** ) & puk);
if (SUCCEEDED(hr))
{
// do nothing
puk -> Release();
}
else
{
cout << " Failed to create object " << endl;
}
CoUninitialize();
return 0 ;
}
#include < tchar.h >
#include < iostream >
#include " ../BeginningCOM/BeginningCOM.h "
using namespace std;
int _tmain( int argc, _TCHAR * argv[])
{
CoInitialize(NULL);
HRESULT hr = NULL;
IUnknown * puk;
hr = CoGetClassObject(CLSID_BeginningCOM, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, ( void ** ) & puk);
if (SUCCEEDED(hr))
{
// do nothing
puk -> Release();
}
else
{
cout << " Failed to create object " << endl;
}
CoUninitialize();
return 0 ;
}
在使用COM组件前,需要初始化COM调用环境,CoInitialize初始化单线程套间,只有一个保留的参数,CoInitializeEx除了一个保留的参数,还可以指定初始化的套件类型。