Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Okay, since I don't like unanswered questions... </p> <p>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: </p> <ol> <li>Write method forwarding for every method manually. </li> <li>Hack dispatch table. </li> </ol> <p>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.</p> <p>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.</p> <p>And there's a <strong>3rd approach</strong>. (which nobody has suggested <em>for some reason</em>)</p> <p>Short description: instead of using virtual method table provided by C++, write non-virtual class that will emulate virtual method table. </p> <p>Example:</p> <pre><code>template&lt;typename T1, typename T2&gt; void unsafeCast(T1 &amp;dst, const T2 &amp;src){ int i[sizeof(dst) == sizeof(src)? 1: -1] = {0}; union{ T2 src; T1 dst; }u; u.src = src; dst = u.dst; } template&lt;int Index&gt; 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(&amp;refCount); return refCount; } ULONG __declspec(nothrow) __stdcall Release(){ ULONG ref = InterlockedDecrement(&amp;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 &amp; D3DUSAGE_DYNAMIC) != 0; writeonly = (flags &amp; 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-&gt;Lock(OffsetToLock, SizeToLock, ppbData, Flags); } ~MyD3DIndexBuffer9(){ if (orig){ orig-&gt;Release(); orig = 0; } delete[] vtbl; } private: void initVtbl(){ int index = 0; for (int i = 0; i &lt; vtblSize; i++) vtbl[i] = 0; #define defaultInit(i) vtbl[i] = &amp;vtblMapper&lt;(i)&gt;; index++ //STDMETHOD(QueryInterface)(THIS_ REFIID riid, void** ppvObj) PURE; unsafeCast(vtbl[0], &amp;MyD3DIndexBuffer9::QueryInterface); index++; //STDMETHOD_(ULONG,AddRef)(THIS) PURE; unsafeCast(vtbl[1], &amp;MyD3DIndexBuffer9::AddRef); index++; //STDMETHOD_(ULONG,Release)(THIS) PURE; unsafeCast(vtbl[2], &amp;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], &amp;MyD3DIndexBuffer9::Lock); index++; //STDMETHOD(Unlock)(THIS) PURE; defaultInit(12); //STDMETHOD(GetDesc)(THIS_ D3DINDEXBUFFER_DESC *pDesc) PURE; defaultInit(13); #undef defaultInit } }; </code></pre> <p>To swap it with real interface, you'll have to use <code>reinterpret_cast</code>.</p> <pre><code> MyD3DIndexBuffer9* myIb = reinterpret_cast&lt;MyD3DIndexBuffer9*&gt;(pIndexData); </code></pre> <p>As you can see this method requires <em>assembly, macros, templates combined together</em> with <em>casting class method pointer to void</em>*. 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).</p> <p>The advantage compared to dispatch tables you can use custom class and store additional data within interface. However: </p> <ol> <li>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). </li> <li>Calling convention must be stdcall (should work with cdecl, though, but for everything else you'll need different wrapper)</li> <li>You have to initialize entire vtable yourself (very error-prone). One mistake, and everything will crash. </li> </ol>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload