【发布时间】:2017-07-21 01:26:40
【问题描述】:
我有一个关于虚拟表的问题。
据我所知,虚拟表是可以找到的函数地址数组 多态对象调用虚函数时的函数地址。
但在directx中,有人提到dx vtable,即d3d9.dll的表 的导入函数地址数组。
为什么他们将导入函数地址数组调用为directx vtable? 它似乎与 vtable 无关。
我的知识有误吗?我可以详细了解 vtable 吗? 谢谢!
【问题讨论】:
我有一个关于虚拟表的问题。
据我所知,虚拟表是可以找到的函数地址数组 多态对象调用虚函数时的函数地址。
但在directx中,有人提到dx vtable,即d3d9.dll的表 的导入函数地址数组。
为什么他们将导入函数地址数组调用为directx vtable? 它似乎与 vtable 无关。
我的知识有误吗?我可以详细了解 vtable 吗? 谢谢!
【问题讨论】:
从 DLL 导出 COM 对象时,通常有两个部分在起作用。首先是“工厂”。工厂可以是从 DLL 导出的标准 C 可调用函数(Direct3D 就是这种情况),也可以是在系统注册表中注册的类,在这种情况下,您将使用CoCreateInstance 创建 COM 接口实例。第一个示例是创建 Direct3D 设备:
ID3D11Device* d3dDevice = nullptr;
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
0,
0,
nullptr,
0,
D3D11_SDK_VERSION,
&d3dDevice,
nullptr,
nullptr);
在这个调用之后,d3dDevice 接口指向一个分配的 COM 接口对象。包含 D3D11CreateDevice 的 DLL 必须隐式链接到调用程序 - 或者您可以使用 LoadLibrary 进行显式链接,在这种情况下,您将使用指向函数 D3D11CreateDevice 的指针,其数量大致相同事物 。这源自 DLL 的“导入表”。
第二个例子是您在使用 Windows 映像组件 (WIC) 时所做的:
IWICImagingFactory* factory = nullptr;
hr = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IWICImagingFactory),
&factory);
这让 COM 系统在注册表中查找类 GUID,加载引用的 DLL,然后调用其中的工厂方法来创建它返回给您的 COM 接口对象。
在这两种情况下,接口所指向的实际事物都是以下形式:
typedef struct IUnknownVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
IUnknown * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
IUnknown * This);
ULONG ( STDMETHODCALLTYPE *Release )(
IUnknown * This);
END_INTERFACE
} IUnknownVtbl;
被设计为完全映射到virtual的Visual C++实现:
MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
END_INTERFACE
};
COM 并没有真正的继承概念,但 ID3D11Device1 使用来自 ID3D1Device 的 C++ 公共继承很方便,因为该语言类型没有声明数据成员。接口的 COM“继承”实际上只是方法的串联,如果您查看标头中的 C 定义,您可以看到这些方法:
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("cc86fabe-da55-401d-85e7-e3c9de2877e9")
ID3D11BlendState1 : public ID3D11BlendState
{
public:
virtual void STDMETHODCALLTYPE GetDesc1(
/* [annotation] */
_Out_ D3D11_BLEND_DESC1 *pDesc) = 0;
};
#else /* C style interface */
typedef struct ID3D11BlendState1Vtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )( /* ... */ );
ULONG ( STDMETHODCALLTYPE *AddRef )( ID3D11BlendState1 * This);
ULONG ( STDMETHODCALLTYPE *Release )( ID3D11BlendState1 * This);
void ( STDMETHODCALLTYPE *GetDevice )( /* ... */ );
HRESULT ( STDMETHODCALLTYPE *GetPrivateData )( /* ... */ );
HRESULT ( STDMETHODCALLTYPE *SetPrivateData )( /* ... */ );
HRESULT ( STDMETHODCALLTYPE *SetPrivateDataInterface )( /* ... */ );
void ( STDMETHODCALLTYPE *GetDesc )(
ID3D11BlendState1 * This,
/* [annotation] */
_Out_ D3D11_BLEND_DESC *pDesc);
void ( STDMETHODCALLTYPE *GetDesc1 )(
ID3D11BlendState1 * This,
/* [annotation] */
_Out_ D3D11_BLEND_DESC1 *pDesc);
END_INTERFACE
} ID3D11BlendState1Vtbl;
#endif
重要的是要注意这仅适用于纯虚拟(即抽象)C++ 类的情况,没有数据成员,只有使用公共继承(即接口)的虚拟方法。构造函数和析构函数被忽略,因为 COM 生命周期是通过
IUnknown的引用计数来管理的。
方法的调用签名还将指向 COM 对象的指针作为第一个参数,该参数映射到 this 的 C++ 调用约定。
COM 接口因此被设计为像 C++ 虚拟方法一样工作,因此您可以使用 C++ 语法来调用它们,但它们根本不一定是 C++ 类对象。
从IUnknown,COM 中有一个已知的标准方法来获取特定接口,即QueryInterface。 Direct3D 工厂函数也会为您处理这个问题,并且只返回基本接口。对于 Direct3D 11,这是一个 ID3D11Device。
如果你想获得一个说 11.1 的接口,那么你可以在
ID3D11Device上使用QueryInterface来请求ID3D11Device1。它在 DirectX 11.0 系统上会失败,但在 DirectX 11.1 或更高版本的系统上工作。
对于使用 Direct3D 的 C++ 程序员,该设计有意将“COM”保持在最低限度(俗称“COM lite”)。确实使用了足够多的 COM,以便更容易处理随时间发生的接口更改并提供合理的 ABI。 Direct3D 工厂函数是来自已知 DLL 的简单 C 可调用导出,因此您甚至不必为标准 COM 工厂而烦恼,事实上,如果您尝试使用 CoCreateInstance,则 API 无法正常工作。从技术上讲,您可以通过标准 MIDL compiler 与 C++ 定义一起生成的一些宏来使用 C,但这样做有点困难,而且目前还没有经过很好的测试。
作为 Direct3D COM 的使用者,您真正需要知道的只是 IUnknown 引用计数和查询的基础知识——今天最好使用 Microsoft::WRL::ComPtr 智能指针来完成——以及如何正确检查 @ 987654349@ 值--参见ThrowIfFailed。
见The Component Object Model和Reference Counting (Direct3D 10)
【讨论】: