0


COM - COM编程的简单实现

本文主要记录一下COM编程的简单实现。关于COM的简单介绍可以参考文章 COM的简单介绍。

1、代码的主要结构

代码结构主要分为两部分,其中 ComTest 为组件程序部分,ComCtrl 为客户部分(即组件的使用者),具体如下所示:

2、ComTest

ComTest 为 VS2012所创建的 Win32项目->DLL 类型的工程。代码的组成结构如下所示:

  1. IUnknown 是所有接口的基类;
  2. IComTest 为组件的接口类,在这个接口类中定义能提供的服务(即函数),所以用户只需要知道这个类就可以了,;
  3. CComTest 是对象类,继承自接口IComTest,用于实现服务;
  4. IClassFactory 为类厂类的接口类,不需要开发人员实现;
  5. CComTestFactory 为类厂类,用于生产CComTest对象;
  6. 还有一个自注册模块,用于将组件程序注册进注册表中;

具体实现可以参考以下进行开发。

2.1、接口类的定义

IComTest.h

#pragma once
#include <Unknwn.h>

// 接口 IComTest 的GUID标识,可以通过VS自带的工具自动生成
// {24EC0D3E-2C69-4C39-8AAB-59AFF1111982}
static const GUID IID_IComTest = 
{ 0x24ec0d3e, 0x2c69, 0x4c39, { 0x8a, 0xab, 0x59, 0xaf, 0xf1, 0x11, 0x19, 0x82 } };

class IComTest : public IUnknown{
public:
    virtual BOOL __stdcall SayHello() = 0;    //声明一个服务函数,用于测试
};

在这个文件中,主要是声明了接口类的GUID, 即IID_IComTest;同时声明了组件程序的接口函数。接口类没有实现,所以没有对应的 IComTest.cpp 文件。另外这个 IComTest.h 头文件必须包含在用户程序中。

2.2、对象类的定义

CComTest.h

#pragma once

#include <stdio.h>
#include "icomtest.h"

// CComTest 对象的GUID标识,名称为 CLSID
// {CEF56010-936E-4F71-BC51-32F7ED3B3F73}
static const GUID CLSID_CComTest = 
{ 0xcef56010, 0x936e, 0x4f71, { 0xbc, 0x51, 0x32, 0xf7, 0xed, 0x3b, 0x3f, 0x73 } };

class CComTest : public IComTest
{
public:
    CComTest(void);
    ~CComTest(void);

public:
    //IUnknown member functions
    virtual HRESULT __stdcall QueryInterface(const IID& iid, void **ppv);
    virtual ULONG __stdcall AddRef();
    virtual ULONG __stdcall Release();

    //IComTest member functions
    virtual BOOL __stdcall SayHello();

private:
    ULONG m_Ref;        //引用计数
};

在这个文件中,主要是声明了对象类的GUID(也成为CLSID), 即 CLSID_CComTest;同时声明了对象类的成员函数,这些成员函数是必须要实现的,所以有对应的 CComTest.cpp文件。

CComTest.cpp

#include "stdafx.h"
#include "CComTest.h"

extern ULONG g_ObjNumber;    //在ComTest.cpp中定义

CComTest::CComTest(void){
    this->m_Ref = 0;        //引用计数初始化为0
    g_ObjNumber ++;        //对象计数 +1
}

CComTest::~CComTest(void){ }

//IUnknown member functions
HRESULT CComTest::QueryInterface(const IID& iid, void **ppv){
    if( iid == IID_IUnknown ){
        *ppv = (IUnknown *) this ;
        ((IUnknown *)(*ppv))->AddRef(); //返回的是 IUnknown 接口
    }else if ( iid == IID_IComTest ){ //IID_IComTest 已经在 IComTest.h 中定义
        *ppv = (IComTest *) this ;
        ((IComTest *)(*ppv))->AddRef();
    }
    else{
        *ppv = NULL;
        return E_NOINTERFACE ;
    }
    return S_OK;
}

ULONG CComTest::AddRef(){
    this->m_Ref ++;        //引用计数 +1
    return this->m_Ref;
}

ULONG CComTest::Release(){
    this->m_Ref --;            //引用计数 -1
    if(this->m_Ref == 0){    //当引用计数为0时,则删除自身对象
        g_ObjNumber --;        //同时对象个数 -1
        delete this;
        return 0;
    }
    return this->m_Ref;
}

//IComTest member functions
BOOL CComTest::SayHello(){
    printf("Hello COM!\n");
    return TRUE;
}

2.3、类厂的定义

CComTestFactory.h

#pragma once
#include <Unknwn.h>    //包含头文件

class CComTestFactory : public IClassFactory
{
public:
    CComTestFactory(void);
    ~CComTestFactory(void);

public:
    //IUnknown member functions
    virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
    virtual ULONG __stdcall AddRef();
    virtual ULONG __stdcall Release();

    //IClassFactory member functions
    virtual HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void** ppv);
    virtual HRESULT __stdcall LockServer(BOOL bLock);

private:
    ULONG m_Ref;        //引用计数
};

CComTestFactory.cpp

#include "stdafx.h"

#include "CComTest.h"
#include "CComTestFactory.h"

extern ULONG g_LockNumber;    //类厂对象 的锁计数器,在ComTest.cpp中定义,下同
extern ULONG g_ObjNumber;    //组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载

CComTestFactory::CComTestFactory(void){
    this->m_Ref = 0;        //引用计数初始化为0
}

CComTestFactory::~CComTestFactory(void){}

//IUnknown member functions
HRESULT CComTestFactory::QueryInterface(const IID& iid, void** ppv){
    if( iid == IID_IUnknown ){
        *ppv = (IUnknown *) this ;
        ((IUnknown *)(*ppv))->AddRef();
    }else if ( iid == IID_IClassFactory ){ //IID_IClassFactory 已经在 Unknwn.h 中定义
        *ppv = (IClassFactory *) this ;
        ((IClassFactory *)(*ppv))->AddRef();
    }
    else{
        *ppv = NULL;
        return E_NOINTERFACE ;
    }
    return S_OK;
}

ULONG CComTestFactory::AddRef(){
    this->m_Ref ++;        //引用计数 +1
    return this->m_Ref;
}

ULONG CComTestFactory::Release(){
    this->m_Ref --;        //引用计数 -1
    //当引用计数为0时,则删除自身对象
    if(this->m_Ref == 0){
        delete this;
        return 0;
    }
    return this->m_Ref;
}

//IClassFactory member functions
//创建 CComTest 对象,并返回 接口指针
HRESULT CComTestFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void** ppv){
   if (NULL != pUnknownOuter)
       return CLASS_E_NOAGGREGATION;

   HRESULT hr = E_OUTOFMEMORY;
   //Create the object passing function to notify on destruction.
   CComTest* pObj = new CComTest();
   if (NULL == pObj)
      return hr;   
   
   //Obtain the first interface pointer (which does an AddRef)
   *ppv = NULL;
   hr=pObj->QueryInterface(iid, ppv);
   if (hr != S_OK) {
       //Kill the object if initial creation or FInit failed.
       g_ObjNumber --; // Reference count g_ObjNumber be added in CComTest constructor
       delete pObj;
   }
   return hr; 
}

HRESULT CComTestFactory::LockServer(BOOL bLock){
    if(bLock){
        g_LockNumber++; 
    }else{
        g_LockNumber--;
    }
    return NOERROR;
}

其中最主要的函数是 CreateInstance ,该函数用于创建对应对象类的接口指针。因为每个类厂能创建的对象类是已知的,所以返回的接口指针就是对应对象类的接口指针。CreateInstance函数 是当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用,这个与 《COM的简单介绍》中介绍的不是很一致,请注意区分。

2.4、自注册模块

组件的自注册就是要在组件的引出函数中定义 **DllRegisterServer **和 **DllUnregisterServer **两个函数。其中 DllRegisterServer ** 函数是往注册表写入注册信息,DllUnregisterServer **函数是从注册表中删除注册信息。

**DllRegisterServer **

extern "C" HRESULT __stdcall DllRegisterServer()
{
    char szModule[1024];        //For char
    wchar_t w_szModule[1024];        //For wchar_t
    DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024);
    if (dwResult == 0)
        return SELFREG_E_CLASS;

    Wchar2Char(szModule, w_szModule);    //在这里进行转换,防止对 Registry.cpp进行过多的修改
    return RegisterServer(CLSID_CComTest,      //对象类 CComTest 的GUID
                          szModule,                //组件程序的完整路径
                          "ComTest.Object",        //ProgID
                          "ComTest Component",    //Description
                          NULL);
}

**DllUnregisterServer **

extern "C" HRESULT __stdcall DllUnregisterServer()
{
    return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL);
}

其中,函数 RegisterServer 和 UnregisterServer 全都定义在 Registry.cpp文件中,在该文件中实现了一个比较通用的注册成员。由于该文件中代码比较多,可前往文末最后的下载链接中自行下载。

2.5、ComTest.cpp

在该文件中定义了组件程序必须提供的四个引出函数 DllGetClassObjectDllCanUnloadNow、**DllRegisterServer DllUnregisterServer **,详细代码如下所示:

// ComTest.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
#include "olectl.h"
#include "IComTest.h"
#include "CComTest.h"
#include "CComTestFactory.h"
#include "Registry.h"

ULONG g_LockNumber = 0;    //类厂对象 的锁计数器
ULONG g_ObjNumber = 0;    //组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载
HANDLE g_hModule;        //DLL句柄

extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void **ppv)
{
    if(clsid == CLSID_CComTest ){
        CComTestFactory *pFactory = new CComTestFactory;
        
        if (pFactory == NULL) {
            return E_OUTOFMEMORY ;
        }
        HRESULT result = pFactory->QueryInterface(iid, ppv);
        return result;
    }else{
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

extern "C" HRESULT __stdcall DllCanUnloadNow(void)
{
    //双重检查,只有当对象计数和锁计数都为0时,才可以卸载组件程序
    if ((g_ObjNumber == 0) && (g_LockNumber == 0))
        return S_OK;
    else
        return S_FALSE;
}

// Server registration
extern "C" HRESULT __stdcall DllRegisterServer()
{
    char szModule[1024];        //For char
    wchar_t w_szModule[1024];        //For wchar_t
    DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024);
    if (dwResult == 0)
        return SELFREG_E_CLASS;

    Wchar2Char(szModule, w_szModule);    //在这里进行转换,防止对 Registry.cpp进行过多的修改
    return RegisterServer(CLSID_CComTest,      //对象类 CComTest 的GUID
                          szModule,                //组件程序的完整路径
                          "ComTest.Object",        //ProgID
                          "ComTest Component",    //Description
                          NULL);
}

// Server unregistration
extern "C" HRESULT __stdcall DllUnregisterServer()
{
    return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL);
}

2.6、dllmain.cpp

dllmain.cpp定义了组件程序的入口函数,如下所示:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
extern HANDLE g_hModule;    //DLL句柄,在ComTest.cpp中定义
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    g_hModule = hModule;        //最重要的就是这条语句
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

2.7、ComTest.def

这个.def文件声明了组件需要引出的几个函数,如下所示:

; ComTest.def : Declares the module parameters for the DLL.
LIBRARY        "ComTest"
EXPORTS
    ; Explicit exports can go here
    DllGetClassObject    PRIVATE
    DllRegisterServer    PRIVATE
    DllUnregisterServer    PRIVATE
    DllCanUnloadNow        PRIVATE

3、ComCtrl

ComCtrl 为 VS2012所创建的 Win32控制台应用程序 类型的工程,主要用于模拟对组件的调用。在ComCtrl工程中,一定要包含ComTest工程中的 IComTest.h头文件,因为在该文件中声明了组件程序的接口。需要注意的是,在使用组件对象之前一定要使用 RegSvr32 命令进行组件的注册,当注册成功之后会有以下提示,否则会报错:

此时就会在注册表中看到的注册信息

用户详细代码如下所示:

#include "windows.h"
#include <stdio.h>
#include <comutil.h>

int TestCom(){
    //初始化COM库,使用默认的内存分配器
    if (CoInitialize(NULL) != S_OK) {
        printf("Initialize COM library failed!\n");
        return -1;
    }

    HRESULT hResult;
    GUID comTestCLSID;

    //获取 ProgID为 ComTest.Object 组件的CLISD,其中 ComTest.Object 是在注册注册表时确定的ID
    hResult = ::CLSIDFromProgID(L"ComTest.Object", &comTestCLSID);
    if (hResult != S_OK) {
        printf(">>> Can't find the ComTest CLSID!\n");
        return -2;
    }else{
        LPOLESTR szCLSID;     
        StringFromCLSID(comTestCLSID, &szCLSID);     //将其转化为字符串形式用来输出  
        wprintf(L">>> Find ComTest CLSID: \"%s\"\n",szCLSID);      
        CoTaskMemFree(szCLSID);                        //调用COM库的内存释放  
    }
    
    IUnknown *pUnknown;
    //用此 CLSID 创建一个COM对象,并获取 IUnknown 接口,指向的是类厂对象
    hResult = CoCreateInstance(    comTestCLSID, 
                                NULL, 
                                CLSCTX_INPROC_SERVER, 
                                IID_IUnknown, 
                                (void **)&pUnknown);
    if (hResult != S_OK || NULL == pUnknown){
        printf("Create object failed!\n");
        return -2;
    }
    IComTest* pComTest;

    //通过此 IUnknown 接口查询 IComTest 接口
    hResult = pUnknown->QueryInterface(IID_IComTest, (void **)&pComTest);
    if (hResult != S_OK) {
        pUnknown->Release();
        printf("QueryInterface IComTest failed!\n");
        return -3;
    }

    BOOL re = FALSE;
    printf(">>> 调用组件接口函数:\n");
    re = pComTest->SayHello();    //调用 IComTest 接口中的函数
    if(re){
        printf(">>> 成功调用 COM 组件!\n");
    }else{
        printf(">>> ERROR!\n");
    }

    pComTest->Release();                //释放 IComTest 接口
    if (pUnknown->Release()== 0)        //释放 IUnknown 接口
        printf("The reference count of ComTest object is zero.\n");

    CoUninitialize();                //COM库反初始化
    return 0;
}

int main(int argc, char* argv[])
{
    int re = TestCom();
    system("pause");
    return 0;
}

以上代码中,当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用类厂的CreateInstance函数创建对象,所以不用再进行显示的调用了,这个与 前文《COM的简单介绍》中的最后介绍的协作流程的不是很一致,请注意区分。

4、代码实现

工程采用VS2012编写,全部代码的下载请访问:ComTest.zip下载

标签:

本文转载自: https://blog.csdn.net/bailang_zhizun/article/details/117322271
版权归原作者 bailang_zhizun 所有, 如有侵权,请联系我们删除。

“COM - COM编程的简单实现”的评论:

还没有评论