代理dll:方法覆盖/方法转发(COM实现继承)
C++/COM/Proxy Dlls: method override/method forwarding (COM implementation inheritance)
你好,祝你一天好。
:
出于某种原因,我不时遇到的情况,当我需要覆盖一个或两个方法的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++中禁止创建抽象类的实例或直接操作虚方法表。因此,有两种常用的解决方案:
- 手动为每个方法写方法转发。
- 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位)。此外,它是不安全的(与调度表黑客一样)。
与调度表相比,优点是可以使用自定义类并在接口中存储额外的数据。然而:
- 禁止所有虚方法。(据我所知,任何使用虚方法的尝试都会立即在类的开头插入不可见的4字节指针,这会破坏一切)。
- 调用约定必须是stdcall(虽然应该与cdecl一起工作,但对于其他所有内容,您将需要不同的包装器) 你必须自己初始化整个虚函数表(非常容易出错)。一个错误,一切都会崩溃。
- 使用非递归插入方法实现 AVL 树
- C++方法实现:是否可以避免每次都键入类名?
- 如何使用吸气剂方法实现C++封装
- 在C++中使用克隆方法实现多晶型效果
- 使用本机 JNI 静态方法实现C++ Java 运行时错误
- 双向链表插入方法实现 - 正在搜索哪个节点
- 没有虚拟调度的默认方法实现
- C 构造函数和方法实现
- C :如何使用相同的数据类使用不同的方法实现
- 将同一类的方法实现放在不同的对象文件中
- 私有方法实现编译错误
- 使用公共命名方法实现非公共赋值运算符
- 使用 SFINAE 在 Clase 模板中选择不同的方法实现
- 如何根据方法实现的声明顺序对其进行排序
- 使用一个方法实现不同类型赋值的优雅方式,并且没有警告
- 在运行时选择方法实现
- 将参数传递给c++ CORBA方法实现
- 静态constexpr方法实现导致GCC错误
- 包括c++中的头文件(类定义和方法实现)
- 如何在swig生成的代理类中更改特定的方法实现