C++ 的COM 接口

一. 什么是的COM接口

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

Illustration showing the interface boundary between an object and an application

在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用了后者, 如下图:Diagram that shows double pointer indirection

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:

FlagDescription
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 通过对象请求创建另一个接口指针

使用QueryInterface,每一个COM接口必须间接或直接继承自IUnknown接口:

Diagram that shows interfaces exposed by the Common Item Dialog object

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

Diagram that shows two interface pointers to interfaces on the same object

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.
}

 

六. Error Handling in COM

posted @ 2015-08-28 12:07  bruceyo  阅读(3326)  评论(0)    收藏  举报