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

发布于 2024-11-30 06:02:52 字数 2808 浏览 1 评论 0原文

你好,祝你有美好的一天。

情况:
由于某种原因,有时我会遇到需要重写 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++ 源代码(宏/模板)中解决并且不使用代码生成器(除非代码生成器的使用非常简单/优雅 - 即编写代码生成器是不行的,使用已经可用的代码生成器我可以在几分钟内设置并解决一行代码中的所有事情都可以)。仅当 Boost 不添加额外的 DLL 依赖项时才可以。 MS 特定的编译器指令和语言扩展也可以。

有想法吗?提前致谢。

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 (i.e. it is created by calling a DLL method, not by 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. 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. 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).

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. For example, override of IDirect3D9 looks like this:

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? 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. 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.

Limitations:
I would prefer to solve in C++ source code (macros/templates) and without code generators (unless code generator usage is extremely simple/elegant - i.e. 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). Boost is okay only if it doesn't add extra DLL dependency. MS-specific compiler directives and language extensions are also ok.

Ideas? Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一个人的夜不怕黑 2024-12-07 06:02:52

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

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

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

#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 (非常容易出错)。一个错误,一切都会崩溃。

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++. This is mostly because in C++ it is forbidden to create an instance of abstract class or manipulate virtual method table directly. 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.
Disadvantage of #1 is that writing a wrapper for every single method is extremely tedious procedure.

Advantage of #2 is that this approach is compact. 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. 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.

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.

        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*. Also it is compiler-dependent(msvc, although you should be able to do same trick with g++) and architecture-dependent (32/64-bit). 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. 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).
  2. Calling convention must be stdcall (should work with cdecl, though, but for everything else you'll need different wrapper)
  3. You have to initialize entire vtable yourself (very error-prone). One mistake, and everything will crash.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文