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组件,需要完成以下工作:

  1. COM组件接口
  2. COM组件实现类
  3. COM组件创建工厂
  4. COM组件注册与取消注册

本文以一个例子作为说明,COM组件提供了一个SayHello的接口函数,将“Hello COM”打印输出。

2.1 创建COM组件接口

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

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

2.2 创建COM组件实现类

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

// ComTest.h
#pragma once
#include "IComTest.h"
// class id,COM组件唯一标识
static const WCHAR* CLSID_CComTestStr = L"{4046FA83-57F0-4475-9381-8818BFC50DDF}";
static const GUID CLSID_CComTest = { 0x4046FA83, 0x57F0, 0x4475, { 0x93, 0x81, 0x88, 0x18, 0xBF, 0xC5, 0x0D, 0xDF } };class CComTest :public IComTest
{public:CComTest();~CComTest();// 实现IUnknown接口// 查找接口// riid : 输入参数,接口id// ppvObject : 输出参数,返回相应的接口virtual HRESULT _stdcall QueryInterface(const IID &riid, void ** ppvObject);// 增加引用计数virtual ULONG _stdcall AddRef();// 减少引用计数virtual ULONG _stdcall Release();virtual int _stdcall SayHello();protected://引用计数ULONG m_RefCount;//全局创建对象个数static ULONG g_ObjNum;
};
// ComTest.cpp
#include "ComTest.h"
#include <stdio.h>ULONG CComTest::g_ObjNum = 0;CComTest::CComTest()
{m_RefCount = 0;g_ObjNum++;//对象个数+1
}CComTest::~CComTest()
{g_ObjNum--;//对象个数-1
}HRESULT _stdcall CComTest::QueryInterface(const IID &riid, void **ppvObject)
{// 通过接口id判断返回的接口类型if (IID_IUnknown == riid){*ppvObject = this;((IUnknown*)(*ppvObject))->AddRef();}else if (IID_IComTest == riid){*ppvObject = (IComTest*)this;((IComTest*)(*ppvObject))->AddRef();}else{*ppvObject = NULL;return E_NOINTERFACE;}return S_OK;
}ULONG _stdcall CComTest::AddRef()
{m_RefCount++;return m_RefCount;
}ULONG _stdcall CComTest::Release()
{m_RefCount--;if (0 == m_RefCount){delete this;return 0;}return m_RefCount;
}int _stdcall CComTest::SayHello()
{printf("hello COM\r\n");return 666;
}

2.3 COM组件创建工厂

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

// ComTestFactory.h
#pragma once
#include <Unknwn.h>class CComTestFactory : public IClassFactory
{public:CComTestFactory();~CComTestFactory();// 实现IUnknown接口  virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject);virtual ULONG _stdcall AddRef();virtual ULONG _stdcall Release();// 实现IClassFactory接口  virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, const IID& riid, void **ppvObject);virtual HRESULT _stdcall LockServer(BOOL fLock);protected:ULONG m_RefCount;//引用计数static ULONG g_ObjNum;//全局创建对象个数
};
// ComTestFactory.cpp
#include "ComTestFactory.h"
#include "ComTest.h"ULONG CComTestFactory::g_ObjNum = 0;CComTestFactory::CComTestFactory()
{m_RefCount = 0;g_ObjNum++;
}CComTestFactory::~CComTestFactory()
{g_ObjNum--;
}// 查询指定接口
HRESULT _stdcall CComTestFactory::QueryInterface(const IID &riid, void **ppvObject)
{if (IID_IUnknown == riid){*ppvObject = (IUnknown*)this;((IUnknown*)(*ppvObject))->AddRef();}else if (IID_IClassFactory == riid){*ppvObject = (IClassFactory*)this;((IClassFactory*)(*ppvObject))->AddRef();}else{*ppvObject = NULL;return E_NOINTERFACE;}return S_OK;
}ULONG _stdcall CComTestFactory::AddRef()
{m_RefCount++;return m_RefCount;
}ULONG _stdcall CComTestFactory::Release()
{m_RefCount--;if (0 == m_RefCount){delete this;return 0;}return m_RefCount;
}// 创建COM对象,并返回指定接口
HRESULT _stdcall CComTestFactory::CreateInstance(IUnknown *pUnkOuter, const IID &riid, void **ppvObject)
{if (NULL != pUnkOuter){return CLASS_E_NOAGGREGATION;}HRESULT hr = E_OUTOFMEMORY;//ComClass::Init();CComTest* pObj = new CComTest();if (NULL == pObj){return hr;}hr = pObj->QueryInterface(riid, ppvObject);if (S_OK != hr){delete pObj;}return hr;
}HRESULT _stdcall CComTestFactory::LockServer(BOOL fLock)
{return NOERROR;
}

2.4 COM组件的注册

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

  1. DllGetClassObject:用于获得类工厂指针
  2. DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载COM组件
  3. DllRegisterServer:将COM组件注册到注册表中
  4. DllUnregisterServer:删除注册表中的COM组件的注册信息

DLL还有一个可选的入口函数DllMain,可用于初始化和释放全局变量

  1. DllMain:DLL的入口函数,在LoadLibrary和FreeLibrary时都会调用
// ComTestExport.h
#include <windows.h> extern "C" HRESULT _stdcall DllRegisterServer();
extern "C" HRESULT _stdcall DllUnregisterServer();
extern "C" HRESULT _stdcall DllCanUnloadNow();
extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv);
// ComTestExport.cpp
#include "ComTestExport.h"
#include "ComTestFactory.h"
#include "ComTest.h"#include <iostream>HMODULE g_hModule;  //dll进程实例句柄
ULONG g_num;        //组件中ComTest对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载  int myReg(LPCWSTR lpPath)   //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID
{HKEY thk, tclsidk;//打开键HKEY_CLASSES_ROOT\CLSID,创建新键为ComTest的CLSID,  //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值  if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){printf("RegOpenKey ok\r\n");if (ERROR_SUCCESS == RegCreateKey(thk, CLSID_CComTestStr, &tclsidk)){wprintf(L"RegCreateKey %s ok\r\n", CLSID_CComTestStr);HKEY tinps32k, tprogidk;if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){printf("RegCreateKey InprocServer32 ok\r\n");if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){}RegCloseKey(tinps32k);}RegCloseKey(tclsidk);}RegCloseKey(thk);}//在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CComTest,  //在该键下创建子键,并将CCompTest的CLSID写为该键的默认值  if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest", &thk)){if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){if (ERROR_SUCCESS == RegSetValue(tclsidk,NULL,REG_SZ,CLSID_CComTestStr,wcslen(CLSID_CComTestStr) * 2)){}}}//这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CComTest为参数调用CLSIDFromProgID函数  //来获取CCompTest的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象  return 0;
}extern "C" HRESULT _stdcall DllRegisterServer()
{WCHAR szModule[1024];//获取本组件(dll)所在路径  DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); if (0 == dwResult){return -1;}MessageBox(NULL, szModule, L"", MB_OK);//将路径等信息写入注册表  myReg(szModule);return 0;
}int myDelKey(HKEY hk, LPCWSTR lp)
{if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){}return 0;
}//删除注册时写入注册表的信息
int myDel()
{HKEY thk;if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){myDelKey(thk, L"{4046FA83-57F0-4475-9381-8818BFC50DDF}\\InprocServer32");myDelKey(thk, CLSID_CComTestStr);RegCloseKey(thk);}if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest", &thk)){myDelKey(thk, L"CLSID");}myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CComTest");return 0;
}extern "C" HRESULT _stdcall DllUnregisterServer()
{//删除注册时写入注册表的信息  myDel();return 0;
}// 用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用
extern "C" HRESULT _stdcall DllCanUnloadNow()
{//如果对象个数为0,则可以卸载  if (0 == g_num){return S_OK;}else{return S_FALSE;}
}//用于创建类厂并返回所需接口,由CoGetClassObject函数调用
extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)
{LPOLESTR szCLSID;StringFromCLSID(rclsid, &szCLSID);     //将其转化为字符串形式用来输出  wprintf(L"rclsid CLSID \"%s\"\n", szCLSID);szCLSID;StringFromCLSID(riid, &szCLSID);     //将其转化为字符串形式用来输出  wprintf(L"riid CLSID \"%s\"\n", szCLSID);if (CLSID_CComTest == rclsid){CComTestFactory* pFactory = new CComTestFactory();//创建类厂对象  if (NULL == pFactory){return E_OUTOFMEMORY;}HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口  return result;}else{return CLASS_E_CLASSNOTAVAILABLE;}
}// 提供DLL入口;对于动态链接库,DllMain是一个可选的入口函数,在COM组件中是必须有的
BOOL APIENTRY DllMain(HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{//获取进程实例句柄,用于获取本组件(dll)路径  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;
}

导出文件(Source.def):

LIBRARY "ComTest_Server"
EXPORTS
DllCanUnloadNow
DllGetClassObject
DllUnregisterServer
DllRegisterServer

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

> regsvr32 ComTest_Server.dll

3. COM组件使用

COM组件的使用包括:

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

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

CoInitialize(NULL);  // COM库初始化
// ...
IUnknow *pUnk = NULL;
IObject *pObj = NULL;
// 创建组件对象,CLSID_XXX为COM组件类的GUID(class id),返回默认IID_IUnknown接口
HRESULT hr = CoCreateInstance(CLSID_XXX,NULL,CLSCTX_INPROC_SERVER,NULL,IID_IUnknown,(void **)&pUnk);
if(S_OK == hr)
{// 获取接口,IID_XXX为组件接口的GUID(interface id)hr = pUnk->QueryInterface(IID_XXX,(void **)&pObj);if(S_OK == hr){// 调用接口方法pObj->DoXXX();}// 释放组件对象pUnk->Release();
}
//...
// 释放COM库
CoUninitialize();

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

#include "IComTest.h"
#include "ComTest.h"
#include <stdio.h>int main()
{// 初始化COM库CoInitialize(NULL);IComTest *pComTest = NULL;HRESULT hResult;// 创建进程内COM组件,返回指定接口hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IComTest, (void **)&pComTest);if (S_OK == hResult){// 调用接口方法printf("%d\r\n", pComTest->SayHello());// 释放组件pComTest->Release();}// 释放COM库CoUninitialize();return 0;
}

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

#include "IComTest.h"
#include "ComTest.h"
#include <stdio.h>int main()
{// 初始化COM库CoInitialize(NULL);IUnknown *pUnk = NULL;IComTest *pComTest = NULL;HRESULT hResult;// 创建COM组件,返回默认接口hResult = CoCreateInstance(CLSID_CComTest, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&pUnk);if (S_OK == hResult){// 查询接口hResult = pUnk->QueryInterface(IID_IComTest, (void **)&pComTest);if (S_OK == hResult){// 调用接口方法printf("%d\r\n", pComTest->SayHello());}// 释放组件pComTest->Release();}// 释放COM库CoUninitialize();return 0;
}

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

4. COM组件运行机制

转自:https://blog.csdn.net/lwwl12/article/details/102784875?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

3. COM编程——COM编程入门实践相关推荐

  1. python快速编程入门课后简答题答案-Python编程:从入门到实践(第2版)第1章习题答案...

    <Python编程:从入门到实践>是一本不错的书.第2版已经公开预售,预计会在10月份正式上市. 动手试一试 本章的练习都是探索性的,但从第2章开始将要求你用那一章学到的知识来解决问题. ...

  2. python编程入门课程视频-带学《Python编程:从入门到实践》

    以<Python编程:从入门到实践>为教材,以吕老师+几位同学学习串讲为主线,系统的进行python的入门,并手把手带着大家做课后习题. 教材简介: <python编程从入门到实践& ...

  3. python编程入门p-读书笔记 - 《Python编程:从入门到实践》

    Tag:看<Python编程:从入门到实践>读书笔记 基础知识: 字符串str 改变大小写(临时):title首字母大写,upper全大写,lower全小写 删除空白(临时):rstrip ...

  4. python起步输入-《Python编程:从入门到实践》第一章:起步

    Python编程:从入门到实践 这篇文章主要知识点是关于Python编程,从入门到实践,起步,Python编程:从入门到实践,的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下电子书 搭建编 ...

  5. python编程入门到实践笔记-python基础(《Python编程:从入门到实践》读书笔记)...

    注: 本文的大部分代码示例来自书籍<Python编程:从入门到实践>. 一.变量: 命名: (1)变量名只能包含字母.数字和下划线.变量名可以字母或下划线打头,但不能以数字打头 (2)变量 ...

  6. python编程从入门到精通实践_《Python编程:从入门到实践》总结_Day01

    前言 是在原有文章的基础上直接扩充更新还是将其作为单独的系列文章呢?思虑再三,还是决定把接下来的Day01-Day05的总结独立出来.此系列是关于<Python编程:从入门到实践>的总结, ...

  7. python编程入门-Python编程:从入门到实践 PDF 中文扫描版

    给大家带来的一篇关于Python3.5编程相关的电子书资源,介绍了关于Python入门.Python实践.Python编程方面的内容,本书是由中国工信出版集团出版,格式为PDF,资源大小9.85M,埃 ...

  8. python编程入门视频-带学《Python编程:从入门到实践》

    以<Python编程:从入门到实践>为教材,以吕老师+几位同学学习串讲为主线,系统的进行python的入门,并手把手带着大家做课后习题. 教材简介: <python编程从入门到实践& ...

  9. 【读书笔记】Python编程:从入门到实践-埃里克·马瑟斯,python基础体系巩固和常见场景练习

    [概述] 书名:Python编程:从入门到实践 作者:埃里克·马瑟斯 日期:2021年09月01日 读书用时:1632页,100小时,27个笔记 [读书笔记] ◆ 第4章 操作列表 >> ...

  10. 一、 Python 基础知识笔记 —— 《Python编程:从入门到实践(第二版)》学习笔记

    前言 先安利这本书<Python编程:从入门到实践(第二版)>,作者埃里克-马瑟斯,很适合新手入门,我的python入门学习就是以这本书为核心: 再安利一个网站:菜鸟教程-Python3教 ...

最新文章

  1. linux默认csh修改命令,Solaris中默认Shell的修改以及命令行补全的设置
  2. 用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
  3. pytorch 计算相似度,相关系数
  4. python操作excel-Python对Excel的读写等操作(转)
  5. 24.下拉列表的交互事件
  6. pythonos模块介绍_Python os模块介绍
  7. 前端面试题整理【转】
  8. rational rose 启动选择_Rational Rose打开问题
  9. 会计丑闻之后 东芝“迎来”第五次延交财报
  10. 如何定义 Java 中的方法
  11. 三星Note 10 Pro曝光:搭载骁龙855处理器 后置四摄
  12. Apache下禁止显示网站目录结构的方法
  13. 阿里云云服务器部署HTML静态网页
  14. PV、UV、访问次数、跳出率、转化率、平均访问时长
  15. 【学习笔记】计算机基础知识
  16. PhotoZoom pro8官方激活下载免费版无损放大图片工具
  17. 专题:2019世界移动通信大会(MWC)精彩纷呈,中国企业各出大招
  18. 《刷新》精髓:微软第三任CEO萨提亚•纳德拉重新发现微软灵魂的传奇历程,给了我们普通人哪些启示?
  19. ajax获取涨停股票接口,80后股神研究的两个涨停买入法!(图解)
  20. 常见4种风险定量分析法

热门文章

  1. sql server 表结构信息查询
  2. android内存泄露_Java应用程序中的内存泄漏及内存管理
  3. JetLinks 物联网基础平台 1.6 RELEASE 发布
  4. 架构实战篇(三)-Spring Boot架构搭建RESTful API案例
  5. 新建模块 pom.xml依赖无法识别_使用模块依赖关系,第2部分
  6. python识别数字程序_Python识别处理照片中的条形码
  7. php phantomjs 安装_安装php-phantomjs
  8. PHP数据处理:合并数据、详情数据
  9. IntelliJ IDEA+Maven运行apache-storm的LocalCluster例子
  10. LINUX一个正确的mime xml范例