Вы не можете безопасно копировать записи vtable; поскольку указатель this
будет по-прежнему ссылаться на ваш прокси-класс, реальная реализация не найдет свои личные данные.
Если базовые классы поддерживают COM-агрегацию , вы можете использовать это, чтобы избежать накладных расходов. При создании базового объекта вы передаете IUnknown вашего внешнего класса как IUnknown внешнего агрегата. Это означает, что QueryInterface / AddRef / Release на всех производных интерфейсах базового объекта теперь будет делегироваться вашему внешнему объекту, делая его похожим на часть вашего внешнего объекта.
Теперь вы можете сделать так, чтобы QueryInterface возвращал ваш исходный объект (с методами проксирования) до инициализации или интерфейс базового объекта непосредственно после завершения инициализации. Пример:
class MyProxy : public IInterface
{
long refCt;
IUnknown *pUnkInner;
IInterface *pIfaceInner;
~MyProxy() {
AddRef(); // pUnkInner may take references to us in its destruction; prevent reentrancy per aggregating object rules
if (pUnkInner) {
pUnkInner->Release();
}
}
public:
MyProxy() : refCt(1), pUnkInner(NULL), pIfaceInner(NULL) { }
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
{
if (!ppvObject) return E_POINTER;
if (riid == IID_IUnknown) {
AddRef();
*ppvObject = (void *)static_cast<IUnknown *>(this);
return S_OK;
} else if (riid == IID_IInterface && pUnkInner) {
// increments refcount of _outer_ object
return pUnkInner->QueryInterface(riid, ppvObject);
} else if (riid == IID_IInterface) {
AddRef();
*ppvObject = (void *)static_cast<IInterface *>(this);
return S_OK;
} else {
return E_NOINTERFACE;
}
}
STDMETHODIMP_(DWORD) AddRef(void)
{ return InterlockedIncrement(&refCt); }
STDMETHODIMP_(DWORD) Release(void)
{ if (!InterlockedDecrement(&refCt)) delete this; }
HRESULT Initialize(VARIANT v) {
// You can use another protocol to create the object as well as long as it supports aggregation
HRESULT res = CoCreateInstance(CLSID_InnerClass, this, CLSCTX_INPROC_SERVER, IID_IUnknown, (LPVOID *)&pUnkInner);
if (FAILED(res)) return res;
res = pUnkInner->QueryInterface(IID_IInterface, (void **)&pIfaceInner);
if (FAILED(res)) {
pUnkInner->Release();
pUnkInner = NULL;
return res;
}
Release(); // the above QueryInterface incremented the _outer_ refcount, we don't want that.
return S_OK;
}
HRESULT DoWork() {
if (!pIfaceInner) return ERROR_NOT_INITIALIZED;
return pIfaceInner->DoWork();
}
};
Хотя исходный указатель IInterface
, который получает клиент, имеет издержки двойного вызова, после завершения инициализации он может повторно набрать QueryInterface
для получения более прямого указателя. Более того, если вы можете переместить инициализацию на другой интерфейс, вы можете заставить клиента повторно QueryInterface
, что обеспечит получение им прямого указателя.
Тем не менее, жизненно важно, чтобы агрегированные объекты поддерживали протокол агрегации; в противном случае вы получите непоследовательные ссылки и другие ошибки. Внимательно прочитайте документацию MSDN перед выполнением агрегирования.