简体   繁体   English

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

[英]C++ COM design. Composition vs multiple inheritance

I'm trying to embed a browser control in my application (IWebBrowser2). 我试图在我的应用程序(IWebBrowser2)中嵌入浏览器控件。 I need to implement IDispatch, IDocHostShowUI, IDocHostUIHandler etc to make this work. 我需要实现IDispatch,IDocHostShowUI,IDocHostUIHandler等以使其工作。 I am doing this in pure C++/Win32 api. 我在纯C ++ / Win32 api中执行此操作。 I'm not using ATL, MFC or any other framework. 我没有使用ATL,MFC或任何其他框架。

I have a main class, called TWebf, that creates a Win32 window to put the browser control in and makes all the OLE calls needed to make it work. 我有一个名为TWebf的主类,它创建一个Win32窗口来放置浏览器控件,并进行使其工作所需的所有OLE调用。 It's also used for controlling the browser control, with methods like Refresh(), Back(), Forward() etc. 它还通过Refresh(),Back(),Forward()等方法用于控制浏览器控件。

Right now this is implemented with composition. 现在,这是通过组合实现的。 TWebf has classes implementing all the different interfaces (IDispatch, IDocHostShowUI...) as (stack allocated) members. TWebf具有将所有不同接口(IDispatch,IDocHostShowUI ...)实现为(堆栈分配的)成员的类。 First thing TWebf does in its constructor is give all those members a pointer back to itself ( dispatch.webf = this; etc). TWebf在其构造函数中所做的第一件事是为所有这些成员提供一个指向其自身的指针( dispatch.webf = this;等等)。 QueryInterface, AddRef and Release are implemented as calls to those methods in TWebf for all interface implementations (by calling return webf->QueryInterface(riid, ppv); for example) QueryInterface,AddRef和Release在所有接口实现中都作为对TWebf中这些方法的调用来实现(例如,通过调用return webf->QueryInterface(riid, ppv);

I don't like this circular dependency between TWebf and the classes implementing the interfaces. 我不喜欢TWebf与实现接口的类之间的这种循环依赖关系。 TWebf has a TDispatch member that has a TWebf member that has a... TWebf的TDispatch成员的TWebf成员的...

So I was thinking about solving this with multiple inheritance instead. 所以我当时正在考虑使用多重继承来解决这个问题。 That would also simplify QueryInterface to always be able to just return this . 这也将简化QueryInterface使其始终能够仅返回this

A UMLish sketch of what I want would be something like this: (Click for a bigger view) 我想要的UMLish草图如下所示:(单击查看大图)

As can be seen in the uml I want to provide bare-minimum implementations of all the interfaces so I only have to override those methods in the interfaces I actually want to do something substantial in TWebf. 从uml中可以看出,我想提供所有接口的最小实现,因此我只需要重写接口中的那些方法,而实际上我想在TWebf中做一些实质性的事情。

Is my "multiple inheritance implementation" possible? 我的“多继承实现”是否可能? Is it a good idea? 这是个好主意吗? Is it the best solution? 这是最好的解决方案吗?

EDIT: 编辑:

For future discussion, here's the current implementation of QueryInterface in 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;
}

EDIT 2: 编辑2:

I tried implementing this for just a couple of the interfaces. 我尝试仅通过几个接口来实现。 Having TWebf inherit from IUnknown and TOleClientSite seems to work fine, but when I added TDispatch to the inheritance list it stopped working. 从IUnknown和TOleClientSite继承TWebf似乎很好,但是当我将TDispatch添加到继承列表时,它停止工作。

Apart from the warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch' warning I also get runtime errors. 除了warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch'警告warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch'我也遇到运行时错误。 The runtime error is "Access violation reading location 0x00000000" 运行时错误为“访问冲突读取位置0x00000000”

The runtime error happens on a line dealing with IOleClientSite, not IDispatch for some reason. 由于某些原因,运行时错误发生在处理IOleClientSite的行上,而不是IDispatch。 I don't know why this is happening, or if it really has to do with the multiple inheritance or not. 我不知道为什么会这样,或者是否确实与多重继承有关。 Any clues anyone? 有任何线索吗?

EDIT 3: 编辑3:

A bad implementation of QueryInterface seems to have been the reason for the runtime exception. QueryInterface的错误实现似乎是运行时异常的原因。 As Mark Ransom correctly noted the this pointer needs to be casted before it's assigned to *ppv, and special care is needed when IUnknown is requested. 正如Mark Ransom正确指出的那样,在将此指针分配给* ppv之前,必须强制转换此指针,并且在请求IUnknown时需要特别注意。 Read Why exactly do I need an explicit upcast when implementing QueryInterface in an object with multiple inheritance for an excellent explanation of that. 请阅读为什么在具有多重继承的对象中实现QueryInterface时我到底需要显式的上位转换,以得到很好的解释。

Why exactly I got that specific runtime error I still do not know. 为什么我仍然不知道确切地得到了那个特定的运行时错误。

Multiple inheritance is a very common way to do COM interfaces, so yes it's possible. 多重继承是建立COM接口的一种非常常见的方式,因此是可能的。

However QueryInterface must still cast the pointer for each interface. 但是QueryInterface仍必须为每个接口强制转换指针。 One interesting property of multiple inheritance is that the pointer may get adjusted for each class type - a pointer to IDispatch won't have the same value as a pointer to IDocHostUIHandler, even though they both point to the same object. 多重继承的一个有趣的特性是,可以针对每种类类型对指针进行调整-指向IDispatch的指针与指向IDocHostUIHandler的指针不会具有相同的值,即使它们都指向同一对象也是如此。 Also make sure that a QueryInterface for IUnknown always returns the same pointer; 另外,请确保IUnknown的QueryInterface始终返回相同的指针; since all the interfaces derive from IUnknown you'll get an ambiguous cast if you try just to cast directly to it, but that also means you can use any interface as an IUnknown, just pick the first one in the parent list. 由于所有接口都是从IUnknown派生的,因此,如果尝试直接将其强制转换为IUnknown,则会得到模棱两可的转换,但这也意味着您可以将任何接口用作IUnknown,只需在父列表中选择第一个即可。

Multiple inheritance has a couple of limitations 多重继承有两个限制

  1. If two interfaces ask for an implementation of a function with the same name/signiture its impossible to provide two different behaviors using multiple inheritance. 如果两个接口要求实现具有相同名称/签名的功能,则不可能使用多重继承来提供两种不同的行为。 In some cases you want the same implementation, but in others you don't. 在某些情况下,您需要相同的实现,但在其他情况下,您不需要。

  2. There will be multiple IUnknown interfaces on the virtual table of your class which can add up to extra memory usage. 您的类的虚拟表上将有多个IUnknown接口,这些接口可能会增加额外的内存使用量。 They do share the same implementations which is nice. 它们确实共享相同的实现,这很好。

It would be substantially easier to remain with composition. 保留组成将是非常容易的。 MI has many pitfalls, like virtual inheritance, and suffers greatly from maintainability. MI有很多陷阱,例如虚拟继承,并且遭受了可维护性的极大困扰。 If you have to pass this in to the composed classes as a data member, you've done it wrong. 如果您必须将此作为数据成员传递给组成的类,那么您做错了。 What you should do is pass this in on method calls if they need to access the other provided methods. 如果他们需要访问其他提供的方法,则应该将其传递给方法调用。 Since you control all method calls to the composed object, it should be no problem to insert the extra pointer. 由于控制了对组成对象的所有方法调用,因此插入额外的指针应该没有问题。 This makes life MUCH, MUCH easier for maintenance and other operations. 这使使用寿命大大缩短,维护和其他操作也变得更加容易。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM