首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C++/COM/Proxy Dlls:方法覆盖/方法转发(COM实现继承)

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

Stack Overflow用户
提问于 2011-08-17 18:37:52
回答 1查看 1K关注 0票数 2

你好,祝你愉快。

形势:

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

编译器:

我在windows机器上使用2008,所以没有C++0x特性。系统安装了msysgit、msys、python、perl、gnu实用程序(awk/sed/wc/bash/etc)、gnu make和qmake (Qt-4.7.1) (并在PATH中可用)。

问题:

重写COM接口的一种方法是很困难的(特别是如果原始接口有大约100种方法),因为我需要将许多调用转发到原始接口,而且目前我看不到简化或自动化流程的方法。例如,IDirect3D9的覆盖如下所示:

代码语言:javascript
运行
复制
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的编译器指令和语言扩展也是可以的。

想法?提前谢谢。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2011-10-03 01:19:11

好吧,既然我不喜欢没有答案的问题..。

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

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

#1的优点是这种方法是安全的,并且可以在自定义类中存储其他数据。第一种方法的缺点是为每个方法编写一个包装器是非常繁琐的过程。

第二种方法的优点是这种方法是紧凑的。你代替了单一的方法。#2的缺点是调度表可能位于受写保护的空间(很可能不会发生,但理论上可能会发生),并且无法将自定义数据存储在受黑客攻击的接口中。因此,虽然它很简单/很短,但它是相当有限的。

还有一个第三次逼近。(由于某些原因,没有人建议)

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

示例:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
        MyD3DIndexBuffer9* myIb = reinterpret_cast<MyD3DIndexBuffer9*>(pIndexData);

如您所见,此方法需要程序集、宏、模板和类型转换方法指针组合到void*。此外,它还依赖于编译器(msvc,尽管您应该能够对g++做同样的操作)和依赖于体系结构(32/64位)。此外,它是不安全的(与调度表黑客一样)。

与调度表相比,它的优点是可以使用自定义类并在接口中存储其他数据。然而:

  1. 所有虚拟方法都是被禁止的。(据我所知,任何使用虚拟方法的尝试都会立即在类的开头插入不可见的4字节指针,这将破坏一切)。
  2. 调用约定必须是stdcall (虽然应该与cdecl一起工作,但对于其他所有需要不同包装器的东西)
  3. 必须自己初始化整个vtable (非常容易出错)。一个错误,一切都会崩溃。--
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7097740

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档