C++ COM 设计。组合与多重继承

Posted

技术标签:

【中文标题】C++ COM 设计。组合与多重继承【英文标题】:C++ COM design. Composition vs multiple inheritance 【发布时间】:2011-04-06 12:33:32 【问题描述】:

我正在尝试在我的应用程序 (IWebBrowser2) 中嵌入浏览器控件。我需要实现 IDispatch、IDocHostShowUI、IDocHostUIHandler 等来完成这项工作。我在纯 C++/Win32 api 中执行此操作。我没有使用 ATL、MFC 或任何其他框架。

我有一个名为 TWebf 的主类,它创建一个 Win32 窗口以将浏览器控件放入其中并进行使其工作所需的所有 OLE 调用。它还用于控制浏览器控件,例如 Refresh()、Back()、Forward() 等方法。

现在这是通过组合实现的。 TWebf 具有将所有不同接口(IDispatch、IDocHostShowUI ...)实现为(堆栈分配的)成员的类。 TWebf 在其构造函数中所做的第一件事是给所有这些成员一个指向自身的指针(dispatch.webf = this; 等)。 QueryInterface、AddRef 和 Release 被实现为对 TWebf 中所有接口实现的这些方法的调用(例如,通过调用 return webf->QueryInterface(riid, ppv);

我不喜欢 TWebf 和实现接口的类之间的这种循环依赖。 TWebf 有一个 TDispatch 成员,该成员有一个 TWebf 成员,该成员有一个...

所以我正在考虑用多重继承来解决这个问题。这也将简化 QueryInterface 以始终能够只返回 this

我想要的 UMLish 草图是这样的: (点击查看大图)

从 uml 中可以看出,我想提供所有接口的最低限度的实现,所以我只需要覆盖接口中的那些方法,我实际上想在 TWebf 中做一些实质性的事情。

我的“多重继承实现”可能吗?这是个好主意吗?这是最好的解决方案吗?

编辑:

为了以后的讨论,这里是 TWebf 中 QueryInterface 的当前实现

HRESULT STDMETHODCALLTYPE TWebf::QueryInterface(REFIID riid, void **ppv)

    *ppv = NULL;

    if (riid == IID_IUnknown) 
        *ppv = this;
     else if (riid == IID_IOleClientSite) 
        *ppv = &clientsite;
     else if (riid == IID_IOleWindow || riid == IID_IOleInPlaceSite) 
        *ppv = &site;
     else if (riid == IID_IOleInPlaceUIWindow || riid == IID_IOleInPlaceFrame) 
        *ppv = &frame;
     else if (riid == IID_IDispatch) 
        *ppv = &dispatch;
     else if (riid == IID_IDocHostUIHandler) 
        *ppv = &uihandler;
    

    if (*ppv != NULL) 
        AddRef();
        return S_OK;
    

    return E_NOINTERFACE;

编辑 2:

我尝试仅针对几个接口实现此功能。让 TWebf 从 IUnknown 和 TOleClientSite 继承似乎工作正常,但是当我将 TDispatch 添加到继承列表时它停止工作。

除了warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch' 警告之外,我还会遇到运行时错误。运行时错误是“访问冲突读取位置0x00000000”

由于某种原因,运行时错误发生在处理 IOleClientSite 而不是 IDispatch 的行上。我不知道为什么会这样,或者它是否真的与多重继承有关。有什么线索吗?

编辑 3:

QueryInterface 的错误实现似乎是运行时异常的原因。正如Mark Ransom 正确指出的那样, this 指针需要在分配给 *ppv 之前进行强制转换,并且在请求 IUnknown 时需要特别小心。阅读 Why exactly do I need an explicit upcast when implementing QueryInterface in an object with multiple inheritance 以获得很好的解释。

为什么我仍然不知道具体的运行时错误。

【问题讨论】:

这是一个骗子,我知道这是因为我向另一个人提出了答案。去寻找它。 相关:***.com/questions/299585/… Why can't we use "virtual inheritance" in COM?的可能重复 @Ben Voigt:谢谢,我在问这个问题之前阅读了他们两个,但觉得他们没有回答我的特定问题。如果他们这样做,我对此没有足够好的理解来实现它。 它应该(显然)是static_cast<IMumble*>(this) 【参考方案1】:

多重继承是实现 COM 接口的一种非常常见的方式,所以是的。

但是 QueryInterface 仍然必须为每个接口转换指针。多重继承的一个有趣特性是指针可能会针对每种类类型进行调整——指向 IDispatch 的指针与指向 IDocHostUIHandler 的指针的值不同,即使它们都指向同一个对象。还要确保 IUnknown 的 QueryInterface 总是返回相同的指针;由于所有接口都派生自 IUnknown,如果您尝试直接转换为 IUnknown,您会得到一个模棱两可的转换,但这也意味着您可以将任何接口用作 IUnknown,只需选择父列表中的第一个。

【讨论】:

所有接口都必须提供IUnknown功能,(否则你会如何QI不同的接口?)所以IUnknown有多个副本(虚拟不能使用,请参阅链接问题)。但是所有IUnknown 实现必须共享一个计数。因此,即使您的设计基于继承,您最终也会得到组合元素。 是的,我之前也看到过,但是我的例子中TWebf对应的类总是直接继承自COM接口。是否也可以像我一样拥有像 TDispatch 这样的中间实现类? @Tobbe,我认为中级课程没有任何问题。虽然我已经做了几年了,但我可能忘记了一些事情。 @Ben Voigt,微软声明多重继承允许 IUnknown 接口共享相同的实现(但我忘记了机械细节):msdn.microsoft.com/en-us/library/ms680509(VS.85).aspx【参考方案2】:

多重继承有一些限制

    如果两个接口要求实现具有相同名称/签名的函数,则不可能使用多重继承提供两种不同的行为。在某些情况下,您需要相同的实现,但在其他情况下,您不需要。

    类的虚拟表上会有多个 IUnknown 接口,这会增加额外的内存使用量。它们确实共享相同的实现,这很好。

【讨论】:

vtables 每个类只有一个,而不是每个对象一个,所以额外的内存使用可以忽略不计。 1.我认为唯一具有相同名称/签名的函数是 IUnknown 函数,它们可以共享一个实现,所以我认为没关系。 2. 在这种情况下,内存使用不是一个非常大的问题。【参考方案3】:

保持构图会容易得多。 MI有很多陷阱,比如虚拟继承,并且在可维护性方面受到很大影响。如果您必须将其作为数据成员传递给组合类,那么您做错了。如果他们需要访问其他提供的方法,您应该做的是在方法调用中传递这个。由于您控制对组合对象的所有方法调用,因此插入额外的指针应该没有问题。这让维护和其他操作变得更加容易。

【讨论】:

我不明白这将如何工作。您能否提供一个示例,说明组合类如何调用 TWebf 的 QueryInterface,例如当调用它们自己的 QI 时。我无法控制对他们的 QI 的调用,它是调用 QI 的其他一些 COM 类,它不提供所需的“this”指针。如果我可以删除循环依赖并保留我也很满意的解决方案。 我不确定“组合”在这里是否合适。 COM 似乎对委托IUnknown 实现的对象的所有兄弟接口使用“聚合”一词。 msdn.microsoft.com/en-us/library/ms686558.aspx @Tobbe:你没有提到你无法控制你继承的类。

以上是关于C++ COM 设计。组合与多重继承的主要内容,如果未能解决你的问题,请参考以下文章

适配器模式(C++)多重继承和组合实现

C++的多重继承

C++多重继承与void*指针转换问题

C++的探索路12继承与派生之高级篇--派生类与赋值运算符及多重继承

C++ 中 QObject 多重继承和策略/特征设计的问题

C++中的多重继承逻辑