保持实现多个接口的COM类的可管理性的好技术 [英] Good techniques for keeping COM classes that implement multiple interfaces manageable

查看:45
本文介绍了保持实现多个接口的COM类的可管理性的好技术的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

实现许多接口的COM对象最终可能会遭受上帝对象反模式的困扰,或者最终会充满乏味的转发代码:

  class MyCOMClass,公开CUnknown,公共IFoo,公共IBar,公共IPersistStream,公共IYetAnotherInterface,,公共IAndAnotherInterfaceToo//等等等等 

在最明显的实现中,类MyCOMClass最终在内部实现了所有接口,变得非常大,并耦合到每个接口的实现细节.另外,MyCOMClass往往会充满大量繁琐的样板代码,这些代码将接口的处理转发给其他对象,这些对象专注于该特定接口的问题.

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

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

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

  class FooHandler:公共IFoo{上市:SetDelegateUnknown(IUnknown * unk){m_DelegateUnknown = unk;}IUnknown * GetDelegateUnknown(){return m_DelegateUnknown;}HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid,void ** ppvObject){return GetDelegateUnknown()-> QueryInterface(riid,ppvObject);}虚拟ULONG STDMETHODCALLTYPE AddRef(void){return GetDelegateUnknown()-> AddRef();}虚拟ULONG STDMETHODCALLTYPE Release(void){return GetDelegateUnknown()-> Release();}//在这里实现所有其他iFoo方法私人的:IUnknown * m_DelegateUnknown;}; 

可以将样板委托设置和IUnknown实现压缩到DirectShow基类中的DECLARE_IUNKNOWN宏之类的宏中.我还没有找到一种将其封装在基类中的好方法.

谢谢您的建议.

解决方案

假设您不需要将处理程序对象与整个对象分开作为单独的C ++实例,那么以下方法可能会起作用...

如果我没记错我的COM ...-为什么不只将FooHandler保留为部分抽象的基类-请将IUnknown部分保留为未实现.让MyCOMClass从所有必需的处理程序派生;然后仅在该派生最多的类中实现IUnknown.您在此处提供的AddRef/Release/QI将用于 all 基类.(通常可以将AddRef/Release转发到CUnknown类或进行计数内务处理的某个基类,但是可能需要手动实现QI,因为这是您唯一知道要公开的接口集的唯一位置.)

换句话说,继续做自己在做的事;但是您无需手动执行委派部分:由于接口的多重继承(特别是具有虚拟方法的类)在C ++中的工作方式,因此编译器实际上在幕后实际上为您执行了类似的操作.魔术部分是默认情况下,在派生最多的接口中声明的方法会覆盖基类中所有具有相同名称和参数签名的方法;因此,任何在自己的IUnknown中调用AddRef或QI的基类实际上都会最终调用您在派生最多的类中提供的版本.

代码可能类似于:

  class MyCOMClass,public CUnknown////假设这可以处理refcounting(并且有一个虚拟dtor!),public CFooHandler//实现IFoo,public CBarHandler//实现IBar{...添加MyCOMClass本身正在实现的任何接口...//IUnknown的实际暗示...STDMETHOD_(ULONG,AddRef)();{return CUnknown :: AddRef();}STDMETHOD_(ULONG,Release)();{return CUnknown :: Release();}STDMETHOD(QueryInterface)(输入已刷新,输出无效** ppv){* ppv = NULL;//IUnknown可能需要额外的转换才能挑选出特定的IUnknown实例//否则编译器将抱怨模棱两可.任何IUnknown都会做的,//我们知道它们都是相同的实现,因此即使将其转换为CFooHandler然后使用IUnknown也可以.//这里假设CUnknown实现IUnknownif(riid == __uuidof(IUnknown))* ppv = static_cast< IUnknown *>(static_cast< CUnknown *>(this));否则if(riid == __uuidof(IFoo))* ppv = static_cast< IFoo *>(this);否则if(riid == __uuidof(IBar))* ppv = static_cast< IBar *>(this);别的返回E_NOINTERFACE;//通常,您在要返回的接口上调用AddRef;但//我们知道我们对整个对象使用相同的引用计数,因此这//适用于此特定的实施策略.AddRef();} 

作为一个有价值的东西,如果您确实想在单独的对象上实现处理程序,那么您将需要进行提议的委托-本质上是一种聚合形式.但是您无需在MyCOMClass上实现接口并编写大量转发器:MyMyClass要做的就是实现QI,以便将返回的值(无论是相同的"this"对象还是某些单独的对象)正确地强制转换为请求的接口.但是,如果您不需要单独的对象,则上述技术应该可以正常工作.

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.

解决方案

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.

这篇关于保持实现多个接口的COM类的可管理性的好技术的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆