组件对象模型 1:概述
组件对象模型(Component Object Model)是微软的组件技术,能够创建二进制可复用的程序。其问题来源于代码的复用机制,最早的复用当然是源代码级别的复用,这种方法的问题在于:
必须公开源代码。
代码会在所有复用的地方占据内存,假如在100个进程复用,就要重复占用100次。
后来出现了动态链接库技术,解决了公开代码和重复内存占用的问题,但是DLL仍然不是一个“可复用组件”,因为:
由于C++标准没有规定ABI,因此不同的编译器编译出来的C++程序必然不兼容,甚至同一厂商编译器的不同版本都不能兼容。
即使C++编译器兼容了,DLL依然需要一个C++编译器才能使用,如果是VB就不能执行。
不同C++编译器在C部分的ABI必然是互相兼容的,导致C++ ABI不兼容的主要原因是虚函数vtbl的处理不一致。既然如此,可以利用抽象类创建接口,宿主程序只负责调用抽象虚函数,具体执行交给DLL。
将胶水代码的调用规则内置在系统中,并创建一个中心化的服务进程(scm),组件登记到注册表,并记录其调用方法。
1 COM标准
COM组件是由一组接口(C++抽象类,指明调用规范)和一组实现类组成的,其基础接口是IUnknown,实例如下:
可以看到:
任何接口都必须继承自IUnknwon。
所有的接口调用传参规则都是__stdcall,并返回一个用于表示操作状态的HRESULT。
接口所有的函数都必须是虚函数。
COM组件必须手动管理内存,AddRef函数用于引用自增1,返回新的引用数,引用数应该是一个32位无符号整数。
Release函数用于引用自减1,返回新的引用数。
QueryInterface函数用于将IUnknwon变成需要的接口,第一个参数是接口ID,第二个参数是所需接口的指针。由于查询必然伴随一个新的引用,因此在此处需要调用AddRef。
2 编程实例
2.1 定义接口和实现类ID
下面以最简单的动态链接库COM组件为例进行说明。接口和实现类是根据一个128位UUID标记的,分别改名为IID和CLSID。首先定义接口和实现类的ID:
可以到Windows SDK的安装目录找到创建IID和CLSID的工具guidgen.exe。
2.2 定义接口和实现类
定义接口和实现类:
2.3 定义类工厂
注意,由于年代问题,类工厂在官方文档中被称为“类对象”(ClassObject),非常迷惑。之所以创建类工厂是因为Windows并不知道怎么创建实现类的对象,因此需要提供一个IClassFactory的实现:
2.4 导出系统调用函数
一个COM组件至少需要以C语言命名模式导出3个函数:
DllCanUnloadNow
DllMain
DllGetClassObject
这三个函数都不需要用户手动调用,而是由系统在创建组件实例的时候调用的,其作用分别为:
(1)应用程序结束时判断是否能够卸载DLL。
(2)加载DLL时的事件处理函数。
(3)创建类工厂。
下面仅描述DllGetClassObject,其函数签名为:
一个实例实现如下:
可以看到其作用就是创建一个类工厂,然后调用其QueryInterface。不难猜到系统在这里调用QueryInterface的目的是获取一个IClassFactory指针。另外两个函数不需做其它工作:
为了导出函数还必须创建定义文件,内容如下:
2.5 导入注册表
至此,系统仍然不知道到哪里寻找这个组件。需以管理员权限到注册表的
HKEY_CLASSES_ROOT\CLSID
下面建立一个名为{CLSID}的项,并创建一个名为InProcServer32的子项。InProcServer32的默认值就是DLL文件的路径,如下图所示:

3 客户端调用
客户端实例代码如下:
main.h
main.cpp