简短版本:
COM类可以在运行时修改自己的虚拟表条目吗? (无视线程问题)
完整版:
我提供了许多实现com接口的C ++类。 COM接口在第三方框架中定义。
(编辑: COM接口的vtable在Windows平台上是标准的;但是我不清楚C ++ vtable和COM vtable之间的关系。)
为了将我的实现正确地注入到该框架中,它需要使用两步初始化:首先创建一个没有参数的对象,然后使用所有参数调用Initialize
方法。这是我可以选择其中一个实现的时刻。
为了实现这一点,我添加了一个“解析器”类(或简称一个包装类),它的唯一责任是在Initialize
方法中选择一个实现。之后,对其COM方法的每次调用都将转发给实际的实现。
然后将解析器类注入框架中。这绕过了框架限制。
现在,看到解析器类在初始化后没有任何用处,我想知道是否有办法摆脱虚方法间接成本?我的想法是将每个COM vtable条目从具体实现复制到解析器类的vtable。
这会有用吗?
示例:
// (FYI) #define HRESULT unsigned long
struct IInterface
{
// ... the usual AddRef, Release and QI omitted
virtual HRESULT Initialize(VARIANT v) = 0; // the Initialize method, where implementation gets chosen
virtual HRESULT DoWork() = 0; // can only call after initialization.
};
class MyResolver : public IInterface
{
// ... the usual AddRef, Release and QI omitted
public:
virtual HRESULT Initialize(VARIANT v)
{
if ( /* some conditions based on v */ )
return Implem_One.Create((void**) &m_pImpl);
else
return Implem_Two.Create((void**) &m_pImpl);
"Here, can I copy the vtable entries from m_pImpl to the vtable of MyResolver (*this) ?";
for (int k = 0; k < num_virtual_methods; ++k)
this->vtbl[k] = m_pImpl->vtbl[k];
}
virtual HRESULT DoWork()
{
if (!m_pImpl) return ERROR_NOT_INITIALIZED;
m_pImpl->DoWork();
}
public:
// this creation method is injected into the framework
static HRESULT Create(void**) { /* create an instance of this class and return it */ }
private:
MyResolver() : m_pImpl(NULL) {}
virtual ~MyResolver() { if (m_pImpl) m_pImpl->Release(); }
IInterface* m_pImpl;
};
答案 0 :(得分:2)
您无法安全地复制vtable条目;由于this
指针仍将引用您的代理类,因此实际实现将无法找到其私有数据。
如果基类支持COM aggregation,则可以使用它来避免开销。构造基础对象时,您将外部类的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 documentation。