作者:Froser 链接:https://www.zhihu.com/question/49433640/answer/116028598 来源:知乎 著作权归作者所有。
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](https://my.oschina.net/u/1052931) 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>(this); return S_OK; } else if (ISEQUAL_IID(riid, IID_IUnknown)) { _ppvObject = static_cast>(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之类的,但是基本的思想就是上面我说的这样了。__