[Windows翻译]COM对象的布局

278 阅读2分钟

原文地址:devblogs.microsoft.com/oldnewthing…

原文作者:devblogs.microsoft.com/oldnewthing…

发布时间:2004年2月5日

Win32 COM调用惯例指定了对象的虚拟方法表(vtable)的布局。如果一种语言/编译器想支持COM,它必须以指定的方式布局其对象,以便其他组件可以使用它。

Win32 COM对象布局与C++对象布局密切吻合,这并非巧合。尽管COM最初开发的时候,C语言是最主要的编程语言,但设计者认为应该与新兴的新语言C++"友好相处"。

COM对象的布局在各种接口的头文件中都有明确的规定。例如,这里是objidl.h中的IPersist,在清理了一些宏之后。

typedef struct IPersistVtbl
{
    HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
        IPersist * This,
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ void **ppvObject);
    ULONG ( STDMETHODCALLTYPE *AddRef )(
        IPersist * This);
    ULONG ( STDMETHODCALLTYPE *Release )(
        IPersist * This);
    HRESULT ( STDMETHODCALLTYPE *GetClassID )(
        IPersist * This,
        /* [out] */ CLSID *pClassID);
} IPersistVtbl;
struct IPersist
{
    const struct IPersistVtbl *lpVtbl;
};

这对应于下面的内存布局。

p ->lpVtbl ->QueryInterface
/AddRef
/Release
/GetClassID

这是什么意思呢?

COM接口指针是指向一个结构的指针,这个结构只由一个vtable组成。vtable是一个包含一堆函数指针的结构。列表中的每个函数都以那个接口指针(p)作为它的第一个参数("this")。

这一切的神奇之处在于,由于你的函数得到了p作为它的第一个参数,你可以把额外的东西 "挂 "到vtable上。

p ->lpVtbl ->QueryInterface
/...AddRef
/other stuffRelease
/...GetClassID

vtable中的函数可以使用相对于接口指针的偏移来访问它的其他东西。

如果一个对象实现了多个接口,但它们都是彼此的后代,那么一个vtable就可以用于所有的接口。例如,上面的对象已经被设置为可以作为IUnknown或者IPersist使用,因为IUnknown是IPersist的一个子集。

另一方面,如果一个对象实现了多个接口,而这些接口又不是彼此的子集,那么就会得到多重继承,在这种情况下,对象在内存中的布局通常是这样的。

p ->lpVtbl->QueryInterface (1)
q ->lpVtbl ->QueryInterface (2)AddRef (1)
/AddRef (2)Release (1)
/other stuffRelease (2)
/

如果你使用的是来自第一个v表的接口,那么接口指针是p,但如果你使用的是来自第二个v表的接口,那么接口指针是q。

挂好这张图,因为明天我们将学习那些神秘的 "调整器thunks"。


www.deepl.com 翻译