C++ 的COM 接口
一. 什么是的COM接口
在C#或者Java中,接口定义了一系列对象能够支持的方法,并且是没有任何实现的。可以说接口把调用方法的代码和实现方法的代码很清晰的分开了。这样的代码会是低耦合的(decoupled) 。

在C++里面,跟接口最接近的定义是纯虚类,就是一个类里面除了纯虚函数没有其他的成员。举一个接口的例子:
// The following is not actual COM. interface IDrawable { void Draw(); };
这个例子是说有些图像对象是drawable的。 IDrawable接口定义了所有能够绘画的对象所支持的操作(接口名一般以“I” 开头)。结构是抽象的,是不能被实例化的。但是可以通过对象来实现接口。下面的例子是一些类以继承一个普通抽象基类的方式实现接口:
class Shape : public IDrawable { public: virtual void Draw(); // Override Draw and provide implementation. }; class Bitmap : public IDrawable { public: virtual void Draw(); // Override Draw and provide implementation. };
Shape:: Draw 跟 Bitmap::Draw 的实现方式可能是大不相同的。在程序中对Shape或Bitmap对象的操作可以不使用Shape或Bitmap的指针,而是直接通过使用IDrawable 指针:
IDrawable *pDrawable = CreateTriangleShape(); if (pDrawable) { pDrawable->Draw(); }
下面的代码轮回一个IDrawable指针数组。这个数组可以是任何顺序的shape,bitmap或者其他图形对象,但是每一个图形对象都继承了IDrawable结构:
void DrawSomeShapes(IDrawable **drawableArray, size_t count) { for (size_t i = 0; i < count; i++) { drawableArray[i]->Draw(); } }
COM的关键就是调用代码永远不会知道接口的派生类的类型。更进一步说,就是在上面的例子中,你不用实例化Shape或Bitmap, 而是通过IDrawable指针来对Shape或Bitmap操作。这样COM就确保了接口和实现之间的严格区分。实现的细节可以很方便的改变而不影响调用的代码(Calling Code)。
在C++的实现中,接口通常用类或结构来声明。用C++的IDL来定义COM接口,然后由COM编译器来产生C++的头文件。COM是对象用来实现的一些方法的集合。多个对象可以实现同一个接口,同样,一个对象可以实现多个接口。看下面ISerializable接口的例子:
// An interface for serialization. class ISerializable { public: virtual void Load(PCWSTR filename) = 0; // Load from file. virtual void Save(PCWSTR filename) = 0; // Save to file. }; // Declarations of drawable object types. class Shape : public IDrawable { ... }; class Bitmap : public IDrawable, public ISerializable { ... };
二. COM库的初始化
所有使用COM的Windows程序都要通过调用CoInitializeEx方法来对COM初始化。每个线程都要分开调用这个方法来初始化COM。下面是CoInitializeEx的格式:
HRESULT CoInitializeEx(LPVOID pvReserved, DWORD dwCoInit);
第一个是预留参数,必须是NULL,第二个是交代程序的线程模式。COM支持2种不同的线程模式,apartment和multithreaded, see Processes, Threads, and Apartments and Understanding and Using COM Threading Models.。参数dwCoInit可以设为下表里的flag值:
| Flag | Description |
| COINIT_APARTMENTTHREADED | Apartment threaded. |
| COINIT_MULTITHREADED | Multithreaded. |
| COINIT_DISABLE_OLE1DDE | an obsolete technology |
在线程退出之前,要调用CoUninitialize函数来结束初始化:
CoUninitialize();
三. 创建COM对象
要使用COM接口,程序需要首先需要实例化一个实现COM接口的对象。一般来说,有两种方法来来创建COM对象:1,对象提供一个函数来实例化对象;2,用COM的CoCreateInstance函数。
1, 在一. 什么是的COM接口中的Shape对象可能暴露一个像下面模式的函数:
HRESULT CreateShape(IDrawable** ppShape);
**代表指针的指针,用这个方法可以创建一个新的对象:
IDrawable *pShape; HRESULT hr = CreateShape(&pShape); if (SUCCEEDED(hr)) { // Use the Shape object. } else { // An error occurred. }
因为CreateShape函数已经返回了一个error/success的值,所以这里的IDrawable指针只能通过函数的参数来返回(指针实现参数返回值的能力)。这个传入的IDrawable指针在函数里将会被一个新的IDrawable指针覆盖。C++中有两种方法来覆盖参数值,传引用和传地址。COM用了后者, 如下图:
CreateShape函数用pShape指针的地址来给pShape副一个新的指针。
2,CoCreateInstance来创建指针对象:
因为一个接口可以被多个对象实现,一个对象也可以实现多个接口,所以用这种普遍方法创建指针对象需要两方面的信息:1,创建哪个对象;2,对象的哪个接口。
在COM里面,每一个对象或接口都有一个128位长的数字唯一标识符Globally Unique Identifier (GUID)。有时候GUIDs也被成为Universally Unique Identifiers (UUIDs)。比如Shapes库可能声明2个GUID常量:
extern const GUID CLSID_Shape; extern const GUID IID_IDrawable;
根据命名传统,CLSID是class identifier, IID是interface identifier。有了这两个值,就可以通过如下方法来创建接口指针对象了:
IDrawable *pShape; hr = CoCreateInstance(CLSID_Shape,
NULL,
CLSCTX_INPROC_SERVER,
IID_Drawable, reinterpret_cast<void**>(&pShape));
if (SUCCEEDED(hr)) { // Use the Shape object. } else { // An error occurred. }
CoCreateInstance函数有5个参数,第一和第五个分别是类标识符和接口标识符。实际上,这些参数告诉函数创建一个Shape对象,并且返回给我一个指向IDrawable接口的指针。
第三个参数的值可以为下表中的flags:
| Flag | Description |
|---|---|
| CLSCTX_INPROC_SERVER | Same process. |
| CLSCTX_LOCAL_SERVER | Different process, same computer. |
| CLSCTX_REMOTE_SERVER | Different computer. |
| CLSCTX_ALL | Use the most efficient option that the object supports. (The ranking, from most efficient to least efficient, is: in-process, out-of-process, and cross-computer.) |
第五个参数是为了返回一个接口指针。
四. COM对象的生命周期
所有的COM接口都继承自IUnknown基类。 IUnknown接口定义了三个方法:
- QueryInterface --> Asking an Object for an Interface
- AddRef --> 控制生命周期reference count: 该对象有多少引用
- Release --> 控制生命周期reference count为0,对象自我删除
五. QueryInterface 通过对象请求创建另一个接口指针
使用QueryInterface,每一个COM接口必须间接或直接继承自IUnknown接口:

现在的问题是如何通过IFileOpenDialog接口来得到指向IFileDialogCustomize的接口指针:?

QueryInterface的语法:
HRESULT QueryInterface(REFIID riid, void **ppvObject);
REFIID 是 const GUID&, 即对GUID的常引用(这样不能通过该引用来改变原GUID的值,确保安全性)。riid就是要查找的接口,即IFileDialogCustomize。下面代码是实例:返回的查找结果就在pCustom里。
hr = pFileOpen->QueryInterface(IID_IFileDialogCustomize, reinterpret_cast<void**>(&pCustom)); if (SUCCEEDED(hr)) { // Use the interface. (Not shown.) // ... pCustom->Release(); } else { // Handle the error. }

浙公网安备 33010602011771号