概述

学习微软技术COM是绕不开的一道坎,最近做项目的时候发现有许多功能需要用到COM中的内容,虽然只是简单的使用COM中封装好的内容,但是许多代码仍然只知其然,不知其所以然,所以我决定从头开始好好学习一下COM基础的内容,因此在这记录下自己学习的内容,以便日后参考,也给其他朋友提供一点学习思路。

COM的全称是Component Object Module,组件对象模型。组件就我自己的理解就是将各个功能部分编写成可重用的模块,程序就好像搭积木一样由这些可重用模块构成,这样将各个模块的耦合降到最低,以后升级修改功能只需要修改某一个模块,这样就大大降低了维护程序的难度和成本,提高程序的可扩展性。COM是微软公司提出的组件标准,同时微软也定义了组件程序之间进行交互的标准,提供了组件程序运行所需的环境。

COM是基于组件化编程的思想,在COM中每一个组件成为一个模块,它可以是动态链接库或者可执行文件,一个组件程序可以包含一个或者多个组件对象,COM对象不同于OOP(面向对象)中的对象,COM对象是定义在二进制机器代码基础之上,是跨语言的。而OOP中的对象是建立在语言之上的。脱离了语言对象也就不复存在.COM是独立在编程语言之上的,是语言无关的。COM的这一特性使得不同语言开发的组件之间的互相交互成为可能。

COM对象和接口

COM中的对象类似于C++中的对象,对象是某个类中的实例。而类则是一组相关的数据和功能组合在一起的一个定义。使用对象的应用(或另一个对象)称为客户,有时也称为对象的用户。

接口是一组逻辑相关的函数的集合,比如一组处理URL的接口,处理HTTP请求的接口等等。在习惯上接口通常是以”I”开头。对象通过接口成员函数为客户提供各种形式的服务。一个对象可以拥有多个不同的接口,以表现不同的功能集合。 在C++语言中,一个接口就是一个虚基类,而对象就是该接口的实现类,派生自该接口并实现接口的功能。

  1. class IBook
  2. {
  3. public:
  4. virtual void NextPage() = 0;
  5. virtual void ForwardPage() = 0;
  6. }
  7. class IAppliances
  8. {
  9. public:
  10. virtual void charge() = 0;
  11. virtual void shutdown() = 0;
  12. }
  13. class CKindle: public IBook, IAppliances
  14. {
  15. public:
  16. virtual void NextPage();
  17. virtual void ForwardPage();
  18. virtual void charge();
  19. virtual void shutdown();
  20. }

就像上面的例子,上面的例子中提供了一个书本的接口,书本可以翻到上一页,下一页,而电器有充电和关机的接口,最后我们利用kindle这个类来实现这两个接口。所以在使用上我们可以利用下面的伪代码来使用

  1. pInterface = CreateInterface(ID_IBOOK, ID_KINDLE);
  2. pInterface->NextPage();
  3. if(Late())
  4. {
  5. pInter2 = pInterface->QueryInterface(ID_APPLIANCES);
  6. pInter2->shutdown();
  7. }

在平时我们使用kindle的翻页功能来看书,因为翻页功能在接口IBook,所以首先调用一个创建接口的函数,传入对应接口以及接口实现类的标识,用来生成相应的接口,其实在内部也就是根据类ID来创建一个对应的实现类的实例。然后根据需要转化为对应基类的指针。在看书看累的时候,将接口转化为电子产品的接口,调用对应的关机功能,关闭电子书。在之后比如说kindle进行了升级,也就是重写了实现这些接口的代码,但是接口原型不变,这样使用接口的代码不用改变,也就是说即使kindle对内部进行了升级,优化某些功能,用户在使用上仍然是那样在用,不必改变使用习惯。再比如kindle出了一个新款,提供了背光功能,这个时候可能提供一个新接口:

  1. class IAppliances2 : public IAppliances
  2. {
  3. public:
  4. virtual void Light() = 0;
  5. }

然后只需要稍微更新一下CKindle这个实现类,新增一个Light接口的实现,在使用上如果不用背光功能原来的代码就够用了,如果要使用背光功能,只需要将原来的接口类型改为IAppliances2 ,并且添加调用背光功能的函数,而其余的功能也不变,这与实际生活相似,某个产品提供新功能时,一般保持原始功能的使用方法不变,新功能会有新的按钮或者其他方法进行打开。

再比如说我不想用kindle了改用其他的电子阅读器,只要接口不变,我的使用方法基本不变,唯一改变的可能是我以前拿着kindle,现在拿着其他品牌的阅读器,也就是说可能要改变传入CreateInterface函数中的类标识。

COM基本接口

COM中所有接口都派生自该接口:

  1. struct IUnknown
  2. {
  3. virtual HRESULT QueryInterface(REFIID riid,void **ppvObject) = 0;
  4. virtual ULONG AddRef( void) = 0;
  5. virtual ULONG Release( void) = 0;
  6. };

所有类都应该实现上述三个方法,AddRef主要将接口的引用计数+1, 而Release则是将引用计数 -1,当对象的引用计数为0,则会调用析构函数,释放对象的存储空间。每一次接口的创建和转化都会增加引用计数,而每次不再使用调用Release,都会把引用计数 -1,当引用计数为0时会释放对象的空间。

QueryInterface主要用来进行接口转化,将对象的指针转化为另外一个接口的指针,就好像上面例子中pInter2 = pInterface->QueryInterface(ID_APPLIANCES);这句代码将之前的Ibook接口转化为电子产品的接口。在C++中也就是做了一次强制类型转化。

对象和接口的唯一标识

在COM中,对象本身对于客户来说是不可见的,客户请求服务时,只能通过接口进行。每一个接口都由一个128位的全局唯一标识符(GUID,Global Unique Identifier)来标识。客户通过GUID来获得接口的指针,再通过接口指针,客户就可以调用其相应的成员函数。与接口类似,每个组件也用一个 128 位 GUID 来标识,称为 CLSID(class identifer,类标识符或类 ID),用 CLSID 标识对象可以保证(概率意义上)在全球范围内的唯一性。

实际上,客户成功地创建对象后,它得到的是一个指向对象某个接口的指针,因为 COM 对象至少实现一个接口(没有接口的 COM 对象是没有意义的),所以客户就可以调用该接口提供的所有服务。根据 COM 规范,一个 COM 对象如果实现了多个接口,则可以从某个接口得到该对象的任意其他接口。

由此可看出,客户与 COM 对象只通过接口打交道,对象对于客户来说只是一组接口。

在COM中GUID的定义如下:

  1. typedef struct _GUID {
  2. unsigned long Data1;
  3. unsigned short Data2;
  4. unsigned short Data3;
  5. unsigned char Data4[ 8 ];
  6. } GUID;

一般我们在程序中只是作为一个标志来使用,并不对它进行特别的操作。生成它一般是使用VS自带的GUID生成工具。

而CLSID的定义如下:

  1. typedef GUID CLSID;

其实在COM中一般涉及到ID的都是GUID,只是利用typedef另外定义了一个名称而已

另外COM也提供了一组函数用来对GUID进行操作:

函数 功能
IsEqualGUID 判断GUID是否相等
IsEqualCLSID 判断CLSID是否相等
IsEqualIID 判断IID是否相等
CLSIDFromProgID 把字符串形式的CLSID转化为CLSID结构形式(类似于将字符串的234转化为数字,也是把字面上的CLSID转化为计算机能识别的CLSID)
StringFromCLSID 把CLSID转化为字符串形式
IIDFromString 把字符串形式的IID转化为IID接口形式
StringFromIID 把IID结构转化为字符串
StringFromGUID2 把GUID形式转化为字符串形式

COM接口的一般使用步骤

一般使用COM中的时候首先使用CoInitialize初始化COM环境,不用的时候使用CoUninitialize卸载COM环境,在使用接口中一般需要进行下面的步骤

1. 调用CoCreateInstance函数传入对应的CLSID和对应的IID,生成对应对象并传入相应的接口指针。

2. 使用该指针进行相关操作

3. 调用接口的QueryInterface函数,转化为其他形式的接口

4. 在最后分别调用各个接口的Release函数,释放接口

下面提供一个小例子,以供参考,也方便更好的理解COM

  1. //组件部分
  2. extern "C" __declspec(dllexport) void __stdcall ComCreateObject(GUID clsID, GUID interfaceID, void** pObj);
  3. void __stdcall ComCreateObject(GUID clsID, GUID interfaceID, void** pObj)
  4. {
  5. if (clsID == CLSID_COMSTRING)
  6. {
  7. CComString *pComObject = new CComString;
  8. *pObj = pComObject->QueryInterface(interfaceID);
  9. }
  10. }
  11. class IComBase
  12. {
  13. public:
  14. virtual void* QueryInterface(GUID gInterfaceId) = 0;
  15. virtual void AddRef() = 0;
  16. virtual void Release() = 0;
  17. };
  18. static const GUID IID_ICOMSTRING = { 0xb2fcd22c, 0x63fa, 0x4f61, { 0xbf, 0x12, 0xd3, 0xd2, 0x5a, 0x99, 0x59, 0x24 } };
  19. class IComString : public IComBase
  20. {
  21. public:
  22. virtual void Init(LPCTSTR pStr) = 0;
  23. virtual int Find(LPCTSTR lpSubStr) = 0;
  24. virtual int GetLength() = 0;
  25. };
  26. static const GUID CLSID_COMSTRING = { 0xf57f3489, 0xff2d, 0x4c97, { 0xb1, 0xf6, 0xc, 0x60, 0x7e, 0xf7, 0xae, 0xfc } };
  27. class CComString : public IComString
  28. {
  29. public:
  30. virtual void* QueryInterface(GUID gInterfaceId);
  31. virtual void AddRef();
  32. virtual void Release();
  33. virtual void Init(LPCTSTR pStr);
  34. virtual int Find(LPCTSTR lpSubStr);
  35. virtual int GetLength();
  36. protected:
  37. int m_nCnt = 0;
  38. CString m_csString;
  39. };
  40. //cpp
  41. void* CComString::QueryInterface(GUID gInterfaceId)
  42. {
  43. if (gInterfaceId == IID_ICOMSTRING)
  44. {
  45. //该接口的引用计数+1
  46. AddRef();
  47. return dynamic_cast<IComString*>(this);
  48. }
  49. //如果它还实现了其他接口,可以再写判断,生成其他类型的接口
  50. return NULL;
  51. }
  52. void CComString::AddRef()
  53. {
  54. m_nCnt++;
  55. }
  56. void CComString::Release()
  57. {
  58. m_nCnt--;
  59. //引用计数为0,此时没有该类的接口被使用,应该释放该类
  60. if (m_nCnt == 0)
  61. {
  62. delete this;
  63. }
  64. }
  65. void CComString::Init(LPCTSTR pStr)
  66. {
  67. m_csString = pStr;
  68. }
  69. int CComString::Find(LPCTSTR lpSubStr)
  70. {
  71. return m_csString.Find(lpSubStr);
  72. }
  73. int CComString::GetLength()
  74. {
  75. return m_csString.GetLength();
  76. }

这些代码被封装在一个dll中,dll中导出一个函数ComCreateObject,外部在使用时调用该函数传入对应的ID,以便生成对应的接口。

在这个dll里面提供一个接口的基类IComBase,这个是仿照了COM种的IUnknow基类,另外定义了一个IComString字符串的接口,同时定义了它的实现类CComString,为了简单,它的功能方法我直接使用了一个CString类实现。

在函数ComCreateObject,会根据传入对应的类ID,来生成对应的类实例,然后调用实例的QueryInterface,转化成对应的接口,在实现类中实现了这个方法,实现类中的QueryInterface方法主要完成了类型转化并将引用计数+1。

而Release函数在每次-1的时候会进行判断,当引用计数为0时销毁该类的实例

由于类是new出来创建在堆上的,所以每次用完一定要记得调用Release释放,否则会造成内存泄露

注意:在使用这里使用的是dynamic_cast进行类型转化,在进行类的强制类型转化时,特别是在有多重继承的情况下,最好使用dynamic_cast方式进行转化,当一个类拥有多个基类时,类中有多个虚函数表,为了能正常找到对应的虚函数表,就需要进行对应的偏移量的计算,C中的强制类型转化是直接将对象的首地址进行转化,这样在寻址虚函数表时可能会出错。而dynamic_cast会进行对应的计算。详细情形请参考这里

在使用上

  1. void ComInitialize();
  2. void ComUninitialize();
  3. typedef void(__stdcall *pfnCreateInstance)(GUID, GUID, void**);
  4. pfnCreateInstance CreateInstance;
  5. HMODULE hComDll = NULL;
  6. int _tmain(int argc, _TCHAR* argv[])
  7. {
  8. ComInitialize();
  9. IComString *pIString = NULL;
  10. CreateInstance(CLSID_COMSTRING, IID_ICOMSTRING, (void**)&pIString);
  11. pIString->Init(_T("Hello World"));
  12. IComString* pIString2 = (IComString*)(pIString->QueryInterface(IID_ICOMSTRING));
  13. int nLength = pIString2->GetLength();
  14. int iPos = pIString2->Find(_T("World"));
  15. printf("%d, %d\n", nLength, iPos);
  16. pIString->Release();
  17. pIString2->Release();
  18. return 0;
  19. }
  20. void ComInitialize()
  21. {
  22. hComDll = LoadLibrary(_T("ComInterface.dll"));
  23. if (NULL != hComDll)
  24. {
  25. CreateInstance = (pfnCreateInstance)GetProcAddress(hComDll, "ComCreateObject");
  26. }
  27. }
  28. void ComUninitialize()
  29. {
  30. FreeLibrary(hComDll);
  31. }

给使用者使用时只需要提供对应类和接口的GUID,然后将函数ComCreateObject原型提供给调用者,以便生成对应的接口。

这里为了模仿COM的使用定义了ComInitialize和ComUninitialize这两个函数,真实的初始化函数怎么写的,我也不知道,在这里只是为了模仿COM的使用。

至此相信各位小伙伴应该对COM有了一个初步的了解

COM学习(一)——COM基础思想的更多相关文章

  1. 如何学习FPGA?FPGA学习必备的基础知识

    如何学习FPGA?FPGA学习必备的基础知识 时间:2013-08-12 来源:eepw 作者: 关键字:FPGA   基础知识       FPGA已成为现今的技术热点之一,无论学生还是工程师都希望 ...

  2. [学习线路] 零基础学习hadoop到上手工作线路指导(初级篇)

    about云课程最新课程Cloudera课程   零基础学习hadoop,没有想象的那么困难,也没有想象的那么容易.在刚接触云计算,曾经想过培训,但是培训机构的选择就让我很纠结.所以索性就自己学习了. ...

  3. Python学习课程零基础学Python

    python学习课程,零基础Python初学者应该怎么去学习Python语言编程?python学习路线这里了解一下吧.想python学习课程?学习路线网免费下载海量python教程,上班族也能在家自学 ...

  4. Java后端高频知识点学习笔记1---Java基础

    Java后端高频知识点学习笔记1---Java基础 参考地址:牛_客_网 https://www.nowcoder.com/discuss/819297 1.重载和重写的区别 重载:同一类中多个同名方 ...

  5. 这几天开始,先学习一些 java 基础吧,学的有点累

    这几天开始,先学习一些 java 基础吧,学的有点累

  6. Emacs学习心得之 基础配置

    作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Emacs学习心得之 基础配置 1.前言2.基础配置 一.前言 本篇博文记录了Emacs的一 ...

  7. Emacs学习心得之 基础操作

    作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Emacs学习心得之 基础操作 1.前言与学习计划2.Emacs基础操作 一. 前言与学习计 ...

  8. java与.net比较学习系列(2) 基础语言要素

    这一篇从最基础的开始对比总结,说起基础语言要素,故名思义,就是学习语言的基础,主要内容包括标识符,关键字和注释.我想从以下几点进行总结,有区别的地方有都使用红色粗体字进行了总结. 1,标识符 2,关键 ...

  9. MyBatis:学习笔记(1)——基础知识

    MyBatis:学习笔记(1)--基础知识 引入MyBatis JDBC编程的问题及解决设想 ☐ 数据库连接使用时创建,不使用时就释放,频繁开启和关闭,造成数据库资源浪费,影响数据库性能. ☐ 使用数 ...

随机推荐

  1. logstash 向elasticsearch写入数据,怎样指定多个数据template

    之前在配置从logstash写数据到elasticsearch时,指定单个数据模板没有问题.可是在配置多个数据模板时候,总是不成功,后来找了非常多资料,最终找到解决的方法,就是要多加一个配置项: te ...

  2. mapreduce作业reduce被大量kill掉

    之前有一段时间.我们的hadoop2.4集群压力非常大.导致提交的job出现大量的reduce被kill掉.同样的job执行时间比在hadoop0.20.203上面长了非常多.这个问题事实上是redu ...

  3. ZOJ ACM 1204 (JAVA)

    毕业好几年了,对算法还是比較有兴趣,所以想又一次開始做ACM题.俺做题比較任意,一般先挑通过率高的题来做. 第1204题,详细描写叙述请參考,ZOJ ACM 1204 1)难度分析 这个题目,基本的难 ...

  4. ThoughtWorks 2017技术雷达

    前言: ThoughtWorks人酷爱技术.我们对技术进行构建.研究. 测试.开源.记述,并始终致力于对其进行改进-以求造福 大众.我们的使命是支持卓越软件并掀起IT革命.我们创建 并分享Though ...

  5. 最受Java开发者青睐的Java应用服务器 —— Tomcat

    Tomcat 是一个小型的轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选.今天,就一起来了解下 Tomcat. Java 应用服务器 Tomc ...

  6. Java filter中的chain

    一.Filter Filter:用来拦截请求,处于客户端和被请求资源之间,是为了代码的复用性.Filter链,在web.xml中哪个先配置就先调用哪个 二.FilterChain(过滤链) 服务器会按 ...

  7. chrome调试,打完断点后关于JS的几个控制介绍

    打完断点之后,关于JS的几个控制介绍. 快捷键:F8 "逐过程执行",继续执行代码,直到遇到下一个断点. 详细解释: 暂停和开始.当设置了断点之后,js的执行就暂停了,如果我们想要 ...

  8. intelli idea中配置Tomcat找不到的解决办法

    这两天新入职一家公司,公司用的是intelli idea,以前用习惯了eclipse,感觉到有点不太习惯,当然,intelli idea也有自己的强大之处.在开始配置Tomact之前,按照网上的说法, ...

  9. linux下脚本做成服务

    一.脚本做成服务 1.把启动脚本复制到 /etc/init.d目录中 2.脚本内容 xxxx代表jar包名称 #!/usr/bin/env bash # chkconfig: 2345 20 80 # ...

  10. 【JMeter】if语句中不能Failure=false解决办法

    错误写法: if(roomId.matches("regEx")) Failure=false; else{ Failure=true; FailureMessage=" ...