首页 > 系统编程 > COM 学习笔记 (2) : 接口查询
2019
07-16

COM 学习笔记 (2) : 接口查询

IUnknown 接口概述

引述

客户对组件的了解是非常有限的,为知道某个组件是否支持某个特定的接口,客户可以在运行时询问组件。即使组件不支持所需要的某个接口,客户也可以在请求失败时很好地处理这种情形

IUnknown 接口

COM 中所有内容最终都起于接口、又最终归于接口。所有的 COM 接口都继承于 IUnknown 接口,该接口的定义如下:

// 该接口在头文件 unknwn.h 中定义,interface 是与 struct 绑定的宏,这样所有成员默认是 public 的,刚好符合接口的封装需求

interface IUnknown {
	virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0;
	virtual ULONG __stdcall AddRef() = 0;
	virtual ULONG __stdcall Release() = 0;
};

接口查询

因为 所有的 COM 接口都继承于 IUnknown 接口,所以客户只需要一个 IUnknown 接口的指针,而且无需知道它所拥有的接口指针到底是什么类型的,就可以查询其他接口

由于所有的 COM 接口都继承了 IUnknown,所以每个接口的虚函数表(vtbl)中的前三个函数都是 QueryInterface、AddRef 和 Release。这便使得所有 COM 接口都可当做 IUnknown 接口来处理

纯虚函数 QueryInterface

IUnknown 包含一个名为 QueryInterface 的纯虚成员函数,客户可以通过此函数来查询某个组件是否支持某个特定的接口。若支持,则 QueryInterface 将返回一个指向此接口的指针,否则返回值将是一个错误代码。然后客户可以接着查询其他接口或将组件卸载

  • QueryInterface 的函数原型
HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
  • 参数 iid

iid 是一个接口标识符(IID)结构, 用于标识客户所需的接口,从客户的角度上来说,指定一个 IID 以使得 QueryInterface 函数利用此接口标识符来查询接口

  • 参数 ppv

ppv 是一个二级指针,QueryInterface 函数将存放客户所请求的接口的地址到其中

  • 返回值(HRESULT 类型)

HRESULT 是具有特定格式的 32 位值,此函数返回 S_OK 或者 E_NOINTERFACE。对其返回值进行判断时应该使用 SUCCEEDED 宏或 FAILED 宏,不应该直接比较


// 使用示例
void foo(IUnknown *pI) {
    // 为接口定义一个指针
    IX *pIX = nullptr;
    
    // 使用 IUnknown 的 QueryInterface 函数来查询接口 IX(接口标识符是 IID_IX)
    HRESULT hr = pI->QueryInterface(IID_IX, (void **)&pIX);

    // 检查返回值
    if (SUCCEEDED(hr)) {
        // 可以使用接口了 (比如调用某个函数)
        pIX->Fx();
    }
} 

QueryInterface 的实现

QueryInterface 的实现其实比较简单。它只需根据某个给定的 IID(接口标识符),返回相应接口的指针。若组件支持,函数就返回 S_OK 以及相应的接口的指针,否则函数就应返回 E_NOINTERFACE,并将指针值置为 nullptr

  • 示例代码
// 一些示例接口、实现类
interface IX : IUnknown { /*...*/ };
interface IY : IUnknown { /*...*/ };
class CA : public IX, public IY { /*...*/ };

// QueryInterface 在 CA 类上的实现
HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv) {

    if (iid == IID_IUnknown) {
        // 客户需要 IUnknown 接口
        *ppv = static_cast<IX *>(this);

    } else if (iid == IID_IX) {
        // 客户需要 IX 接口
        *ppv = static_cast<IX *>(this);

    } else if (iid == IID_IY) {
        // 客户需要 IY 接口
        *ppv = static_cast<IY *>(this);

    } else {
        // 不支持的接口,返回 E_NOINTERFACE,并将返回指针置为 nullptr
        *ppv = nullptr;
        return E_NOINTERFACE;
    }
    
    // 增加引用计数(暂时先不关心)
    static_cast<IUnknown *>(*ppv)->AddRef();

    // 接口受支持,返回 S_OK
    return S_OK;
}

完整示例代码

// 本代码修改自《COM技术内幕》第 35 页

#include <iostream>
#include <objbase.h>

using namespace std;

void trace(const char* msg) { cout << msg << endl; }

interface IX : IUnknown {
    virtual void __stdcall Fx() = 0;
};

interface IY : IUnknown {
    virtual void __stdcall Fy() = 0;
};

interface IZ : IUnknown {
    virtual void __stdcall Fz() = 0;
};

extern const IID IID_IX;
extern const IID IID_IY;
extern const IID IID_IZ;

class CA : public IX, public IY {
    // IUnknown implementation
    virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
    virtual ULONG __stdcall AddRef() { return 0; }
    virtual ULONG __stdcall Release() { return 0; }

    // Interface IX implementation
    virtual void __stdcall Fx() { cout << "Fx" << endl; }
	
    // Interface IY implementation
    virtual void __stdcall Fy() { cout << "Fy" << endl; }
};

HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) {
	if (iid == IID_IUnknown) {
	    trace("QueryInterface: Return pointer to IUnknown");
	    *ppv = static_cast<IX *>(this);
	}
	else if (iid == IID_IX) {
	    trace("QueryInterface: Return pointer to IX");
	    *ppv = static_cast<IX *>(this);
	}
	else if (iid == IID_IY) {
	    trace("QueryInterface: Return pointer to IY");
	    *ppv = static_cast<IY *>(this);
	}
	else {
	    trace("QueryInterface: Interface not supported.");
	    *ppv = NULL;
	    return E_NOINTERFACE;
	}

	reinterpret_cast<IUnknown *>(*ppv)->AddRef();
	return S_OK;
}

IUnknown* CreateInstance() {
	IUnknown* pI = static_cast<IX*>(new CA);
	pI->AddRef();
	return pI;
}

static const IID IID_IX = 
{ 0x32bb8320, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82} };

static const IID IID_IY =
{ 0x32bb8321, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82} };

static const IID IID_IZ =
{ 0x32bb8322, 0xb41b, 0x11cf, {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82} };

int main() {
    HRESULT hr;
    trace("Client: Get an IUnknown pointer.");
    IUnknown* pIUnknown = CreateInstance();

    trace("Client: Get interface IX.");
    IX *pIX = NULL;
    hr = pIUnknown->QueryInterface(IID_IX, (void**)& pIX);
    if (SUCCEEDED(hr)) {
        trace("Client: Succeeded getting IX.");
        pIX->Fx();
    }

    trace("Client: Get interface IY.");
    IY *pIY = NULL;
    hr = pIUnknown->QueryInterface(IID_IY, (void**)& pIY);
    if (SUCCEEDED(hr)) {
        trace("Client: Succeeded getting IY.");
        pIY->Fy();
    }

    trace("Client: Ask for an unsupported interface.");
    IZ *pIZ = NULL;
    hr = pIUnknown->QueryInterface(IID_IZ, (void**)& pIZ);
    if (SUCCEEDED(hr)) {
        trace("Client: Succeeded getting IZ.");
        pIZ->Fz();
    }
    else {
        trace("Client: Could not get interface IZ.");
    }

    trace("Client: Get interface IY from interface IX.");
    IY *pIYfromIX = NULL;
    hr = pIX->QueryInterface(IID_IY, (void**)& pIYfromIX);

    if (SUCCEEDED(hr)) {
        trace("Client: Succeeded getting IY.");
        pIYfromIX->Fy();
    }

    trace("Client: Get interface IUnknown from IY.");
    IUnknown *pIUnknownFromIY = NULL;
    hr = pIX->QueryInterface(IID_IUnknown, (void**)& pIUnknownFromIY);
    if (SUCCEEDED(hr)) {
        cout << "Are the IUnknown pointers equal?    ";
        if (pIUnknownFromIY == pIUnknown) {
	    cout << "Yes, pIUnknownFromIY == pIUnknown." << endl;
        }
        else {
            cout << "No, pIUnknownFromIY != pIUnknown." << endl;
        }
    }

    delete pIUnknown;
    return 0;
}

若运行将有如下输出结果

Client: Get an IUnknown pointer.
Client: Get interface IX.
QueryInterface: Return pointer to IX
Client: Succeeded getting IX.
Fx
Client: Get interface IY.
QueryInterface: Return pointer to IY
Client: Succeeded getting IY.
Fy
Client: Ask for an unsupported interface.
QueryInterface: Interface not supported.
Client: Could not get interface IZ.
Client: Get interface IY from interface IX.
QueryInterface: Return pointer to IY
Client: Succeeded getting IY.
Fy
Client: Get interface IUnknown from IY.
QueryInterface: Return pointer to IUnknown
Are the IUnknown pointers equal?    Yes, pIUnknownFromIY == pIUnknown.

参考资料

[1] (美)Dale Rogerson , 李忠 , 王晓波 . COM 技术内幕 – 微软组件对象模型 [M] . 北京 : 清华大学出版社 , 1991.


打赏 赞(1)
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏

最后编辑:
作者:Yenyu
Yenyu
编程爱好者

留下一个回复

你的email不会被公开。