自修改虚拟表条目以指向具体实现

时间:2011-07-21 06:00:14

标签: visual-c++ com vtable

简短版本:

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;
};

1 个答案:

答案 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