COM其实是一个接口贩子,所有COM对象必须提供IUnknown接口,客户端可以通过这个接口来查询到这个COM对象提供的其他接口,以便贩卖接口。查看Using and Implementing IUnknown

这个接口有一个特别的要求,那就是每次调用必须返回相同的接口指针。并且,通过IUnknown可以查询到的接口集合必须是固定不变的。对于IUnknown的QueryInterface,只要是COM对象所能支持的接口,在其任意的接口上调用QueryInterface,都必须能够返回所求接口的指针。也就是说查询接口的时候,不能一会儿成功,一会儿失败;也不能通过一个接口指针查询成功,通过另外一个接口指针查询失败。

一个不稳的接口称为unstably implemented

根据Implementing Reference Counting,IUnknown的AddRef和Release用来管理接口的生命周期。COM对象必须在QueryInterface返回指针的时候就把引用技术加一,然后客户端调用AddRef或Release来增减引用计数,当引用计数为零的时候释放COM对象。因为每个接口都是从IUnknown派生出来的,所有客户在接口指针上进行AddRef或者Release操作,针对的只是那个接口指针。虽然从实现上一个COM对象可能会对所有的接口采用一个计数器。

如何在COM客户端维护引用计数的准确性是一个麻烦事,如果管理接口指针的所有权和生命周期,可以参考 Rules for Managing Reference Counts

需要注意的是AddRef以及Release的返回值有时候不确定,最好只用来做调试时候的信息输出。

Reusing Objects

COM里面使用两种种方式来重用既有COM对象:containment/delegation,aggregation。containment/delegation有点类似面向对象中的组合。通过拥有其他对象的接口,一个COM对象可以再制定出其他接口,让后把接口请求转给所拥有的其他对象的接口,来实现服务。

Aggregation可以看成是一种特化的containment/delegation。在Aggregation的情况下,外部的COM直接把内部COM对象的接口暴露给客户端。但是这个不能直接通过暴露内部COM对象的IUnknown给客户端,因为内部COM对象的IUnknown可能包含了外部COM对象所没有实现的接口。所以外部COM对象还是要自己实现一套IUnknown接口。

实现Aggregable对象的一些注意事项:

  • The aggregable (or inner) object’s implementation of QueryInterface, AddRef, and Release for its IUnknown interface controls the inner object’s reference count, and this implementation must not delegate to the outer object’s unknown (the controlling IUnknown).
  • The aggregable (or inner) object’s implementation of QueryInterface, AddRef, and Release for its other interfaces must delegate to the controlling IUnknown and must not directly affect the inner object’s reference count.
  • The inner IUnknown must implement QueryInterface only for the inner object.
  • The aggregable object must not call AddRef when holding a reference to the controlling IUnknown pointer.
  • When the object is created, if any interface other than IUnknown is requested, the creation must fail with E_NOINTERFACE.

The COM Library

任何支持COM的程序需要用到The COM Library,并且需要在合适的时间点初始化以及清除它。COM Library包含一些DLL和EXE(主要是Ole32.dll和Rpcss.exe)。

COM Library主要提供以下服务:

  • A small number of fundamental functions that facilitate the creation of COM applications, both client and server. For clients, COM supplies basic functions for creating objects. For servers, COM supplies the means of exposing their objects.

  • Implementation-locator services through which COM determines, from a unique class identifier (CLSID), which server implements that class and where that server is located. This service includes support for a level of indirection, usually a system registry, between the identity of an object class and the packaging of the implementation so that clients are independent of the packaging, which can change in the future.

  • Transparent remote procedure calls when an object is running in a local or remote server.

  • A standard mechanism to allow an application to control how memory is allocated within its process, particularly memory that needs to be passed between cooperating objects so that it can be freed properly.

使用COM Library的话,必须调用 CoInitialize或者CoInitializeEx来初始化。值得注意的是,使用内存分配相关的调用不需要先初始化。

对于OLE compound document应用,需要调用OleInitialize 来初始化,这个函数最终调用的也是 CoInitializeEx。值得注意的是不能再自由线程上调用OleInitialize .

如果是对进程内提供服务,则不需要调用这些函数,因为当这些服务入驻进程的时候已经执行过初始化操作了。对内服务端必须通过设置 InprocServer32来注明自己的线程模型。

CoUninitialize和 OleUninitialize是相应的清理函数。

Managing Memory Allocation

COM可以支持不同的编程语言。但是这些不同的语言有不同的内存管理机制。为了统一起见,COM实现了一套通用的线程安全的内存管理机制。通过这个机制分配的内存可以再接口上传递。

CoGetMalloc返回提供COM内存分配器接口 IMalloc。但是你可不必直接使用IMalloc,而是通过封装 函数CoTaskMemAlloc, CoTaskMemRealloc, 以及CoTaskMemFree来管理内存。

再接口传递内存,有额外的规则需要遵守:

  • In-parameters must be allocated and freed by the caller.
  • Out-parameters must be allocated by the one called; they are freed by the caller using the standard COM task memory allocator. See The OLE Memory Allocator for more information.
  • In/out-parameters are initially allocated by the caller, and then freed and reallocated by the one called, if necessary. As is true for out parameters, the caller is responsible for freeing the final returned value. The standard COM memory allocator must be used.

对于Out或者In-Out参数传递的内存,在分配失败的情况下,有额外的规则需要遵守:

  • In case of an error condition, parameters must always be reliably set to a value that will be cleaned up without any action by the caller.
  • All out pointer parameters must explicitly be set to NULL. These are usually passed in a pointer-to-pointer parameter but can also be passed as members of a structure that the caller allocates and the called code fills. The most straightforward way to ensure this is (in part) to set these values to NULL on function entry. This rule is important because it promotes more robust application interoperability.
  • Under error conditions, all in-out parameters must either be left alone by the code called (thus remaining at the value to which they were initialized by the caller) or be explicitly set, as in the out parameter error return case.

对于调试的话,IMallocSpy接口可以提供一些帮助。IMallocSpy提供"pre"和“post”两个钩子,可以在内存分配前后被调用。

(本篇完)