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

发布于 2024-10-27 18:22:25 字数 1449 浏览 7 评论 0原文

实现许多接口的 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 并为其提供 MyCOMClass 的 IUnknown 作为委托 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 宏。我还没有找到将其封装在基类中的好方法。

感谢您的任何建议。

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.

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

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

发布评论

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

评论(1

栀梦 2024-11-03 18:22:25

假设您不需要将处理程序对象与整个对象分开的 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”对象还是某个单独的对象)正确地转换为请求的接口。但如果您不需要单独的对象,上述技术应该可以正常工作。

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.

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