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

发布于 2024-09-16 23:12:57 字数 2583 浏览 13 评论 0原文

我正在尝试在我的应用程序中嵌入浏览器控件(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 添加到继承列表时,它停止工作。

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

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

编辑3:

QueryInterface 的错误实现似乎是运行时异常的原因。正如 Mark Ransom 正确指出的那样,需要在将 this 指针分配给 *ppv 之前对其进行强制转换,并且需要特别小心当请求 IUnknown 时。阅读 我到底为什么需要在具有多重继承的对象中实现 QueryInterface 时的显式向上转换对此有很好的解释。

我仍然不知道为什么我会收到特定的运行时错误。

I'm trying to embed a browser control in my application (IWebBrowser2). I need to implement IDispatch, IDocHostShowUI, IDocHostUIHandler etc to make this work. I am doing this in pure C++/Win32 api. I'm not using ATL, MFC or any other framework.

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. It's also used for controlling the browser control, with methods like Refresh(), Back(), Forward() etc.

Right now this is implemented with composition. TWebf has classes implementing all the different interfaces (IDispatch, IDocHostShowUI...) as (stack allocated) members. First thing TWebf does in its constructor is give all those members a pointer back to itself (dispatch.webf = this; etc). 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)

I don't like this circular dependency between TWebf and the classes implementing the interfaces. TWebf has a TDispatch member that has a TWebf member that has a...

So I was thinking about solving this with multiple inheritance instead. That would also simplify QueryInterface to always be able to just return this.

A UMLish sketch of what I want would be something like this:
(Click for a bigger view)

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.

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

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:

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.

Apart from the warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch' warning I also get runtime errors. The runtime error is "Access violation reading location 0x00000000"

The runtime error happens on a line dealing with IOleClientSite, not IDispatch for some reason. 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:

A bad implementation of QueryInterface seems to have been the reason for the runtime exception. 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. Read Why exactly do I need an explicit upcast when implementing QueryInterface in an object with multiple inheritance for an excellent explanation of that.

Why exactly I got that specific runtime error I still do not know.

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

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

发布评论

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

评论(3

镜花水月 2024-09-23 23:12:57

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

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

Multiple inheritance is a very common way to do COM interfaces, so yes it's possible.

However QueryInterface must still cast the pointer for each interface. 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. Also make sure that a QueryInterface for IUnknown always returns the same pointer; 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.

半岛未凉 2024-09-23 23:12:57

多重继承有一些限制

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

  2. 类的虚拟表上将有多个 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. They do share the same implementations which is nice.

爱殇璃 2024-09-23 23:12:57

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

It would be substantially easier to remain with composition. MI has many pitfalls, like virtual inheritance, and suffers greatly from maintainability. 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.

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