我对虚拟桌有疑问。
我知道的虚拟表是可以找到的函数地址数组 当多态对象调用虚函数时的函数地址。
但是在directx中,有人提到dx vtable表d3d9.dll 导入函数地址数组。
为什么他们将导入函数地址数组调用为directx vtable? 它似乎与vtable无关。
我的知识不正确吗?我可以详细了解vtable吗? 谢谢!
答案 0 :(得分:4)
从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
进行显式链接,在这种情况下,您使用指向函数{{1这相当于一件事。这来自“导入表”。对于DLL。
第二个示例是使用Windows Imaging Component(WIC)时所执行的操作:
D3D11CreateDevice
这让COM系统在注册表中查找类GUID,加载引用的DLL,然后在那里调用工厂方法来创建一个它返回给你的COM接口对象。
在这两种情况下,界面指向的实际内容都是以下形式:
IWICImagingFactory* factory = nullptr;
hr = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IWICImagingFactory),
&factory);
设计为完全映射到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;
的Visual C ++实现:
virtual
COM确实没有继承的概念,但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
};
使用来自ID3D11Device1
的C ++公共继承也很方便,因为语言类型没有数据成员声明。 COM"继承"接口实际上只是方法的串联,如果你查看标题中的C定义,你可以看到这些方法:
ID3D1Device
值得注意的是,这仅适用于纯虚拟(即抽象)C ++类,没有数据成员,只有虚拟方法使用公共继承(即接口)。由于COM生命期通过
#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
引用计数来管理,因此忽略构造函数和析构函数。
方法的调用签名还将指向COM对象的指针作为映射到IUnknown
的C ++调用约定的第一个参数。
COM接口因此设计为像C ++虚拟方法一样工作,因此您可以使用C ++语法来调用它们,但它们根本不是C ++类对象。
从this
开始,COM中有一种已知的标准方法来获取特定接口,即IUnknown
。 Direct3D工厂功能也为您完成这项工作,并返回基本接口。对于Direct3D 11,这是QueryInterface
。
如果您想要一个说11.1的界面,那么您可以在
ID3D11Device
上使用QueryInterface
,要求ID3D11Device
。它在DirectX 11.0系统上会失败,但在DirectX 11.1或更高版本的系统上运行。
对于使用Direct3D的C ++程序员,该设计故意保留" COM"至少(通俗地称为" COM lite")。真正足够的COM用于使随着时间的推移更容易处理接口更改并提供合理的ABI。 Direct3D工厂函数是来自已知DLL的简单C可调用导出,因此您甚至不必使用标准COM工厂进行调试,事实上,如果您尝试使用API,API不能正常工作ID3D11Device1
。从技术上讲,您可以通过标准MIDL compiler生成的一些宏以及C ++定义来使用C,但这样做有点难度并且目前还没有经过特别好的测试。
作为Direct3D COM的消费者,您真正需要知道的是CoCreateInstance
引用计数和查询的基础知识 - 今天最好使用Microsoft::WRL::ComPtr智能指针 - 以及如何要正确检查IUnknown
值,请参阅ThrowIfFailed。
请参阅The Component Object Model和Reference Counting (Direct3D 10)