《COM本质论》读书笔记

前言

虽然一直都不喜欢Windows的应用开发,不过由于现在工作需要,必须对 Win32 开发非常熟悉。 Windows 上的 C++ 开发,逃不过对组建对象模型COM(Component Object Model)编程的学习和理解,尤其是游戏、音视频领域 —— Direct3D/Direct2D/DirectShow/DirectSound 等等。COM已经是上个世纪的产物了,所以相关的学习资料也都很老了,不过《COM本质论》是一本不错的入门书。在此我做做笔记加深理解。

 

第1章:COM是一个更好的C++

"静态库",不同程序有相同的代码片段 FastString.obj。

“动态库”,不同程序可以指向相同的代码片段,减少内存空间。

 

C++的编译模型,它不能够支持独立的二进制组建的设计。C++的编译模型要求客户必须知道对象的布局结构,从而导致了客户和对象可执行代码之间的二进制耦合关系。
 
封装(encapsulation)的概念是把“一个对象的外观(接口)同其实际工作方式(实现)分离开来”为基础。C++的问题在于这条原则并没有被应用到二进制层次上,因为C++类既是接口也是实现。
 

第2章:接口

COM 提供了 接口定义语言(Interface Definition Language, IDL),它只用到了基本的、大家都很熟悉的 C 语法,同时加入了某些用来 ”消除 C 语言中二义性特征“的能力。
COM IDL 基于 Open Software Foundation 的 Distributed Computing Environment Remote Procedure Call (分布式 RPC 远程调用)。
IDL 语言中,定义COM方法,都需要指明caller或callee将写入或者读出每一个方法参数,通过使用参数属性[in]和[out]来完成:
void Method1([in] long arg1,
             [out] long *parg2,
             [in,out] long parg3);

 

 
 
几乎素有的COM方法都会返回一个HRESULT类型的错误号码,HRESULT是32位整数。
00~15 bit: 信息码
16~28 bit: 操作码
29~30 bit: 保留
31 bit: 严重程度位
 
SDK 头文件定义了两个宏可以简化 HRESULT 有关的代码:
 
#define SUCCEEDED(hr) (long(hr) >= 0)
#define FAILED(hr) (long(hr) < 0)

 

 
COM 接口都被分配一个二进制名字:全局唯一标识符 GUID,是 128 bit 的,GUID 基于 UUID。
IID: 接口 ID (Interface ID)
CLSID: 类 ID (class ID)
 
接口的IDL定义如下:
[attribute1, attribute2, ...]
interface IThisInterface: IBaseInterface {
    typedef1;
    typedef2;
    ...
    method1;
    method2;
    ...
}

 

下面是一个Interface的例子(一个计算器的例子):

[object, uuid(BDA4A270-A1BA-11d0-8C2C-0080C73925BA)]
interface ICalculator : IBaseInterface {
HRESULT Clear(void);
HRESULT Add([in] long n);
HRESULT Sum([out, retval] long *pn);
}

其中, [object] 和 [uuid] 都是 attribute,并且每个COM接口都必须要有 [object] 属性,说明该接口定义是一个 COM 接口。

下面是 COM 所有 interface 的基类 —— IUnknown 的 C++ 定义(其中 interface 就是 struct):

extern "C" const IId IID_IUnknown;
interface IUnknown {
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) = 0;
    virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
    virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
};

 

 

IDL语言中,为了从一个接口派生另一个接口,我们要么在同一个文件中定义基接口,要么使用 import 指示符:

// calculator.idl
[object, uuid(BDA4A270-A1BA-11d0-8C2C-0080C73925BA)]
interface ICalculator : IUnknown {
    import "unknwn.idl"; // 引入 IUnknown 的定义
    HRESULT Clear(void);
    HRESULT Add([in] long n);
    HRESULT Sum([out, retval] long *pn);
}

 

另外,COM不支持多继承语法。

 

COM 的 IUnknown接口的 “运行时类型发现”就是 QueryInterface 方法,它的 IDL 描述:

HRESULT QueryInterface([in] REFIID riid,
[out] void **ppv);

第一个参数 riid 是被请求的接口的实质名字,第二个参数 ppv 是指向第一个接口指针变量。

 

C++ 程序员必须显示地使用 IUnknown 的方法,因为 COM 所对应的 C++ 语言映射并没有在客户代码和对象代码之间提供一个运行时层(runtime layer)。不过这样运行效率更高。

 

 

第3章:类

三个不同的概念: interface, implementation, class

· interface: 与对象通信的抽象协议

· implementation: 支持一个或多个接口的具体数据类型

· class: 被命名的 implementation,代表了具体的、可实例化的类型,

 

 

COM 有三种激活方式:
CoGetClassObject : 绑定到类对象的引用;
CoCreateInstanceEx :绑定到指向一个新的类实例的引用
CoGetInstanceFromFile: 绑定到指向文件中永久实例的引用。
 
这三种激活模型都用到了 COM服务控制管理器(SCM)。
 
名字对象(moniker),引出了 IMoniker 接口的普通COM对象,IMoniker接口是最复杂的COM接口之一,它有一个很重要的方法 BindToObject :
interface IMoniker : IPersistStream {
    HRESULT BindToObject([in] IBindCtx *pbc,
[in, unique] IMoniker *pmkToLeft,
[in] REFIID riid,
[out, iid_is(riid)] void **ppv); }

 

 
 

第4章:对象

 
 
 
 
 
 
 
(未完待续)
 
 

 

posted @ 2016-02-06 18:37  R.H.Zhang  阅读(2108)  评论(0编辑  收藏  举报