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的根接口,其声明为:
interface IUnknown 

     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 = 
0x7bb69a250x68e40x427a, {  0xbe0x4b0xb00x6e0xd10x750x310xaa } }; 

#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

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 "

@="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 注册表重定机制向导致程序运行异常

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
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值