简体   繁体   English

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

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

Hello and good day to you.你好,祝你有美好的一天。

Situation:情况:
For some reason, from time to time I run into situation when I need to override one or two methods of a COM interface (that is being used for some older application without source code), which is normally Direct3D/DirectInput related (ie it is created by calling a DLL method, not by CoCreateInstance).出于某种原因,有时我会遇到需要覆盖 COM 接口(用于一些没有源代码的旧应用程序)的一种或两种方法的情况,这通常与 Direct3D/DirectInput 相关(即它是通过调用 DLL 方法创建,而不是由 CoCreateInstance 创建)。 Normally I deal with situation by writing a proxy DLL that overrides a method that creates interface I need to "modify", and replace original interface with my own.通常我通过编写代理 DLL 来处理这种情况,该代理覆盖创建我需要“修改”的接口的方法,并用我自己的替换原始接口。 Normally this is required to make some older application work properly without crashing/artifacts.通常,这是使一些较旧的应用程序正常工作而不会崩溃/伪影所必需的。

Compiler:编译器:
I use Visual Studio express 2008 on windows machine, so there are no C++0x features.我在 windows 机器上使用 Visual Studio express 2008,所以没有 C++0x 功能。 The system has msysgit, msys, python, perl, gnu utilities (awk/sed/wc/bash/etc), gnu make and qmake (Qt-4.7.1) installed (and available within PATH).该系统安装了 msysgit、msys、python、perl、gnu 实用程序(awk/sed/wc/bash/etc)、gnu make 和 qmake(Qt-4.7.1)(在 PATH 中可用)。

Problem:问题:
Overriding one method of a COM interface is a pain (especially if original interface has a hundred of methods or so), because I need to forward many calls to original interface, and currently I see no way to simplify or automate the process.覆盖 COM 接口的一个方法是一种痛苦(特别是如果原始接口有大约一百个方法),因为我需要将许多调用转发到原始接口,目前我看不到简化或自动化该过程的方法。 For example, override of IDirect3D9 looks like this:例如,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;
        }
    }
};

As you can see, this is very inefficient, error-prone and requires a lot of copy-pasting.正如你所看到的,这是非常低效的,容易出错并且需要大量的复制粘贴。

Question:问题:
How can I simplify overriding of a single method of a COM interface in this situation?在这种情况下,如何简化 COM 接口的单一方法的覆盖? I would like to specify only method I change, but I currently see no way to do so.我只想指定我更改的方法,但我目前看不到这样做的方法。 I also don't see a way to elegantly shorten "forwarded" methods with macros or templates or macros, because they have variable number of arguments.我也看不到使用宏或模板或宏优雅地缩短“转发”方法的方法,因为它们具有可变数量的 arguments。 Another approach I saw is to use directly patch method table returned by another method (modify access right using VirtualProtect, then write into method table), which I don't exactly like.我看到的另一种方法是直接使用另一个方法返回的补丁方法表(使用VirtualProtect修改访问权限,然后写入方法表),我不太喜欢。

Limitations:限制:
I would prefer to solve in C++ source code (macros/templates) and without code generators (unless code generator usage is extremely simple/elegant - ie writing code generator is not ok, using already available code generator I can set up in minutes and solve the whole thing in one line of code is ok).我更愿意在 C++ 源代码(宏/模板)中解决并且没有代码生成器(除非代码生成器的使用非常简单/优雅 - 即编写代码生成器是不行的,使用已经可用的代码生成器我可以在几分钟内设置并解决一行代码中的整个事情都可以)。 Boost is okay only if it doesn't add extra DLL dependency.仅当不添加额外的 DLL 依赖项时,提升才可以。 MS-specific compiler directives and language extensions are also ok.特定于 MS 的编译器指令和语言扩展也可以。

Ideas?想法? Thanks in advance.提前致谢。

Okay, since I don't like unanswered questions...好吧,因为我不喜欢没有答案的问题...

To implement "COM implementation inheritance" there's currently no sane and compact solution written in pure C++.为了实现“COM 实现继承”,目前还没有用纯 C++ 编写的合理而紧凑的解决方案。 This is mostly because in C++ it is forbidden to create an instance of abstract class or manipulate virtual method table directly.这主要是因为在 C++ 中禁止创建抽象 class 的实例或直接操作虚拟方法表。 As a result, there are 2 commonly used solutions:因此,有两种常用的解决方案:

  1. Write method forwarding for every method manually.手动为每个方法编写方法转发。
  2. Hack dispatch table.破解调度表。

Advantage of #1 is that this approach is safe and you can store additional data within custom class. #1 的优点是这种方法是安全的,您可以在自定义 class 中存储其他数据。 Disadvantage of #1 is that writing a wrapper for every single method is extremely tedious procedure. #1 的缺点是为每个方法编写一个包装器是非常繁琐的过程。

Advantage of #2 is that this approach is compact. #2 的优点是这种方法很紧凑。 You replace single method.您替换单个方法。 Disadvantage of #2 is that dispatch table might be located in write-protected space (most likely it wouldn't happen, but it could happen in theory) and you can't store custom data in hacked interface. #2 的缺点是调度表可能位于写保护空间中(很可能不会发生,但理论上可能会发生),并且您无法将自定义数据存储在被黑客入侵的界面中。 As a result, although it is simple/short, it is quite limiting.结果,虽然它很简单/很短,但它的局限性很大。

And there's a 3rd approach .还有第三种方法 (which nobody has suggested for some reason ) 由于某种原因没有人建议)

Short description: instead of using virtual method table provided by C++, write non-virtual class that will emulate virtual method table.简短描述:不要使用 C++ 提供的虚拟方法表,而是编写将模拟虚拟方法表的非虚拟 class。

Example:例子:

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
    }
};

To swap it with real interface, you'll have to use reinterpret_cast .要将其与真实界面交换,您必须使用reinterpret_cast

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

As you can see this method requires assembly, macros, templates combined together with casting class method pointer to void *.如您所见,此方法需要程序集、宏、模板以及将 class 方法指针转换为 void *。 Also it is compiler-dependent(msvc, although you should be able to do same trick with g++) and architecture-dependent (32/64-bit).它也是编译器相关的(msvc,虽然你应该能够用 g++ 做同样的技巧)和架构相关的(32/64 位)。 Plus it is unsafe (as with dispatch table hacking).此外,它是不安全的(与调度表黑客攻击一样)。

The advantage compared to dispatch tables you can use custom class and store additional data within interface.与调度表相比,您可以使用自定义 class 并在接口内存储其他数据的优势。 However:然而:

  1. All virtual methods are forbidden.禁止所有虚方法。 (as far as I know, any attempt to use virtual method will instantly insert invisible 4-bytes pointer at the beginning of the class, which will break everything). (据我所知,任何使用虚方法的尝试都会立即在 class 的开头插入不可见的 4 字节指针,这会破坏一切)。
  2. Calling convention must be stdcall (should work with cdecl, though, but for everything else you'll need different wrapper)调用约定必须是 stdcall (虽然应该与 cdecl 一起使用,但对于其他所有内容,您将需要不同的包装器)
  3. You have to initialize entire vtable yourself (very error-prone).您必须自己初始化整个 vtable(非常容易出错)。 One mistake, and everything will crash.一个错误,一切都会崩溃。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM