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

发布于 2024-11-25 12:03:03 字数 2022 浏览 5 评论 0原文

简短版本:

COM 类可以在运行时修改自己的虚拟表条目吗? (忽略线程问题)

完整版本:

我提供了许多实现 界面。 COM 接口是在第三方框架中定义的。

已编辑: COM 接口的 vtable 在 Windows 平台上是标准的;但我不清楚 C++ vtable 和 COM vtable 之间的关系。)

为了将我的实现正确注入到其中框架,它需要使用两步初始化:首先创建一个不带参数的对象,然后调用带所有参数的 Initialize 方法。这是可以选择我的实现之一的时刻。

为了实现这一点,我添加了一个“resolver”类(或简称为包装类),其唯一职责是在“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;
};

Short version:

Can a COM class modify its own virtual table entries at runtime? (disregarding thread issues)

Full version:

I'm providing a number of C++ classes which implement a interface. The COM interface is defined in a third-party framework.

(Edited: The vtable of the COM interface is standard on Windows platform; but the relationship between the C++ vtable and the COM vtable isn't clear to me.)

In order to inject my implementations correctly into that framework, it needs to use two-step initialization: first create an object with no parameters, then call an Initialize method with all parameters. This is the moment when one of my implementations can be chosen.

To make this happen, I added a "resolver" class (or simply a wrapper class), whose sole responsibility is to choose an implementation in the Initialize method. After that, every call to its COM methods will be forwarded to the actual implementation.

The resolver class is then injected into the framework. This gets around the framework limitation.

Now, seeing that the resolver class doesn't really have any use after initialization, I'm wondering if there is a way to get rid of the virtual method indirection cost? My idea is to copy each COM vtable entry from the concrete implementation to the vtable of the resolver class.

Will this work?

Example:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

从﹋此江山别 2024-12-02 12:03:03

你无法安全地复制 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 文档聚合。

You can't safely copy vtable entries; since the this pointer will still refer to your proxy class, the real implementation won't find its private data.

If the base classes support COM aggregation, you can use this to avoid the overhead however. When constructing your base object, you'd pass your outer class's IUnknown as the outer aggregation IUnknown. This means that the QueryInterface/AddRef/Release on all derived interfaces of the base object will now delegate to your outer object, making it look like a part of your outer object.

Now you can have QueryInterface return your original object (with proxying methods) prior to initialization, or the base object's interface directly when initialization is complete. Example:

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

Although the initial IInterface pointer the client gets has the double-call overhead, once the initialization is complete, they can re-QueryInterface to get a more direct pointer. What's more, if you can move the initialization to a different interface, you can force the client to re-QueryInterface, thus ensuring they get a direct pointer.

That said, it's vitally important that the aggregated objects support the aggregation protocol; otherwise you'll end up with inconsistent reference counts and other badness. Read the MSDN documentation carefully before implementing aggregation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文