ATL学习笔记(3): QueryInterface功能的实现

在CComObjectRootEx类中,实现了线程安全的引用计数管理。而在CComObjectRootEx的父类CComObjectRootBase中,存在对QueryInterface的一个内部实现——InternalQueryface()。

1. CComObjectRootBase类

class CComObjectRootBase
{
public:
    ......
    static HRESULT InternalQueryInterface(
        void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObj)
    { return hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObj);  }
    ......
};

以上代码作了一些简化,去掉了调试用的条件编译选项。在代码中,InternalQueryInterface作为静态方法存在,并且仅仅将执行过程转给了AtlInternalQueryInterface()。其方法参数与标准的QueryInterface方法相比也有不同,其中也有一个新的结构类型_ATL_INTMAP_ENTRY,并且InternalQueryInterface和AtlInternalQueryInterface函数的参数一一对应。

2. _ATL_INTMAP_ENTRY结构和AtlInternalQueryInterface()函数

在atlbase.h中,_ATL_INTMAP_ENTRY结构类型有以下声明:
struct _ATL_INTMAP_ENTRY
{
    const IID * piid;
    DWORD_PTR dw;
    _ATL_CREATORARGFUNC* pFunc;
};
其中piid表示接口的ID,其他的成员变量说明现在暂时无从知晓,只知_ATL_CREATORARGFUNC在atlbase.h中的声明是:
typedef HRESULT (WINAPI _ATL_CREATORARGFUNC) (
    void* pv, REFIID riid, LPVOID* ppv, DWORD_PTR dw);
同样,这个函数类型在MSDN中也没有找到说明。

跳过这些暂时无法弄清的声明,看看AtlInternalQueryInterface()函数的实现:
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
    if (InlineIsEqualUnknown(iid))
    {
        IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
        pUnk->AddRef();
        *ppvObject = pUnk;
        return S_OK;
    }
    while (pEntries->pFunc != NULL)
    {
        BOOL bBlind = (pEntries->piid == NULL);
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
        {
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) 
            {
                IUnknown* pUnk = (IUnknown*)((INT_PTR)pThis+pEntries->dw);
                pUnk->AddRef();
                *ppvObject = pUnk;
                return S_OK;
            } else {
                HRESULT hRes = pEntries->pFunc(pThis,
                    iid, ppvObject, pEntries->dw);
                if (hRes == S_OK || (!bBlind && FAILED(hRes)))
                    return hRes;
            }
        }
            pEntries++;
    }
    return E_NOINTERFACE;
}
参考MSDN,得知:
    pThis - 指向包含有COM接口映射表的对象的指针;
    pEntries - 一个_ATL_INTMAP_ENTRY结构类型的数组,该数组中包含有效的接口映射;
    iid - 请求的接口的GUID;
    ppvObj - 用以输出指向iid表示的接口的指针。
参照AtlInternalQueryInterface的实现,如果参数中的iid与IUnknown接口的iid相同(InlineIsEqualUnknown(iid))),将直接输出对象指针加上_ATL_INTMAP_ENTRY结构数组的第一个元素的dw成员的偏移。可见,在请求IUnknown时,函数返回的时pEntries数组中第一个元素表示的接口的IUnknown实现(因为每个COM接口都从IUnknown实现,在多重继承的前提下,函数只返回第一个接口的IUnknown)。

如果请求的接口不是IUnknown,则AtlInternalQueryInterface将继续搜索pEntries数组,直到存在一个元素,并且该元素的pFunc指针是NULL。可见,pEntries数组应该包含pThis所指向的COM对象所实现的所有接口,即所谓的COM对象接口映射表。并且,该数组中最后一个元素不表示任何接口且其pFunc成员应为NULL。

同时还可知,如果接口映射表元素的pFunc成员值为_ATL_SIMPLEMAPENTRY,dw成员则表示所对应的接口指针相对于pThis对象指针的偏移。否则pFunc指向一个自定义的接口指针计算函数。

3. 结论

根据上述分析,与AddRef和Release相似,ATL也没有直接实现IUnknown的QueryInterface方法,而同样是在CComObjectBase类中先作一个内部实现,该实现随着CComObjectRootEx被继承到每个COM对象中。

ATL对于QueryInterface的实现采用的是表驱动的方式(MFC也常用到表驱动方式,似乎是Microsoft钟情于表驱动这个方式,也可能这种方式的确在性能上有过人之处),因此每个ATL  COM对象中必须首先存在一个包含其所有实现接口的接口映射表。

我想,下面应该去看看《接口映射表是怎样建成的》了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值