代理dll:方法覆盖/方法转发(COM实现继承)

C++/COM/Proxy Dlls: method override/method forwarding (COM implementation inheritance)

本文关键字:方法 实现 COM 继承 dll 覆盖 代理 转发      更新时间:2023-10-16

你好,祝你一天好。

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

编译器:
我在windows机器上使用Visual Studio express 2008,所以没有c++ 0x的特性。系统安装了msysgit, msys, python, perl, gnu实用程序(awk/sed/wc/bash/等),gnu make和qmake (Qt-4.7.1)(可在PATH中使用)。

问题:
重写COM接口的一个方法是一件痛苦的事情(特别是如果原始接口有100个左右的方法),因为我需要将许多调用转发到原始接口,而且目前我看不到简化或自动化该过程的方法。例如,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++源代码(宏/模板)解决问题,而不使用代码生成器(除非代码生成器的使用非常简单/优雅-即编写代码生成器是不行的,使用已经可用的代码生成器,我可以在几分钟内设置好,并在一行代码中解决整个问题)。Boost只有在不增加额外的DLL依赖时才可以。特定于ms的编译器指令和语言扩展也可以。

想法?

好吧,既然我不喜欢悬而未决的问题…

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

  1. 手动为每个方法写方法转发。
  2. Hack调度表。

第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. 你必须自己初始化整个虚函数表(非常容易出错)。一个错误,一切都会崩溃。