Good techniques for keeping COM classes that implement multiple interfaces manageable

廉价感情. 提交于 2021-02-07 04:24:15

问题


COM objects that implement many interfaces can end up suffering from the god object anti-pattern or end up full of tedious forwarding code:

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

In the most obvious implementation, the class MyCOMClass ends up implementing all the interfaces internally, becomes very large and coupled to details of implementation of each interface. Alternatively MyCOMClass tends to get filled with lots of tedious boilerplate code that forwards the handling of the interfaces to other objects that are focused on the concerns of that particular interface.

Are there any light weight techniques for separating handling of the different interfaces to other internal objects without having to use error-prone COM aggregation or violating COM symmetry requirements for QueryInterface?

My initial attempt at a solution seems to work but feels like a bit of a hack:

Instead of implementing IFoo in MyCOMClass, implement IFoo in a lightweight non-COM C++ class that delegates back to a supplied IUnknown. When QueryInterface(__uuidof(IFoo)) is called, return a FooHandler and supply it with the IUnknown of MyCOMClass as a delegate 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;  
};

The boilerplate delegate setting and IUnknown implementation could be compacted into a macro like the DECLARE_IUNKNOWN macro in the DirectShow base classes. I haven't found a good way to encapsulate this in a base class.

Thanks for any suggestions.


回答1:


Assuming that you don't need the handler objects to be separate C++ instances from the overall object, then the following may work...

If I Remember my COM Correctly... - why not just leave FooHandler as a partially abstract base class - leave the IUnknown portion unimplemented. Have MyCOMClass derive from all the necessary handlers; and then implement IUnknown only in that most-derived class. The AddRef/Release/QI you supply there will be used for all the base classes. (Typically can just forward AddRef/Release to the CUnknown class or some base that does the refcounting housekeeping, but likely need to implement QI manually since this is the only place that you know fully what set of interfaces you want to expose.)

In other words, keep doing what you are doing; but you don't need to do the delegation part manually: the compiler actually does similar for you behind the scenes anyhow due to how Multiple Inheritance of interfaces (specifically, classes with virtual methods) works in C++. The magic part is that methods declared in the most-derived interface by default override all those of the same name and parameter signature in the base classes; so any base class that calls AddRef or QI in their own IUnknown will end up actually calling the version you supply in the most-derived class.

Code likely looks something like:

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

As a for-what-it's-worth, if you do want to implement the handlers on separate objects, then you will need to do the delegation that you are proposing - it's essentially a form of aggregation. But you don't need to implement the interfaces on the MyCOMClass and write lots of forwarders: all MyCOMClass has to do is implement QI such that the returned value - whether the same 'this' object or some separate object - is properly cast to the requested interface. But if you don't need separate objects, the above technique should work fine.



来源:https://stackoverflow.com/questions/5478669/good-techniques-for-keeping-com-classes-that-implement-multiple-interfaces-manag

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!