zoukankan      html  css  js  c++  java
  • C++ COM编程-COM编程入门实践

    1. COM编程基础
    COM是一种规范,而不是实现。

    当使用C++来实现时,COM组件就是一个C++类,而COM接口就是继承至IUnknown的纯虚类,COM组件就是实现相应COM接口的C++类。

    COM规范规定,任何组件或接口都必须从IUnknown接口中继承而来。IUnknown定义了3个重要函数,分别是QueryInterface、AddRef和Release。其中,QueryInterface负责组件对象上的接口查询,AddRef用于增加引用计数,Release用于减少引用计数。引用计数是COM中的一个非常重要的概念,它很好地解决了组件对象地生命周期问题,即COM组件何时被销毁,以及谁来销毁地问题。

    除了IUnknown接口外,还有另外一个重要地接口,即IClassFactory。COM组件实际上是一个C++类,对于组件地外部使用者来说,这个类名一般不可知,那么如何创建这个类地的例?由谁来创建?COM规范规定,每个组件都必须实现一个与之对应的类工厂(Class Factory)。类工厂也是一个COM组件,它实现了IClassFactory接口。在IClassFactory的接口函数CreateInstance中,才能使用new操作生成一个COM组件类对象实例。

    COM组件有3种类型:
    ① 进程内组件(CLSCTX_INPROC_SERVER)
    ② 本地进程组件(CLSCTX_LOCAL_SERVER)
    ③ 远程组件(CLSCTX_REMOTE_SERVER)

    在接口成员函数中,字符串变量必须用Unicode字符指针,这是COM规范的要求。

    2. COM组件开发
    实现一个COM组件,需要完成以下工作:

    COM组件接口
    COM组件实现类
    COM组件创建工厂
    COM组件注册与取消注册
    本文以一个例子作为说明,COM组件提供了一个SayHello的接口函数,将“Hello COM”打印输出。

    2.1 创建COM组件接口
    COM组件接口是一个继承IUnknown的抽象类:

     1 // IComTest.h
     2 #pragma once
     3 #include <Unknwn.h>
     4 // interface id,COM组件接口唯一标识
     5 static const WCHAR* IID_IComTestStr = L"{213D1B15-9BBA-414A-BAB6-CA5B6CEF0006}";
     6 static const GUID IID_IComTest = { 0x213D1B15, 0x9BBA, 0x414A, { 0xBA, 0xB6, 0xCA, 0x5B, 0x6C, 0xEF, 0x00, 0x06 } };
     7 class IComTest :public IUnknown
     8 {
     9 public:
    10     virtual int _stdcall SayHello() = 0;
    11 };

    2.2 创建COM组件实现类

    COM组件类是一个实现了相应COM组件接口的C++类,注意:一个COM组件可以同时实现多个COM接口。

     1 // ComTest.h
     2 #pragma once
     3 #include "IComTest.h"
     4 // class id,COM组件唯一标识
     5 static const WCHAR* CLSID_CComTestStr = L"{4046FA83-57F0-4475-9381-8818BFC50DDF}";
     6 static const GUID CLSID_CComTest = { 0x4046FA83, 0x57F0, 0x4475, { 0x93, 0x81, 0x88, 0x18, 0xBF, 0xC5, 0x0D, 0xDF } };
     7 
     8 class CComTest :public IComTest
     9 {
    10 public:
    11     CComTest();
    12     ~CComTest();
    13 
    14     // 实现IUnknown接口
    15     // 查找接口
    16     // riid : 输入参数,接口id
    17     // ppvObject : 输出参数,返回相应的接口
    18     virtual HRESULT _stdcall QueryInterface(const IID &riid, void ** ppvObject);
    19     // 增加引用计数
    20     virtual ULONG _stdcall AddRef();
    21     // 减少引用计数
    22     virtual ULONG _stdcall Release();
    23     virtual int _stdcall SayHello();
    24 
    25 protected:
    26     //引用计数
    27     ULONG m_RefCount;
    28     //全局创建对象个数
    29     static ULONG g_ObjNum;
    30 };
     1 // ComTest.cpp
     2 #include "ComTest.h"
     3 #include <stdio.h>
     4 
     5 ULONG CComTest::g_ObjNum = 0;
     6 
     7 CComTest::CComTest()
     8 {
     9     m_RefCount = 0;
    10     g_ObjNum++;//对象个数+1
    11 }
    12 
    13 CComTest::~CComTest()
    14 {
    15     g_ObjNum--;//对象个数-1
    16 }
    17 
    18 HRESULT _stdcall CComTest::QueryInterface(const IID &riid, void **ppvObject)
    19 {
    20     // 通过接口id判断返回的接口类型
    21     if (IID_IUnknown == riid){
    22         *ppvObject = this;
    23         ((IUnknown*)(*ppvObject))->AddRef();
    24     }
    25     else if (IID_IComTest == riid){
    26         *ppvObject = (IComTest*)this;
    27         ((IComTest*)(*ppvObject))->AddRef();
    28     }
    29     else{
    30         *ppvObject = NULL;
    31         return E_NOINTERFACE;
    32     }
    33     return S_OK;
    34 }
    35 
    36 ULONG _stdcall CComTest::AddRef()
    37 {
    38     m_RefCount++;
    39     return m_RefCount;
    40 }
    41 
    42 ULONG _stdcall CComTest::Release()
    43 {
    44     m_RefCount--;
    45     if (0 == m_RefCount){
    46         delete this;
    47         return 0;
    48     }
    49     return m_RefCount;
    50 }
    51 
    52 int _stdcall CComTest::SayHello()
    53 {
    54     printf("hello COM
    ");
    55     return 666;
    56 }

    2.3 COM组件创建工厂
    对于组件地外部使用者来说,这个COM组件的类名一般不可知,那么如何创建这个类地实例?由谁来创建?COM规范规定,每个组件都必须实现一个与之对应的类工厂(Class Factory)。类工厂也是一个COM组件,它实现了IClassFactory接口。在IClassFactory的接口函数CreateInstance中,才能使用new操作生成一个COM组件类对象实例。

     1 // ComTestFactory.h
     2 #pragma once
     3 #include <Unknwn.h>
     4 
     5 class CComTestFactory : public IClassFactory
     6 {
     7 public:
     8     CComTestFactory();
     9     ~CComTestFactory();
    10 
    11     // 实现IUnknown接口  
    12     virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);
    13     virtual ULONG _stdcall AddRef();
    14     virtual ULONG _stdcall Release();
    15 
    16     // 实现IClassFactory接口  
    17     virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, const IID& riid, void **ppvObject);
    18     virtual HRESULT _stdcall LockServer(BOOL fLock);
    19 
    20 protected:
    21     ULONG m_RefCount;//引用计数
    22     static ULONG g_ObjNum;//全局创建对象个数
    23 };
     1 // ComTestFactory.cpp
     2 #include "ComTestFactory.h"
     3 #include "ComTest.h"
     4 
     5 ULONG CComTestFactory::g_ObjNum = 0;
     6 
     7 CComTestFactory::CComTestFactory()
     8 {
     9     m_RefCount = 0;
    10     g_ObjNum++;
    11 }
    12 
    13 CComTestFactory::~CComTestFactory()
    14 {
    15     g_ObjNum--;
    16 }
    17 
    18 // 查询指定接口
    19 HRESULT _stdcall CComTestFactory::QueryInterface(const IID &riid, void **ppvObject)
    20 {
    21     if (IID_IUnknown == riid){
    22         *ppvObject = (IUnknown*)this;
    23         ((IUnknown*)(*ppvObject))->AddRef();
    24     }
    25     else if (IID_IClassFactory == riid){
    26         *ppvObject = (IClassFactory*)this;
    27         ((IClassFactory*)(*ppvObject))->AddRef();
    28     }
    29     else{
    30         *ppvObject = NULL;
    31         return E_NOINTERFACE;
    32     }
    33     return S_OK;
    34 }
    35 
    36 ULONG _stdcall CComTestFactory::AddRef()
    37 {
    38     m_RefCount++;
    39     return m_RefCount;
    40 }
    41 
    42 ULONG _stdcall CComTestFactory::Release()
    43 {
    44     m_RefCount--;
    45     if (0 == m_RefCount){
    46         delete this;
    47         return 0;
    48     }
    49     return m_RefCount;
    50 }
    51 
    52 // 创建COM对象,并返回指定接口
    53 HRESULT _stdcall CComTestFactory::CreateInstance(IUnknown *pUnkOuter, const IID &riid, void **ppvObject)
    54 {
    55     if (NULL != pUnkOuter){
    56         return CLASS_E_NOAGGREGATION;
    57     }
    58     HRESULT hr = E_OUTOFMEMORY;
    59     //ComClass::Init();
    60     CComTest* pObj = new CComTest();
    61     if (NULL == pObj){
    62         return hr;
    63     }
    64     hr = pObj->QueryInterface(riid, ppvObject);
    65     if (S_OK != hr){
    66         delete pObj;
    67     }
    68     return hr;
    69 }
    70 
    71 HRESULT _stdcall CComTestFactory::LockServer(BOOL fLock)
    72 {
    73     return NOERROR;
    74 }

    2.4 COM组件的注册
    COM组件需要使用regsvr32工具注册到系统才能被调用,然而COM组件是如何被regsvr32注册的?一个典型的自注册COM组件需要提供4个必需的导出函数:

    DllGetClassObject:用于获得类工厂指针
    DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载COM组件
    DllRegisterServer:将COM组件注册到注册表中
    DllUnregisterServer:删除注册表中的COM组件的注册信息
    DLL还有一个可选的入口函数DllMain,可用于初始化和释放全局变量

    DllMain:DLL的入口函数,在LoadLibrary和FreeLibrary时都会调用

    1 // ComTestExport.h
    2 #include <windows.h> 
    3 
    4 extern "C" HRESULT _stdcall DllRegisterServer();
    5 extern "C" HRESULT _stdcall DllUnregisterServer();
    6 extern "C" HRESULT _stdcall DllCanUnloadNow();
    7 extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv);
      1 // ComTestExport.cpp
      2 #include "ComTestExport.h"
      3 #include "ComTestFactory.h"
      4 #include "ComTest.h"
      5 
      6 #include <iostream>
      7 
      8 HMODULE g_hModule;  //dll进程实例句柄  
      9 ULONG g_num;        //组件中ComTest对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载  
     10 
     11 int myReg(LPCWSTR lpPath)   //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID  
     12 {
     13     HKEY thk, tclsidk;
     14 
     15     //打开键HKEY_CLASSES_ROOTCLSID,创建新键为ComTest的CLSID,  
     16     //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值  
     17     if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
     18 
     19         printf("RegOpenKey ok
    ");
     20 
     21         if (ERROR_SUCCESS == RegCreateKey(thk, CLSID_CComTestStr, &tclsidk)){
     22 
     23             wprintf(L"RegCreateKey %s ok
    ", CLSID_CComTestStr);
     24 
     25             HKEY tinps32k, tprogidk;
     26             if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){
     27 
     28                 printf("RegCreateKey InprocServer32 ok
    ");
     29 
     30                 if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){
     31                 }
     32                 RegCloseKey(tinps32k);
     33             }
     34             RegCloseKey(tclsidk);
     35         }
     36         RegCloseKey(thk);
     37     }
     38     //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CComTest,  
     39     //在该键下创建子键,并将CCompTest的CLSID写为该键的默认值  
     40     if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest", &thk)){
     41         if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){
     42             if (ERROR_SUCCESS == RegSetValue(tclsidk,
     43                 NULL,
     44                 REG_SZ,
     45                 CLSID_CComTestStr,
     46                 wcslen(CLSID_CComTestStr) * 2)){
     47             }
     48         }
     49     }
     50     //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CComTest为参数调用CLSIDFromProgID函数  
     51     //来获取CCompTest的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象  
     52     return 0;
     53 }
     54 
     55 extern "C" HRESULT _stdcall DllRegisterServer()
     56 {
     57     WCHAR szModule[1024];
     58     //获取本组件(dll)所在路径  
     59     DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); 
     60     if (0 == dwResult){
     61         return -1;
     62     }
     63     MessageBox(NULL, szModule, L"", MB_OK);
     64     //将路径等信息写入注册表  
     65     myReg(szModule);
     66     return 0;
     67 }
     68 
     69 int myDelKey(HKEY hk, LPCWSTR lp)
     70 {
     71     if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){
     72     }
     73     return 0;
     74 }
     75 
     76 //删除注册时写入注册表的信息  
     77 int myDel() 
     78 {
     79     HKEY thk;
     80     if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){
     81         myDelKey(thk, L"{4046FA83-57F0-4475-9381-8818BFC50DDF}\InprocServer32");
     82         myDelKey(thk, CLSID_CComTestStr);
     83 
     84         RegCloseKey(thk);
     85     }
     86     if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest", &thk)){
     87         myDelKey(thk, L"CLSID");
     88     }
     89     myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest");
     90     return 0;
     91 }
     92 
     93 extern "C" HRESULT _stdcall DllUnregisterServer()
     94 {
     95     //删除注册时写入注册表的信息  
     96     myDel();
     97     return 0;
     98 }
     99 
    100 // 用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用  
    101 extern "C" HRESULT _stdcall DllCanUnloadNow()
    102 {
    103     //如果对象个数为0,则可以卸载  
    104     if (0 == g_num){
    105         return S_OK;
    106     }
    107     else{
    108         return S_FALSE;
    109     }
    110 }
    111 
    112 //用于创建类厂并返回所需接口,由CoGetClassObject函数调用  
    113 extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)
    114 {
    115     LPOLESTR szCLSID;
    116     StringFromCLSID(rclsid, &szCLSID);     //将其转化为字符串形式用来输出  
    117     wprintf(L"rclsid CLSID "%s"
    ", szCLSID);
    118 
    119     szCLSID;
    120     StringFromCLSID(riid, &szCLSID);     //将其转化为字符串形式用来输出  
    121     wprintf(L"riid CLSID "%s"
    ", szCLSID);
    122 
    123     if (CLSID_CComTest == rclsid){
    124         CComTestFactory* pFactory = new CComTestFactory();//创建类厂对象  
    125         if (NULL == pFactory){
    126             return E_OUTOFMEMORY;
    127         }
    128         HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口  
    129         return result;
    130     }
    131     else{
    132         return CLASS_E_CLASSNOTAVAILABLE;
    133     }
    134 }
    135 
    136 // 提供DLL入口;对于动态链接库,DllMain是一个可选的入口函数,在COM组件中是必须有的
    137 BOOL APIENTRY DllMain(HMODULE hModule,
    138     DWORD  ul_reason_for_call,
    139     LPVOID lpReserved
    140     )
    141 {
    142     //获取进程实例句柄,用于获取本组件(dll)路径  
    143     g_hModule = hModule;
    144     switch (ul_reason_for_call)
    145     {
    146     case DLL_PROCESS_ATTACH:
    147     case DLL_THREAD_ATTACH:
    148     case DLL_THREAD_DETACH:
    149     case DLL_PROCESS_DETACH:
    150         break;
    151     }
    152     return TRUE;
    153 }

    导出文件(Source.def):

    1 LIBRARY "ComTest_Server"
    2 EXPORTS
    3 DllCanUnloadNow
    4 DllGetClassObject
    5 DllUnregisterServer
    6 DllRegisterServer

    生成完后,使用regsvr32注册到系统中:

    > regsvr32 ComTest_Server.dll

    3. COM组件使用

    COM组件的使用包括:

    1. 如何创建COM组件
    2. 如何得到组件对象上的接口以及如何调用接口方法
    3. 如何管理组件对象(需熟悉COM的引用计数机制)

    下面的代码是最一般的步骤:

     1 CoInitialize(NULL);    // COM库初始化
     2 // ...
     3 IUnknow *pUnk = NULL;
     4 IObject *pObj = NULL;
     5 // 创建组件对象,CLSID_XXX为COM组件类的GUID(class id),返回默认IID_IUnknown接口
     6 HRESULT hr = CoCreateInstance(CLSID_XXX,NULL,CLSCTX_INPROC_SERVER,NULL,IID_IUnknown,(void **)&pUnk);
     7 if(S_OK == hr)
     8 {
     9     // 获取接口,IID_XXX为组件接口的GUID(interface id)
    10     hr = pUnk->QueryInterface(IID_XXX,(void **)&pObj);
    11     if(S_OK == hr)
    12     {
    13         // 调用接口方法
    14         pObj->DoXXX();
    15     }
    16     // 释放组件对象
    17     pUnk->Release();
    18 }
    19 //...
    20 // 释放COM库
    21 CoUninitialize();

    下面我们编写一个客户端,调用之前写的COM组件服务:

     1 #include "IComTest.h"
     2 #include "ComTest.h"
     3 #include <stdio.h>
     4 
     5 int main()
     6 {
     7     // 初始化COM库
     8     CoInitialize(NULL);
     9 
    10     IComTest *pComTest = NULL;
    11     HRESULT hResult;
    12 
    13     // 创建进程内COM组件,返回指定接口
    14     hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IComTest, (void **)&pComTest);
    15     if (S_OK == hResult)
    16     {
    17         // 调用接口方法
    18         printf("%d
    ", pComTest->SayHello());
    19         // 释放组件
    20         pComTest->Release();
    21     }
    22     // 释放COM库
    23     CoUninitialize();
    24 
    25     return 0;
    26 }

    上面的例子和一般步骤不一致,少了QueryInterface,是因为默认返回的就是指定的接口,下面按一般步骤再实现一次:

     1 #include "IComTest.h"
     2 #include "ComTest.h"
     3 #include <stdio.h>
     4 
     5 int main()
     6 {
     7     // 初始化COM库
     8     CoInitialize(NULL);
     9 
    10     IUnknown *pUnk = NULL;
    11     IComTest *pComTest = NULL;
    12     HRESULT hResult;
    13 
    14     // 创建COM组件,返回默认接口
    15     hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk);
    16     if (S_OK == hResult)
    17     {
    18         // 查询接口
    19         hResult = pUnk->QueryInterface(IID_IComTest, (void **)&pComTest);
    20         if (S_OK == hResult)
    21         {
    22             // 调用接口方法
    23             printf("%d
    ", pComTest->SayHello());
    24         }
    25         // 释放组件
    26         pComTest->Release();
    27     }
    28 
    29     // 释放COM库
    30     CoUninitialize();
    31     return 0;
    32 }

    是直接创建COM组件并获取接口,还是先创建COM组件得到默认接口再查询其他的接口,需要具体问题具体分析。

    4.COM组件运行机制

    一个COM组件从编写到最终可以被调用,整个运行流程是怎样的?或者我们再考虑简单一点,COM组件是如何被调用的?

  • 相关阅读:
    下拉选择框,允许手动输入和过滤
    MVC数据绑定
    一个页面多个ng-app注意事项
    modal 多层弹窗 Maximum call stack size exceeded 解决方法
    VS10x CodeMap 注册码(key):
    VS2015卸载再安装
    VS2015无法创建工程
    解决VS2015版本key required问题手动方案
    猪猪公寓—事后诸葛亮
    猪猪公寓——测试总结
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/15146324.html
Copyright © 2011-2022 走看看