保持实现多个接口的COM类可管理的良好技术

时间:2011-03-29 20:41:52

标签: c++ com directshow anti-patterns

实现许多接口的COM对象可能最终遭受上帝对象反模式或最终充满繁琐的转发代码:

class MyCOMClass
    , public CUnknown
    , public IFoo
    , public IBar
    , public IPersistStream
    , public IYetAnotherInterface,
    , public IAndAnotherInterfaceToo
// etc etc etc

在最明显的实现中,MyCOMClass类最终在内部实现所有接口,变得非常大并且与每个接口的实现细节相关联。或者,MyCOMClass往往会充满大量繁琐的样板代码,这些代码将接口的处理转发给专注于特定接口问题的其他对象。

是否有轻量级技术将不同接口的处理分离到其他内部对象,而不必使用容易出错的COM聚合或违反QueryInterface的COM对称性要求?

我最初尝试解决方案似乎有效,但感觉有点像黑客:

不是在MyCOMClass中实现IFoo,而是在一个轻量级的非COM C ++类中实现IFoo,该类委托给提供的IUnknown。当调用QueryInterface(__ uuidof(IFoo))时,返回一个FooHandler并将其作为委托IUnknown提供给MyCOMClass的IUnknown。

class FooHandler : public IFoo
{
public:
        SetDelegateUnknown(IUnknown* unk) { m_DelegateUnknown=unk; }
        IUnknown* GetDelegateUnknown() { return m_DelegateUnknown; }
    HRESULT STDMETHODCALLTYPE QueryInterface(const IID &riid,void **ppvObject) { return GetDelegateUnknown()->QueryInterface(riid, ppvObject); }
    virtual ULONG STDMETHODCALLTYPE AddRef(void) { return GetDelegateUnknown()->AddRef(); }
    virtual ULONG STDMETHODCALLTYPE Release( void) { return GetDelegateUnknown()->Release(); }

        // all the other iFoo methods are implemented here
private:
    IUnknown*   m_DelegateUnknown;  
};

可以将样板代理设置和IUnknown实现压缩为一个宏,如DirectShow基类中的DECLARE_IUNKNOWN宏。我还没有找到一种将它封装在基类中的好方法。

感谢您的任何建议。

1 个答案:

答案 0 :(得分:1)

假设您不需要处理程序对象与整个对象分离C ++实例,那么以下内容可能有用......

如果我正确地记住我的COM ...... - 为什么不将FooHandler留作部分抽象的基类 - 让IUnknown部分保持未实现状态。让MyCOMClass来自所有必要的处理程序;然后仅在最派生的类中实现IUnknown。您在那里提供的AddRef / Release / QI将用于所有基类。 (通常可以将AddRef / Release转发到CUnknown类或一些执行引用计数管理的基础,但可能需要手动实现QI,因为这是您完全知道要公开的接口集的唯一地方。)

换句话说,继续做你正在做的事;但是您不需要手动执行委托部分:由于接口的多重继承(特别是具有虚方法的类)如何在C ++中工作,编译器实际上在幕后实际上也是类似的。神奇的部分是默认情况下在最派生的接口中声明的方法会覆盖基类中所有相同名称和参数签名的方法;所以任何在自己的IUnknown中调用AddRef或QI的基类最终都会调用你在派生程度最高的类中提供的版本。

代码可能看起来像:

class MyCOMClass
    , public CUnknown // Assume this handles refcounting (and has a virtual dtor!)
    , public CFooHandler // Implements IFoo
    , public CBarHandler // Implements IBar
{
    ... add any interfaces that MyCOMClass itself is implementing...

    // Actual impl of IUnknown...
    STDMETHOD_(ULONG, AddRef)(); { return CUnknown::AddRef(); }
    STDMETHOD_(ULONG, Release)(); { return CUnknown::Release(); }

    STDMETHOD(QueryInterface)(IN REFIID riid, OUT void** ppv)
    {
        *ppv = NULL;

        // IUnknown can require extra casting to pick out a specific IUnknown instance
        // otherwise compiler will complain about an ambiguous cast. Any IUnknown will do,
        // we know they're all the same implementation, so even casting to CFooHandler then IUnknown is fine here.
        // Here am assuming that CUnknown implements IUnknown
        if(riid == __uuidof(IUnknown))
            *ppv = static_cast<IUnknown*>(static_cast<CUnknown*>(this));
        else if(riid == __uuidof(IFoo))
            *ppv = static_cast<IFoo*>(this);
        else if(riid == __uuidof(IBar))
            *ppv = static_cast<IBar*>(this);
        else
            return E_NOINTERFACE;

        // Usually you call AddRef on the interface you are returning; but
        // we know we're using the same ref count for the whole object, so this
        // is appropriate for this specific implementation strategy.
        AddRef();
    }

如果您确实希望在单独的对象上实现处理程序,那么您需要执行您提议的委派 - 它本质上是一种聚合形式。但是你不需要在MyCOMClass上实现接口并编写大量的转发器:MyCOMClass必须做的就是实现QI,这样返回的值 - 无论是相同的'this'对象还是一些单独的对象 - 都被正确地转换为请求的界面。但是如果你不需要单独的对象,上述技术应该可以正常工作。