【问题标题】:What is a Direct X Virtual Table?什么是 Direct X 虚拟表?
【发布时间】:2017-07-21 01:26:40
【问题描述】:

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

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

但在directx中,有人提到dx vtable,即d3d9.dll的表 的导入函数地址数组。

为什么他们将导入函数地址数组调用为directx vtable? 它似乎与 vtable 无关。

我的知识有误吗?我可以详细了解 vtable 吗? 谢谢!

【问题讨论】:

    标签: c++ directx


    【解决方案1】:

    从 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 ModelReference Counting (Direct3D 10)

    【讨论】:

    • 多么详细的答案!我只想补充说明 COM 和 c++ 对象完全不同。 d3d12 接口不会覆盖 d3d9 接口。没有继承。两者都有自己的“vtable”功能,但它们之间没有关系,没有共同的祖先。您不能将 d312 对象投射到 d3d9。
    • 好点。我更新以涵盖这一点...它们实际上并不相似,但 COM 接口设计旨在使依赖 C++ 调用语法使它们看起来成为纯虚拟类公共继承。
    猜你喜欢
    • 2013-04-08
    • 2020-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    相关资源
    最近更新 更多