C ++ / COM / Proxy Dlls:方法覆盖/方法转发(COM实现继承)

时间:2011-08-17 18:37:53

标签: c++ windows com interface

你好,祝你好运。

情况:
出于某种原因,当我需要覆盖一个或两个COM接口的方法(用于一些没有源代码的旧应用程序)时,我会不时遇到这种情况,这通常与Direct3D / DirectInput相关(即它是通过调用DLL方法创建,而不是通过CoCreateInstance创建。通常我通过编写一个代理DLL来处理情况,该代理DLL会覆盖创建我需要“修改”的接口的方法,并用我自己的接口替换原始接口。通常,这需要使一些旧的应用程序正常工作而不会崩溃/伪影。

编译器:
我在Windows机器上使用Visual Studio express 2008,因此没有C ++ 0x功能。系统安装了msysgit,msys,python,perl,gnu实用程序(awk / sed / wc / bash / etc),gnu make和qmake(Qt-4.7.1)(在PATH中可用)。

问题:
覆盖COM接口的一个方法很痛苦(特别是如果原始接口有大量的方法),因为我需要将许多调用转发到原始接口,目前我认为无法简化或自动化该过程。例如,IDirect3D9的覆盖如下所示:

class MyD3D9: public IDirect3D9{
protected:
    volatile LONG refCount;
    IDirect3D9 *orig;
public:
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj){
        if (!ppvObj)
            return E_INVALIDARG;
        *ppvObj = NULL;
        if (riid == IID_IUnknown  || riid == IID_IDirect3D9){
            *ppvObj = (LPVOID)this;
            AddRef();
            return NOERROR;
        }
        return E_NOINTERFACE;
    }

    STDMETHOD_(ULONG,AddRef)(THIS){
        InterlockedIncrement(&refCount);
        return refCount;
    }
    STDMETHOD_(ULONG,Release)(THIS){
        ULONG ref = InterlockedDecrement(&refCount);
        if (refCount == 0)
            delete this;
        return ref;
    }

    /*** IDirect3D9 methods ***/
    STDMETHOD(RegisterSoftwareDevice)(THIS_ void* pInitializeFunction){
        if (!orig)
            return E_FAIL;
        return orig->RegisterSoftwareDevice(pInitializeFunction);
    }

    STDMETHOD_(UINT, GetAdapterCount)(THIS){
        if (!orig)
            return 0;
        return orig->GetAdapterCount();
    }

    STDMETHOD(GetAdapterIdentifier)(THIS_ UINT Adapter,DWORD Flags, D3DADAPTER_IDENTIFIER9* pIdentifier){
        if (!orig)
            return E_FAIL;
        return orig->GetAdapterIdentifier(Adapter, Flags, pIdentifier);
    }

    STDMETHOD_(UINT, GetAdapterModeCount)(THIS_ UINT Adapter,D3DFORMAT Format){
        if (!orig)
            return 0;
        return orig->GetAdapterModeCount(Adapter, Format);
    }
/* some code skipped*/

    MyD3D9(IDirect3D9* origD3D9)
        :refCount(1), orig(origD3D9){
    }

    ~MyD3D9(){
        if (orig){
            orig->Release();
            orig = 0;
        }
    }
};

正如您所看到的,这是非常低效,容易出错并且需要大量复制粘贴。

问题:
在这种情况下,如何简化COM接口的单个​​方法的覆盖?我只想指定我改变的方法,但我目前看不到这样做。我也没有看到用宏或模板或宏优雅地缩短“转发”方法的方法,因为它们具有可变数量的参数。我看到的另一种方法是直接使用另一种方法返回的补丁方法表(使用VirtualProtect修改访问权限,然后写入方法表),我不太喜欢。

限制:
我宁愿用C ++源代码(宏/模板)解决,也没有代码生成器(除非代码生成器使用非常简单/优雅 - 即编写代码生成器不行,使用已经可用的代码生成器,我可以在几分钟内设置并解决在一行代码中的整个事情是好的)。只有在不添加额外的DLL依赖项时,Boost才可以。特定于MS的编译器指令和语言扩展也可以。

想法?提前谢谢。

1 个答案:

答案 0 :(得分:1)

好的,因为我不喜欢未回答的问题......

要实现“COM实现继承”,目前还没有用纯C ++编写的理智且紧凑的解决方案。这主要是因为在C ++中禁止创建抽象类的实例或直接操作虚方法表。因此,有两种常用的解决方案:

  1. 手动为每种方法编写方法转发。
  2. 黑客调度表。
  3. #1的优点是这种方法是安全的,您可以在自定义类中存储其他数据。 #1的缺点是为每个方法编写一个包装器是非常繁琐的过程。

    #2的优点是这种方法很紧凑。您可以替换单个方法。 #2的缺点是调度表可能位于写保护空间(很可能不会发生,但理论上可能会发生)并且您无法在黑客接口中存储自定义数据。因此,虽然简单/短暂,但它是非常有限的。

    还有一个第三种方法。 (由于某种原因没有人建议

    简短描述:不使用C ++提供的虚方法表,而是编写将模拟虚方法表的非虚拟类。

    示例:

    template<typename T1, typename T2> void unsafeCast(T1 &dst, const T2 &src){
        int i[sizeof(dst) == sizeof(src)? 1: -1] = {0};
        union{
            T2 src;
            T1 dst;
        }u;
        u.src = src;
        dst = u.dst;
    }
    
    template<int Index> void __declspec(naked) vtblMapper(){
    #define pointerSize 4 //adjust for 64bit
        static const int methodOffset = sizeof(void*)*Index;
        __asm{
            mov eax, [esp + pointerSize]
            mov eax, [eax + pointerSize]
            mov [esp + pointerSize], eax
            mov eax, [eax]
            add eax, methodOffset
            mov eax, [eax]
            jmp eax
        };
    #undef pointerSize
    }
    
    struct MyD3DIndexBuffer9{
    protected:
        VtblMethod* vtbl;
        IDirect3DIndexBuffer9* orig;
        volatile LONG refCount;
        enum{vtblSize = 14};
        DWORD flags;
        bool dynamic, writeonly;
    public:
        inline IDirect3DIndexBuffer9*getOriginalPtr(){
            return orig;
        }
    
        HRESULT __declspec(nothrow) __stdcall QueryInterface(REFIID riid, LPVOID * ppvObj){
            if (!ppvObj)
                return E_INVALIDARG;
            *ppvObj = NULL;
            if (riid == IID_IUnknown  || riid == IID_IDirect3DIndexBuffer9){
                *ppvObj = (LPVOID)this;
                AddRef();
                return NOERROR;
            }
            return E_NOINTERFACE;
        }
    
        ULONG __declspec(nothrow) __stdcall AddRef(){
            InterlockedIncrement(&refCount);
            return refCount;
        }
    
        ULONG __declspec(nothrow) __stdcall Release(){
            ULONG ref = InterlockedDecrement(&refCount);
            if (refCount == 0)
                delete this;
            return ref;
        }
    
        MyD3DIndexBuffer9(IDirect3DIndexBuffer9* origIb, DWORD flags_)
                :vtbl(0), orig(origIb), refCount(1), flags(flags_), dynamic(false), writeonly(false){
            dynamic = (flags & D3DUSAGE_DYNAMIC) != 0;
            writeonly = (flags & D3DUSAGE_WRITEONLY) != 0;
            vtbl = new VtblMethod[vtblSize];
            initVtbl();
        }
    
        HRESULT __declspec(nothrow) __stdcall Lock(UINT OffsetToLock, UINT SizeToLock, void** ppbData, DWORD Flags){
            if (!orig)
                return E_FAIL;
            return orig->Lock(OffsetToLock, SizeToLock, ppbData, Flags);
        }
    
        ~MyD3DIndexBuffer9(){
            if (orig){
                orig->Release();
                orig = 0;
            }
            delete[] vtbl;
        }
    private:
        void initVtbl(){
            int index = 0;
            for (int i = 0; i < vtblSize; i++)
                vtbl[i] = 0;
    
    #define defaultInit(i) vtbl[i] = &vtblMapper<(i)>; index++
            //STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE;
            unsafeCast(vtbl[0], &MyD3DIndexBuffer9::QueryInterface); index++;
            //STDMETHOD_(ULONG,AddRef)(THIS) PURE;
            unsafeCast(vtbl[1], &MyD3DIndexBuffer9::AddRef); index++;
            //STDMETHOD_(ULONG,Release)(THIS) PURE;
            unsafeCast(vtbl[2], &MyD3DIndexBuffer9::Release); index++;
    
            // IDirect3DResource9 methods 
            //STDMETHOD(GetDevice)(THIS_ IDirect3DDevice9** ppDevice) PURE;
            defaultInit(3);
            //STDMETHOD(SetPrivateData)(THIS_ REFGUID refguid,CONST void* pData,DWORD SizeOfData,DWORD Flags) PURE;
            defaultInit(4);
            //STDMETHOD(GetPrivateData)(THIS_ REFGUID refguid,void* pData,DWORD* pSizeOfData) PURE;
            defaultInit(5);
            //STDMETHOD(FreePrivateData)(THIS_ REFGUID refguid) PURE;
            defaultInit(6);
            //STDMETHOD_(DWORD, SetPriority)(THIS_ DWORD PriorityNew) PURE;
            defaultInit(7);
            //STDMETHOD_(DWORD, GetPriority)(THIS) PURE;
            defaultInit(8);
            //STDMETHOD_(void, PreLoad)(THIS) PURE;
            defaultInit(9);
            //STDMETHOD_(D3DRESOURCETYPE, GetType)(THIS) PURE;
            defaultInit(10);
            //STDMETHOD(Lock)(THIS_ UINT OffsetToLock,UINT SizeToLock,void** ppbData,DWORD Flags) PURE;
            //defaultInit(11);
            unsafeCast(vtbl[11], &MyD3DIndexBuffer9::Lock); index++;
            //STDMETHOD(Unlock)(THIS) PURE;
            defaultInit(12);
            //STDMETHOD(GetDesc)(THIS_ D3DINDEXBUFFER_DESC *pDesc) PURE;
            defaultInit(13);
    #undef defaultInit
        }
    };
    

    要将其与真实界面交换,您必须使用reinterpret_cast

            MyD3DIndexBuffer9* myIb = reinterpret_cast<MyD3DIndexBuffer9*>(pIndexData);
    

    正如您所看到的,此方法需要将程序集,宏,模板组合在一起将类方法指针指向void *。它还依赖于编译器(msvc,尽管你应该能够用g ++做同样的技巧)和依赖于体系结构(32/64位)。此外,它是不安全的(如调度表黑客)。

    与调度表相比,您可以使用自定义类并在界面中存储其他数据。但是:

    1. 禁止使用所有虚拟方法。 (据我所知,任何使用虚方法的尝试都会立即在类的开头插入不可见的4字节指针,这会破坏一切)。
    2. 调用约定必须是stdcall(应该使用cdecl,但是对于其他一切你需要不同的包装器)
    3. 你必须自己初始化整个vtable(非常容易出错)。一个错误,一切都会崩溃。