Хорошо, так как я не люблю вопросы без ответов ...
Для реализации «наследования реализации COM» в настоящее время не существует разумного и компактного решения, написанного на чистом C ++. Это происходит главным образом потому, что в C ++ запрещено создавать экземпляр абстрактного класса или напрямую манипулировать таблицей виртуальных методов. В результате есть 2 часто используемых решения:
- Переадресация метода для каждого метода вручную
- Таблица отправки взлома.
Преимущество # 1 в том, что этот подход безопасен, и вы можете хранить дополнительные данные в пользовательском классе.
Недостаток # 1 заключается в том, что написание оболочки для каждого отдельного метода является чрезвычайно утомительной процедурой.
Преимущество # 2 в том, что этот подход компактен. Вы заменяете единственный метод.
Недостаток # 2 в том, что таблица диспетчеризации может быть расположена в защищенном от записи пространстве (скорее всего, этого не произойдет, но теоретически это может произойти), и вы не можете хранить пользовательские данные в взломанном интерфейсе. В результате, хотя это просто / коротко, это довольно ограничивающее.
И есть 3-й подход . (который никто не предложил по какой-то причине )
Краткое описание: вместо использования таблицы виртуальных методов, предоставляемой C ++, напишите не виртуальный класс, который будет эмулировать таблицу виртуальных методов.
* * 1 022 Пример: * 1 023 *
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, но для всего остального вам понадобится другая оболочка)
- Вы должны инициализировать весь vtable самостоятельно (очень подвержен ошибкам). Одна ошибка, и все рухнет.