对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?
Posted
技术标签:
【中文标题】对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?【英文标题】:Is it safe to cast a IDispatch* into an IUnknown*, without using QueryInterface, for interprocess COM objects?对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是否安全? 【发布时间】:2015-06-10 13:46:42 【问题描述】:在处理进程间COM
对象时,不使用QueryInterface
将IDispatch*
转换为IUnknown*
是否安全?
这里我们的IDispatch
对象来自另一个进程OtherProcess.exe
。
我的一位同事说我应该在IDispatch
上拨打QueryInterface
以获得IUnknown
。
目前我在做:
void CComThrowDispatch::CheckCOMAvailabilty() const
IUnknown * pIUnknown = m_spDispatchDriver.p;
// is this line above a problem ?
// m_spDispatchDriver is an ATL CComDispatchDriver
// it handles an object instanciated in another process.
// m_spDispatchDriver.p is of type IDispatch*
if (pIUnknown == nullptr) return;
bool bComObjectReachable = ::CoIsHandlerConnected(pIUnknown) == TRUE;
if (bComObjectReachable == false)
throw MyException;
我对他的建议的问题:我正在处理 OtherProcess.exe 崩溃或被杀死的情况(访问冲突)。似乎在 IDispatch
上调用像 Invoke
这样的任何函数,这些函数封装了不再存在的 OtherProcess.exe 中的任何对象,这会引发这些访问冲突(EDIT: cmets 和答案表明这个最新的假设完全错误!)。
这就是为什么我试图保护应用程序测试::CoIsHandlerConnected(pIUnknown);
,它以IUnknown
作为参数。
但是通过在IDispatch
上调用QueryInterface
,就像我的同事建议我做的那样,我害怕回到我试图解决的同一个问题:这个IDispatch
处理一个不再存在的对象, 和 QueryInterface
到 IUnknown
只是未定义的行为都是一样的(再次EDIT,这个假设也是错误的)。
当我只是做演员时,我真的错了吗?
处理死进程间COM
对象的常用方法是什么?
这是 OAIdl.h 中IDispatch
定义的开始,它被声明为派生自IUnknown
。
MIDL_INTERFACE("00020400-0000-0000-C000-000000000046")
IDispatch : public IUnknown
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ __RPC__out UINT *pctinfo) = 0;
【问题讨论】:
投射是真的错误。从死服务器调用接口上的方法会导致 RPC 错误,而不是 AVE。您需要获取 RPC_E_SERVERDIED。你需要关注真正的问题,现在你只会让事情变得更糟。 @HansPassant 这很有趣,谢谢。但是,我很难理解为什么将派生自 IUnknown 的 IDispatch 向上转换 到 IUnknown 是真的错误。你能详细说明一下吗?我最初犯的错误是什么? @StephaneRolland:在某些情况下,您希望/需要查询IUnknown
,即使任何接口派生自它并且可以使用转换(请参阅 Objects Must Have Identity section),但您的情况并非如此问题。
@RomanR.yes,您的回答和 Hans 的回答让我看到访问冲突必须来自其他地方。但这是否意味着 COM 不完全兼容 C++? 因为不能保证将 IDispatch 强制转换到它的基类中,所以它被破坏了,因为多态性是不能保证的。我对问题的表述是否正确?
@StephaneRolland:不,我不认为这个措辞是不正确的。不确定性来自这样一个事实,即 COM 对象可能一次实现几个 IUnkown、几个 IDispatch,然后QueryInterface
不是强制转换 - 它是一个返回指针的方法,这个指针可能是也可能不是由相同的 C++(或非 C++)类实现。问题中的 API 说“给我 IUnknown”,这就提出了一个问题,即您应该提供哪个 IUnknown,以及作为 IDispatch 的一部分实现的 API 是否足够好。所有提到的都是合法的,没有被破坏。
【参考方案1】:
在 C++ 中将IDispatch
转换为IUnknown
(如static_cast<IUnknown*>(pDispatch)
)会产生完全相同的指针值,因为IDispatch
派生自IUnknown
。 OTOH,在pDispatch
上为IID_IUnknown
执行QueryInterface
可能返回不同的指针,但它仍然是合法的操作。事实上,这就是如何获取 COM 对象的身份,例如,检查两个接口是否由同一个 COM 对象实现(一个始终在同一个 COM 单元内工作的硬 COM 规则)。
也就是说,COM 编组器实现的代理 COM 对象可能是缓存接口,因此对 IDispatch::QueryInterface
的调用可能会返回 S_OK
和代理的有效 IUnknown
身份,尽管远程服务器已经关闭。也就是说,这样的操作可能不会导致即时 IPC 调用。
在您的情况下,为了测试 COM 服务器是否仍然正常运行,我只需在您已有的代理对象上调用 IDispatch::GetTypeInfoCount
。这实际上会导致 IPC 调用(或者如果服务器在不同的主机上运行,则通过网络进行往返)。
如果远程服务器崩溃或不可用,您可能会收到CO_E_OBJNOTCONNECTED
错误(可能是不同的错误代码,但肯定不是S_OK
)。
但请注意,根据您的情况,执行额外的 IPC 调用只是为了检查服务器是否可用可能是一项昂贵的操作。
【讨论】:
【参考方案2】:为了检测对象是否是远程CoIsHandlerConnected
无论如何都会使用QueryInterface
参数(对于IProxyManager
等),因此无论您提供已有的指针,还是额外查询@ 987654324@。您的QueryInterface
调用不会影响远程对象的状态:无论对象是否为远程,远程对象是否已死 - 无论您是否添加了QueryInterface
,CoIsHandlerConnected
对您的结果都是一样的。因此,没有必要这样做。
另一个注意事项是,如果远程对象已死(进程外服务器崩溃等),调用IDispatch::Invoke
仍然是安全的。代理只返回没有未定义行为的错误代码。也就是说,看起来您根本不需要CoIsHandlerConnected
,如果您在客户端进程的上下文中遇到访问冲突,那么您可能还有其他问题需要先解决。
【讨论】:
【参考方案3】:不,你应该永远是你QueryInterface
。
仅仅因为您提供了IUnknown
接口,并不意味着您可以直接将其转换为IDispatch
。 COM 可能给你一个底层对象的代理,这意味着指针与IDispatch
无关。
同样,一个实现可以包装一个实现IDispatch
的对象,当您调用QueryInterface
时,它会委托给这个对象。或者,您可能有一个指向委托给外部IUnknown
的 COM 对象的指针。
所以,基本上,永远不要直接施放,即使你认为它会起作用,因为事情可能会随着时间而改变。调用QueryInterface
很少会成为性能瓶颈,因此不值得避免。
【讨论】:
我有一个 IDispatch* 并将其转换为 IUnkown*。 IDispatch 派生自 IUnkown。你的回答是相反的,我完全同意这是错误的。**向下转型是危险的**,但据我所知,它并不乐观。不是吗? @StephaneRolland - 它仍然不安全。您的IDispatch
可能是整个对象的成员。通过向下转换,您将看到成员的 IUnknown
,而不是实际代表 COM 对象的控制 IUnknown
。以上是关于对于进程间 COM 对象,在不使用 QueryInterface 的情况下将 IDispatch* 转换为 IUnknown* 是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章