我正在尝试在我的应用程序中嵌入浏览器控件(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草图将是这样的: (点击查看大图)
http://img827.imageshack.us/img827/3269/lsactivedesktopumlsmall.png
从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:
我尝试仅针对几个接口实现此功能。从IUnknown和TOleClientSite继承TWebf似乎工作正常,但是当我将TDispatch添加到继承列表时它停止工作。
除warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch'
警告外,我还会遇到运行时错误。运行时错误是“访问冲突读取位置0x00000000”
由于某种原因,运行时错误发生在处理IOleClientSite的行上,而不是IDispatch。我不知道为什么会发生这种情况,或者它是否真的与多重继承有关。有人提出任何线索吗?
编辑3:
QueryInterface的错误实现似乎是运行时异常的原因。正如Mark Ransom正确地指出,在指向* ppv之前需要对该指针进行转换,并且在请求IUnknown时需要特别小心。请阅读Why exactly do I need an explicit upcast when implementing QueryInterface in an object with multiple inheritance以获得对此的出色解释。
为什么我确实得到了我仍然不知道的特定运行时错误。
答案 0 :(得分:2)
多重继承是一种非常常见的COM接口方式,所以是的,这是可能的。
但是,QueryInterface仍然必须为每个接口强制转换指针。多重继承的一个有趣属性是指针可以针对每个类类型进行调整 - 指向IDispatch的指针与指向IDocHostUIHandler的指针的值不同,即使它们都指向同一个对象。还要确保IUnknown的QueryInterface始终返回相同的指针;因为所有接口都来自IUnknown,如果你只是试图直接转换它,你会得到一个模糊的演员,但这也意味着你可以使用任何接口作为IUnknown,只需选择父列表中的第一个。
答案 1 :(得分:0)
多重继承有一些限制
如果两个接口要求实现具有相同名称/签名的函数,则无法使用多重继承提供两种不同的行为。在某些情况下,您需要相同的实现,但在其他情况下则不需要。
在您的类的虚拟表上将有多个IUnknown接口,这可以增加额外的内存使用量。他们确实共享相同的实现,这很好。
答案 2 :(得分:0)
保留作文要容易得多。 MI有很多陷阱,比如虚拟继承,并且受可维护性的影响很大。如果你必须将它作为数据成员传递给组合类,那你就错了。如果需要访问其他提供的方法,那么您应该在方法调用中传递此内容。由于您控制对组合对象的所有方法调用,因此插入额外指针应该没有问题。这使得生活变得更加容易,维护和其他操作也更容易。