COM 入门(1)

最近需要用到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的根接口,其声明为:

interfaceIUnknown

{

virtualSTDMETHODIMP QueryInterface(REFIID riid, VOIDppv)=0;

virtualSTDMETHODIMP_(ULONG) AddRef(VOID)=0;

virtualSTDMETHODIMP_(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

#defineBEGINNINGCOM_H



#pragmaonce

#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"



classBeginningCOM :publicIUnknown

{

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;



returnE_NOINTERFACE;

}



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

returnS_OK;

}



STDMETHODIMP_(ULONG) BeginningCOM::AddRef(VOID)

{

returnInterlockedIncrement(&m_ulRefCount);

}



STDMETHODIMP_(ULONG) BeginningCOM::Release(VOID)

{

ULONG tmp
=InterlockedDecrement(&m_ulRefCount);



if(tmp==0)

{

delete
this;

}



returntmp;

}



//DLL entry point.

BOOL APIENTRY DllMain( HMODULE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

)

{

switch(ul_reason_for_call)

{

caseDLL_PROCESS_ATTACH:

caseDLL_THREAD_ATTACH:

caseDLL_THREAD_DETACH:

caseDLL_PROCESS_DETACH:

break;

}

returnTRUE;

}





STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
void
ppv)

{

if(rclsid==CLSID_BeginningCOM)

{

BeginningCOM
pbc=newBeginningCOM;



if(pbc==NULL)

{

returnE_OUTOFMEMORY;

}



returnpbc->QueryInterface(riid, ppv);

}



ppv=0;



returnCLASS_E_CLASSNOTAVAILABLE;

}


BeginningCOM.h中,先定义一个GUID作为实现接口的class的ID。BeginningCOM.cpp中实现IUnknown接口。

DllGetClassObject是客户调用COM组件的入口,需要导出这个函数:

//BeginningCOM.def

LIBRARY"BeginningCOM"

EXPORTS

DllGetClassObject
private

4. 注册COM组件

在HKEY_CLASSES_ROOT\CLSID键下注册COM组件的class信息。创建一个reg文件,导入注册表即可。

//regsvr.reg

Windows Registry Editor Version5.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 Version5.00



[
-HKEY_CLASSES_ROOT\CLSID{7BB69A25-68E4-427a-BE4B-B06ED17531AA}]

@
="BeginningCOM"需要注意的是,在Win64中,如果COM组件编译选择的是X86,那么注册表会进行重定向,读取位置变为HKEY_CLASSES_ROOT\Wow6432Node\CLSID,因此,注册时,键值应该写在这个路径下。见《Win64 注册表重定机制向导致程序运行异常



5. 调用COM组件

要调用COM组件,必须向客户公布接口ID,class ID等,我们这里把需要公布的信息放到BeginningCOM.h中。

下面的代码用于调用刚创建的COM组件。

#include<Windows.h>

#include
<tchar.h>

#include
<iostream>

#include
"../BeginningCOM/BeginningCOM.h"

usingnamespacestd;



int_tmain(intargc, _TCHARargv[])

{

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();



return0;

}


在使用COM组件前,需要初始化COM调用环境,CoInitialize初始化单线程套间,只有一个保留的参数,CoInitializeEx除了一个保留的参数,还可以指定初始化的套件类型。



Author: Zhenxing Zhou

Blog: http://www.xianfen.net/

原文链接: https://www.cnblogs.com/zxjay/archive/2010/08/28/1811163.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/14429

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 下午2:03
下一篇 2023年2月7日 下午2:04

相关推荐