作者:灵剑
链接:https://www.zhihu.com/question/49433640/answer/115952604
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解释不解释也都是死掉了的技术了啊……
COM主要是一套给C/C++用的接口,当然为了微软的野心,它也被推广到了VB、Delphi以及其他一大堆奇奇怪怪的平台上。它主要为了使用dll发布基于interface的接口。我们知道dll的接口是为了C设计的,它导出的基本都是C的函数,从原理上来说,将dll加载到内存之后,会告诉你一组函数的地址,你自己call进去就可以调用相应的函数。
但是对于C++来说这个事情就头疼了,现在假设你有一个类,我们知道使用一个类的第一步是创建这个类:new MyClass()。这里直接就出问题了,new方法通过编译器计算MyClass的大小来分配相应的内存空间,但是如果库升级了,相应的类可能会增加新的成员,大小就变了,那么使用旧的定义分配出来的空间就不能在新的库当中使用。
要解决这问题,我们必须在dll当中导出一个CreateObject的方法,用来代替构造函数,然后返回一个接口。然而,接口的定义在不同版本当中也是有可能会变化的,为了兼容以前的版本同时也提供新功能,还需要让这个对象可以返回不同版本的接口。接口其实是一个只有纯虚函数的C++类,不过对它进行了一些改造来兼容C和其他一些编程语言。
在这样改造之后,出问题的还有析构过程~MyClass()或者说delete myClass,因为同一个对象可能返回了很多个接口,有些接口还在被使用,如果其中一个被人delete了,其他接口都会出错,所以又引入了引用计数,来让许多人可以共享同一个对象。
其实到此为止也并不算是很奇怪的技术,我们用C++有的时候也会使用Factory方法来代替构造函数实现某些特殊的多态,也会用引用计数等等。COM技术的奇怪地方在于微软实在是脑洞太大了,它们构造了一个操作系统级别的Factory,规定所有人的Interface都统一用UUID来标识,以后想要哪个Interface只要报出UUID来就行了。这样甚至连链接到特定的dll都省了。

这就好比一个COM程序员,只要他在Windows平台上,调用别的库就只要首先翻一下魔导书,查到了一个用奇怪文字写的“Excel = {xxx-xxx-xxxx...}”的记号,然后它只要对着空中喊一声:“召唤,Excel!CoCreateInstance, {xxx-xxx-xxxx...}”
然后呼的从魔法阵里面窜出来了一个怪物,它长什么样我们完全看不清,因为这时候它的类型是IUnknow,这是脑洞奇大无比的微软为所有接口设计的一个基类。我们需要进一步要求它变成我们能控制的接口形态,于是我们再喊下一条指令:
“变身,Excel 2003形态!QueryInterface, {xxx-xxx-xxxx...}”
QueryInterface使用的是另一个UUID,用来表示不同版本的接口。于是怪物就变成了我们需要的Excel 2003接口,虽然我们不知道它实际上是2003还是2007还是更高版本。
等我们使唤完这只召唤兽,我们就会对它说“回去吧,召唤兽!Release!”但是它不一定听话,因为之前给它的命令也许还没有执行完,它会忠诚地等到执行完再回去,当然我们并不关心这些细节。

微软大概会觉得自己设计出了软件史上最完美的二进制接口,从今以后所有的第三方库都可以涵盖在这套接口之下。然而历史的车轮是无情的,它碾过那些自以为是的人的速度总是会比想象的更快。Java的直接基于类的接口被广泛应用,开发使用起来远远来的简单,即便偶尔出点问题大家也都想办法解决了,事实证明程序员并不愿意花10倍的编写代码的时间来解决二进制库的版本兼容问题,他们更愿意假装没看见。很快微软也抄了一个.NET托管dll的方案出来,于是纯的二进制接口COM就慢慢被抛弃了。

COM,OLE,ActiveX,OCX,VBScript,历史不会忘记你们的,如果历史忘了,我替历史记住你们。安息吧。

=============================================================

这怎么还火了,这不应该是一个讨论遗留技术的冷门问题吗……

补充说明一下,其实并没有贬低COM的意思,COM在当时一定是一个伟大的发明,只是技术革新太快。到今天,其实COM都是C++的二进制本机代码的动态链接的最佳的选择(Windows上的)。但是,C++,二进制本机代码,动态链接,这三件事现在没有一件是重要的……
COM在Windows操作系统底层继续发挥余热的时间肯定还会很长,因为永远都会有偏底层的C++开发的需要,必须游戏之类。只是技术趋势已经很明确了。

作者:知乎用户
链接:https://www.zhihu.com/question/49433640/answer/116028598
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

因为公司产品的关系,一直都使用COM框架。
下面来一个一个解释吧 。
GUID:
全局唯一标识符,可以看成是唯一的一个ID,类似于物理网址那样。

IID:
也就是接口的唯一ID。C++中本没有接口的概念,是COM强行引入的,也就是一个类中全部都是纯虚函数,这样的类称为接口(interface),微软甚至定义了一个宏,大概就是这样:#define interface struct;

IUnknown接口:
这个解释起来会比较麻烦。如

@灵剑

所说,当自己写的一个dll升级的时候,内部可能增加了成员,导致分配的空间发生变化,从而使得次dll和以前的dll不能兼容。这个就是臭名昭著的dll hell,为此微软最开始想了个很挫的方法,那就是在dll后面加上自己的版本号,如:
myDll_1.dll, myDll_2.dll……如果你打开system32目录看看就知道是怎么回事了。

但是这样总不是一个办法,假如有个实现类MyClass,我这样操作:MyClass* ptr = new MyClass(); MyClass可能在不同dll版本中占的空间不同产生兼容问题,我拿一个指向MyClass的指针调用方法也会产新问题,那么,如果是指向一个接口(只含有纯虚方法,不含有成员)的指针不就没有问题了吗,于是就变成了这样:
IMyClass* ptr = new MyClass();
但是这样还没有解决new的问题,最好有一个创建实例的方法,并且返回共同的接口,这便是IUnknown的由来。微软一拍脑袋,想出了类似下面这样的代码:
IUnknown* pUnk = NULL;
HRESULT hr = CreateObject(&pUnk);
通过统一的函数,创建出个统一的接口的实例来避免产生dll兼容性的问题。

再谈GUID、CLSID、IID:
从上来看,所有的COM类其实都继承了IUnknown。但是,我拿个IUnknown接口有毛用啊,我还是需要把它转为我的具体类才行。假设有个汽车类Car,它继承于ICar,像这样:
IUnknown* pUnk = NULL;
CreateCar(&pUnk);
ICar* pCar = (ICar*)pUnk;
这样,我们拿到ICar指针才有意义。
但是微软认为,直接由用户来转型是不安全的,比如,你怎么知道pUnk一定可以转成ICar*呢。除此之外,ICar这个类不具有唯一性,我们需要唯一的一个标识符来确定一个类,那么这个标识符就是GUID。类ID就叫作CLSID,接口ID就叫作IID。我们需要一个转型的函数叫QueryInterface

QueryInterface作为IUnknown中的一个纯虚函数,做的事情其实很简单,判断自己能不能转成某个GUID所指向的类而已。如果不可以,则返回E_NOTIMPL(谢谢

@Gee Law

指出),可以的话返回S_OK,并将转换后的指针作为参数返回,代码类似如下,可以体会一下:

public class Car : IUnknown, ICar
{
HRESULT QueryInterface(REFIID riid, void **ppvObject)
{
if (ISEQUAL_IID(riid, IID_ICar)) //riid和ICar的IID相同,说明可以转换成ICar
{
*ppvObject = static_cast<ICar*>(this);
return S_OK;
}
else if (ISEQUAL_IID(riid, IID_IUnknown))
{
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
return E_NOTIMPL;
}
}

一个真正的QueryInterface要做的事情还要多一点,如增加引用计数等,这里就不多说了。

外部是这样调用:
ICar* pCar = NULL;
pUnk->QueryInterface(IID_ICar, (void**)&pCar);
这样,我们就从pUnk得到了个ICar*。
其实,这种写法,丑爆了。

IDL:
微软说我们的COM很NB,和语言是无关的!只要你按照我们的要求,建立一个接口,就可以实现COM。
但是,不同语言的语法不同,怎么才能有个通用的方案来定义接口呢,于是微软用了洪荒之力发明了一种语言,叫作IDL。
遵循IDL,就可以根据不同平台生成不同代码,如我定义了一个整数,在Java中可能是double,在VB中可能是Integer,但是我只需要写一份IDL,用IDL的解析器,如midl.exe可自动生成目标语言的代码(想法往往是美好的,但是好像没有什么人会生成其他语言吧……)

COM其实是个很庞大的体系,还有很多内容没有说,如引用计数、Invoke、RPC之类的,但是基本的思想就是上面我说的这样了。

作者:Xiaoyu Ma
链接:https://www.zhihu.com/question/49433640/answer/116142887
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

已经将近7年没碰COM了,强行答一发。
从原理来说,C++并不像Java的JVM一样到二进制级别都有详细定义。C++对compiler vendor在二进制细节上并没有约束:因为C++更贴近裸机,每一个约束都可能造成二进制代码并不能在某些平台上高效执行。
举个简单的例子,C++的对象模型中实现多态和动态绑定的部分,是通过虚表(vtable)和虚指针(vptr)实现的。但是标准中没有说,vptr应该长在对象的首还是尾,没说各个虚函数的坑应该怎么排列,是按照函数定义的顺序排列还是按照字母表顺序排,厂商也可以在虚表实现里增加自己的奇怪插入信息,比如加个对象识别符什么的,甚至函数重载所使用的Name Mangling(同一个名字的函数能绑定到不同的参数版本,需要在名字上做手脚,因为symbol table在link的时候需要看到不同的名字才能找到函数实体)机制也没有规范。
这意味着什么?Java产生的对象,至少ByteCode级别是兼容的,不论你是用IBM的JDK或者OpenJDK还是Oracle的原装JDK,只要大版本一样,互相之间是可以调用的。C++就不行了,你一个Intel C++ Compiler或者g++哪怕在同一个平台,格式也是不同的,你想做动态调用,我说
ptr->func()
就这么个简单的动作,不同的编译器产生的对象而言,你都无法确定虚表怎么定位(当然你也可以说你见过的compiler都把虚表放在对象头部,其实他们也可以不这么做,没人拦着他们),就算定位了,你甚至都找不到对应的虚函数指针。
对于所有工程的源码都混在一个项目里编译的场景,这不是问题,因为你用同一个编译器,同一套规范产生代码,代码也是编译时就link起来的,二进制细节你完全无需关心。但是你要发布一个组件呢?我想做一个时钟类,不开放代码,二进制形式,比如以DLL方式发布出来,给别的C++项目,甚至一个JAVA项目去调用,没有COM的话,你大概只能选择以没有OO的基本方式发布一个函数组合,里面没有继承,没有类,什么都没有,只有一堆函数。但是如果我还是希望有面向对象呢,还是希望有类,有继承呢?
COM一大部分是为了解决这个问题而产生的。本质上来说,COM就是定义了一套二进制规范,用C、C++无歧义的部分,手工撸了一套OO机制出来。这样的机制,在你不考虑跨组件之间共享的时候,是很累赘很扯淡的,毕竟以前用编译器帮忙搞定的事情,现在都得手动了。但是遵循这套规范的好处是,一个组件对象,可以被不同编译器动态加载和调用。甚至,即便如.NET Java VB这样画风完全不一样的东西,也能调用一个C++写出来的COM组件。
既然自己定义OO规范,那么微软就可以按照自己的想法对整个体系进行全新设计。例如用引用计数(相对于另一种在VM中非常普遍的设计:垃圾回收,这个更容易在没有VM的环境干净地实现)来管理对象生命周期,所以有了AddRef和Release;为了实现安全的动态转型(Dynamic Cast),微软规定需要用QueryInterface来查询接口;这三个放在一起,就是COM的根接口,类似Java的Object根对象,它定义了所有COM对象都需要实现的功能:生命周期管理,安全的动态转型。
IDL则是为了方便对COM对象的接口定义而设计的语言。IDL规定了一个COM组件有些什么接口,接口有些什么函数,但是不涉及具体实现。你可以简单粗暴地认为这个东西类似COM的头文件。用这个接口定义语言,可以方便地产生跨平台的COM调用辅助代码。
而GUID则是一个全局唯一标识符(通过奇怪的算法产生一个基本不可能重复的ID),这个标识符可以用在标示组件本身,也可以用来标示一个接口,标示接口的时候它就被称为IID(Interface ID)。唯一的标示解决了组件重名可能的混淆,或者同一个组件不同版本,接口之间不同版本的识别。不同于Java的用包路径标识类还可能会版本冲突,GUID标识的类和接口,基本可以认为没有这个问题。

需要详细理解的,请去看《COM本质论》,在此之前可能要先看《Inside C++ Object Model》。后一本看完让你对C++的OO实现有比较深入的理解,再看前一本你才能明白为何COM要被设计出来。

COM总结 - 转自知乎相关推荐

  1. java 自省_自知 自省 自立 自信 自尊 自治 自强 自制

    自知 自省 自立 自信 自尊 自治 自强 自制 能知人者有智力,能自知才是真正的智者: 能战胜别人者有力量,能战胜自己才是真正的强者: 能知足者就是富有,能勤奋顽强坚持才是真正的有志者: 不失其立足之 ...

  2. 自知、自胜、知足、强行,不失其所

    老子在<道德经>中零散地谈到很多,我觉得第三十三章讲的最接地气.就算不能融会贯通老子五千言,只要对着本章38个字死磕到底,每个人都会有不凡的成长. 先看原文: 知人者智,自知者明. 胜人者 ...

  3. 送给前端的你,推荐几篇前端汇总文章。(来自知乎专栏)

    送给前端的你,推荐几篇前端汇总文章.(来自知乎专栏) 来源:https://zhuanlan.zhihu.com/p/22229868 作者:路人甲 链接:https://zhuanlan.zhihu ...

  4. 自控力极差的人如何自救-转载自知乎高赞回答

    目录 一. 75%真实的故事 二.学霸们的困扰 三.自控力的假象与真相 1.保证我们高效运转的其实是习惯,而不是自控力. 2.人的自控力是有限的 四. 看不见的敌人 五.习惯的原理 六. 三点心态 1 ...

  5. 关于DAO的反身性理论:贡献者不自知的互助性成长

    撰文|ThePrimediaDAO发起人Jerry 在一个有知识.技术.眼界和胆识作为参与游戏门槛的领域,即使是在财富刺激下的投机者的行为,也可能参与了塑造世界的进程-- 投机者不是直接想参与塑造世界 ...

  6. 当生活真的过得很艰难的时候,你是怎么熬过来的?(内容来自知乎,用于自省)...

    当生活真的过得很艰难的时候,你是怎么熬过来的?(内容来自知乎,用于自省) 一.总结 一句话总结:我自省这样的文章,并不是因为我状态差,事实上,我状态超好,只是因为忧患意识,在需要的时候自省一下 1.都 ...

  7. 你曾后悔进入 IT 行业吗?为什么?(转自知乎)--一生不悔入IT

    你曾后悔进入 IT 行业吗?为什么?(转自知乎)--一生不悔入IT 一.总结 一句话总结:看了大概200条评论,99%的不后悔,大部分人后悔没有早点干,但是做it最最主要的是要注意身体. 1.it是最 ...

  8. 来自知乎的Android学习总结

    转自知乎: http://www.zhihu.com/question/26417244 之前写了一篇博客 Android学习之路 被疯狂转发,带起了一阵学习Android的大风,我不晓得具体影响了多 ...

  9. 【转自知乎】哪些运动可以锻炼腰背部肌肉,进而避免腰痛?

    [转自知乎] http://www.zhihu.com/question/20777355 高科,跑酷私人教练 919 票,来自 Bruce Jia.iii屦及剑及.王恒 更多 腰痛一般就是指肋骨到髂 ...

  10. 苏格拉底:自知其无知

    众所周知,哲学是关于智慧的学问,然而,当一个哲学家说出最高的智慧就是"自知其无知"时,将会引起怎样的一种震动.但历史却往往上演如此的戏剧,这句"自知其无知"恰恰 ...

最新文章

  1. 信息系统分析与设计杨选辉_信息系统分析与设计(第2版)
  2. 里程碑Droid/Milestone/XT702官方正式2.2ROM刷机教程及刷机失败解决方法(含2.1底包)
  3. 几种常用通信协议:IIC协议、SPI协议、UART协议
  4. 梯度反传_反事实政策梯度解释
  5. nodejs基于art-template模板引擎生成
  6. 简易数字时钟软件详细制作过程
  7. 在ubuntu上搭建开发环境9---Ubuntu删除ibus出现的问题及解决
  8. 第三节:21个新的语义化标签,你撸过几个?
  9. python父类方法的装饰器_Python使用装饰器自动调用父类__init__
  10. ACM程序设计基础题解
  11. Java直连Access
  12. 新中大银色快车数据恢复
  13. LeetCode刷题指南与答案
  14. 激光打印机与计算机相连,Hp laserjet1010打印机怎么连接电脑使用?
  15. java-数字转换汉语中人民币的大写
  16. word 宏命令批量把当前文件夹下的doc另存为docx格式
  17. 程序员面试、算法研究、编程艺术、红黑树、数据挖掘5大经典原创系列集锦与总结
  18. 【微机原理】数字电路器件—门 与门 或门 非门电路及实例
  19. Servlet概念性回顾(结合Ajax)
  20. 培育企业安全基因 永信至诚召开2016年企业安全人才能力提升解决方案发布会...

热门文章

  1. 高性能 XC6SLX25T-2CSG324C(FPGA)现场可编程门阵列
  2. windows 11 访问带SMB的文件服务器(小米路由器)
  3. opencv——角点检测
  4. Ocelot一个优秀的.NET API网关框架
  5. 任务一:基于控制台的购书系统 java实验报告
  6. 《数字图像处理第二版》第一、二章部分习题
  7. 已知顺序表中元素值递增有序。 用算法实现将元素x查到表中适当的位置上,以保持顺序表的有序性。
  8. python把.CSV文件转换成.JSON格式文件并格式化储存
  9. php html5 框架,几个很好用的HTML5移动开发框架
  10. 华三MSR路由配置设备管理控制台DMC,实训室