即使找到接口,CoCreateInstance 也会返回 E_NOINTERFACE
Posted
技术标签:
【中文标题】即使找到接口,CoCreateInstance 也会返回 E_NOINTERFACE【英文标题】:CoCreateInstance returning E_NOINTERFACE even though interface is found 【发布时间】:2010-12-19 09:48:48 【问题描述】:我有一个 COM 类 CMyCOMServer
在一个应用程序中实现 IMyInterface
,两者都具有正确的 GUID。如果请求 IUnknown 或 IMyInterface,CMyCOMServer::QueryInterface
将返回 S_OK(并将自身转换为正确的类型),否则返回 E_NOINTERFACE。
在同一台 PC 上的另一个应用程序中,我调用:
HRESULT hr = ::CoCreateInstance(__uuidof(CMyCOMServer), 0, CLSCTX_SERVER,
__uuidof(IMyInterface ),(void **)&pInterface);
它返回 E_NOINTERFACE。所以我认为我做错了什么并在CMyCOMServer::QueryInterface
上添加了一个断点。我发现当CoCreateInstance
被调用时,QueryInterface
会针对不同的接口触发多次:
(IMyInterface *)this
设置为接口指针,正如预期的那样
所以我的困惑是为什么调用 CoCreateInstance 给我一个 NULL 指针并返回 E_NOINTERFACE 代码,而 COM 服务器应用程序显然返回了我要求的接口?
编辑:我的客户端应用程序在启动时调用 CoInitialize(NULL),这没有区别。
【问题讨论】:
澄清一下:您的 COM 服务器在一个应用程序中运行,而客户端在另一个应用程序中运行?因为这意味着它们将处于不同的进程中,而这反过来意味着您需要编组,可能是自定义的。 是的。 1 台电脑上有两个独立的应用程序。不过,我以前从来没有搞乱编组,这就是我感到困惑的原因。我以前几乎没有听说过,我已经做了相当多的 COM 开发。 【参考方案1】:如果你的 COM 服务器运行在不同的进程中,或者在同一个进程中的不同单元中,COM 需要知道如何在你调用你的接口时打包和传递参数。此过程称为“编组”。
如果您定义自定义接口,则需要使用以下方法之一为其实现编组。
标准编组:让 MIDL 编译器生成代理 和存根,您必须在系统上注册。这可能是最好的选择,因为您已经定义了界面。 OLE 自动化编组:您定义一个自动化兼容 自定义界面并使用 marshaller 已经是 COM 框架 自定义编组:您实现 IMarshal 的方法当您调试 COM 服务器时,虽然您看到在调用 QueryInterface 时返回了您的自定义接口,但它并没有跨越进程边界,因为 COM 无法弄清楚如何编组该接口,因此客户端看到 E_NOINTERFACE。
更新(根据您的评论)
如果这是一个现有的 COM 服务器应用程序,那么您可能已经有一个代理/存根。您需要在客户端和服务器上注册它。会不会是您在一台新机器上测试它而只是忘记注册它?要注册,您只需在代理/存根 dll 上执行 regsvr32。
【讨论】:
正如我所说,服务器应用程序不是新代码。它已经在实时系统中使用 - 界面或任何东西都没有变化。在服务器端更改任何东西都不是一种选择,我需要让我的新客户端应用程序与现有的东西一起工作,因为其他客户端以某种方式设法这样做。 我认为这是最好的答案。事实证明我们确实有一个代理存根 DLL,我只是不知道它。使用 regsvr32 修复它。【参考方案2】:发生这种情况是因为 COM 子系统试图编组您的自定义接口 (IMyInterface) 并且根本不知道如何做到这一点。发生这种情况要么是因为服务器在进程外,要么是因为服务器在进程内,并且调用 CoCreateInstance() 的消费者应用程序的线程错误地调用了 CoInitialize()/ CoInitializeEx(),因此如上所述请求“多线程单元”文章中user Thomas指的是另一个答案。
如果您只需要一个进程内服务器,您可以通过确保调用 CoCreateInstance() 的线程调用 CoInitialize() 或 CoInitializeEx() 并使用 COINIT_APARTMENTTHREADED 来强制执行“单线程单元”来抑制编组。
如果您需要一个进程外服务器,则无法绕过编组。在后一种情况下,您可以执行以下操作之一:
实现 IMarshal - 最不推荐 添加代理/存根并为您的自定义界面注册它们 (不确定它是否适用于 out-proc,但它是最简单的)如果您的接口可以使用自动化编组器进行编组,只需在 COM 服务器的资源中包含一个 typlib 并在注册表中注册该类型库。李>【讨论】:
我不知道这意味着什么!我怀疑如果我这样做了,我的问题可能会得到解决。 是的,我现在有。它看起来很相关,但没有给我任何改变的想法。服务器应用程序已经运行多年,我只是将一个新的客户端 MFC 应用程序放在一起与之交互。我在那篇文章中没有看到任何关于如何“抑制编组”的提及。 关键是客户端如何调用CoInitializeEx()/CoInitialize()。它应该调用它来设置“公寓” - 使用 COINIT_APARTMENTTHREADED 调用 CoInitialize() 或 CoInitializeEx()。 如果您不强制执行“公寓”编组,则会启动。该设置针对客户端的每个线程单独完成。 我将以上内容包含在答案中。【参考方案3】:会不会是Raymond Chen wrote about的线程模型问题?
编辑回复评论:
如果您的线程模型与您正在创建的对象的线程模型不兼容,那么 COM 编组就会启动。如果编组的东西不存在,则出现的错误是 E_NOINTERFACE,因为编组接口是不见了。
实际上,它更多的是关于线程模型而不是编组。
【讨论】:
也许吧,但我从未使用过 Marshalling,据我所知,在这个项目上工作了 3 年后,拥有超过 100 个 COM 服务器 EXE/DLL。任何地方都没有定制编组或花哨的线程。所以我很困惑为什么现在应该是个问题。【参考方案4】:之前关于E_NOINTERFACE
的cmets因为缺少编组接口而返回非常有帮助,
然而,
对我们来说,答案/修复是强制主应用程序(调用 CoCreateInstance
的应用程序)为 STA
(单线程单元),这是通过设置高级链接器选项来完成的,即:
“CLR 线程属性”设置为“STA 线程属性”
或者在你做的链接命令行上:
"/CLRTHREADATTRIBUTE:STA"
这可以防止 MTA 和 STA 混合使用,从而导致跨线程调用。
希望其他人觉得这很有帮助。
【讨论】:
以上是关于即使找到接口,CoCreateInstance 也会返回 E_NOINTERFACE的主要内容,如果未能解决你的问题,请参考以下文章
CoCreateInstance 返回 E_INVALIDARG
调用 COM 服务器,C++ CoCreateInstance 抛出“不支持此类接口”,C# 正常工作
IWICImagingFactory 的 CoCreateInstance