什么是直接 X 虚拟表?

What is a Direct X Virtual Table?

本文关键字:虚拟 什么      更新时间:2023-10-16

我有一个关于虚拟表的问题。

我知道的虚拟表是可以找到的函数地址数组 多态对象调用虚函数时的函数地址。

但是在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的视觉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 编译器生成的一些宏以及C++定义来使用 C,但这有点挑战性,而且现在没有经过特别好的测试。

作为 Direct3D COM 的使用者,你需要了解的只是IUnknown引用计数和查询的基础知识(目前最好使用 Microsoft::WRL::ComPtr 智能指针来完成(,以及如何正确检查HRESULT值 - 请参阅 ThrowIfFailed。

请参阅组件对象模型和引用计数 (Direct3D 10(