2011-07-21 43 views
2

短版:自我修改虛擬表條目指向具體的實施

COM類可以修改其自身的虛擬表條目在運行時? (不考慮線程問題)

完整版:

我提供了一些其實現接口C++類的。 COM接口在第三方框架中定義。

編輯: COM接口的虛函數表是Windows平臺上的標準;但是C++虛函數表和COM虛函數表之間的關係,我不清楚。)

爲了注入我的實現正確地進入該框架,它需要使用兩步初始化:首先創建一個沒有參數的對象,然後用所有參數調用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

什麼是「STDMETHOD」? –

+0

@ phresnel:我用'virutal long'代替了那個。感謝您的建議。 – rwong

+0

現在你有一些不返回任何東西的函數。 –

回答

2

你不能安全地複製虛函數表條目;由於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以獲得更直接的指針。更重要的是,如果您可以將初始化移至不同的界面,您可以強制客戶端重新登錄,以確保他們獲得直接指針。

也就是說,聚合對象支持聚合協議是非常重要的;否則你會以不一致的引用計數和其他不好的結果。在實施聚合之前仔細閱讀MSDN documentation