你好,祝你好运。
情况:
出于某种原因,当我需要覆盖一个或两个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 ++源代码(宏/模板)解决,也没有代码生成器(除非代码生成器使用非常简单/优雅 - 即编写代码生成器不行,使用已经可用的代码生成器,我可以在几分钟内设置并解决在一行代码中的整个事情是好的)。只有在不添加额外的DLL依赖项时,Boost才可以。特定于MS的编译器指令和语言扩展也可以。
想法?提前谢谢。
答案 0 :(得分:1)
好的,因为我不喜欢未回答的问题......
要实现“COM实现继承”,目前还没有用纯C ++编写的理智且紧凑的解决方案。这主要是因为在C ++中禁止创建抽象类的实例或直接操作虚方法表。因此,有两种常用的解决方案:
#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位)。此外,它是不安全的(如调度表黑客)。
与调度表相比,您可以使用自定义类并在界面中存储其他数据。但是: