尽管找到接口,CoCreateInstance仍返回E_NOINTERFACE。

8

我有一个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 会多次触发不同的接口请求:
  • 首先,请求 IUnknown - 没有问题
  • 然后,请求几个诸如 IMarshall 等接口...这些接口不受支持,因此返回 E_NOINTERFACE
  • 最后,请求 IMyInterface。我验证 QueryInterface 返回 S_OK 并将 (IMyInterface *)this 设置为接口指针,正如预期的那样
因此,我的困惑是,当 COM 服务器应用程序明显返回我要求的接口时,为什么调用 CoCreateInstance 给我留下一个空指针和 E_NOINTERFACE 的返回代码?
编辑:我的客户端应用程序在启动时调用 CoInitialize(NULL),但这没有任何影响。

1
只是为了澄清:您的COM服务器在一个应用程序中运行,而客户端在另一个应用程序中运行?因为这意味着它们将在不同的进程中,这反过来意味着您需要编写可能是自定义的封送处理。 - MSalters
是的,它们可以在同一台电脑上使用两个分开的应用程序。然而我以前从没涉及过马歇尔技术,所以有点困惑。尽管我在COM开发方面做了不少工作,但之前对此几乎没有听说过。 - Mr. Boy
4个回答

6
这是因为COM子系统试图编组您的自定义接口(IMyInterface),但不知道如何执行。这种情况可能是由于服务器是外部进程,或者是因为服务器是内部进程且调用CoCreateInstance()的使用应用程序线程错误地调用了CoInitialize()/ CoInitializeEx(),从而请求了“多线程公寓”,正如user Thomas在另一个答案中提到的那样。
如果您只需要一个内部进程服务器,则可以通过确保调用CoCreateInstance()的线程使用COINIT_APARTMENTTHREADED调用CoInitialize()或CoInitializeEx()来抑制编组以强制实施"单线程公寓"。
如果您需要一个外部进程服务器,则无法避免编组。在后一种情况下,您可以执行以下操作之一:
- 实现IMarshal - 最不可取的 - 添加代理/存根并为您的自定义接口注册它们 - (不确定是否适用于外部进程,但最简单)如果您的接口可以使用自动化编组器进行编组,则将typlib包含到COM服务器的资源中,并在注册表中注册该typlib。

我不知道那是什么意思!我猜如果我知道了,我的问题可能就解决了。 - Mr. Boy
是的,我现在有了。它看起来相关,但没有给我任何改变的想法。服务器应用程序已经运行了多年,我只是随意编写了一个新的客户端MFC应用程序与之交互。我没有看到那篇文章中如何“抑制封送”的提及。 - Mr. Boy
关键在于客户端如何调用CoInitializeEx()/CoInitialize()。它应该调用它来设置“公寓” - 要么使用COINIT_APARTMENTTHREADED调用CoInitialize()或CoInitializeEx()。 - sharptooth
如果您不强制执行“公寓式”封送,则会触发封送。该设置针对客户端的每个线程单独完成。 - sharptooth
我已将上述内容包含在答案中。 - sharptooth
TLB/OLE自动化封送对于跨进程封送工作正常。 - Kim Gräsman

6
如果您的COM服务器运行在不同的进程中,或者在同一进程的不同公寓中,当您调用接口时,COM需要知道如何打包和传输参数。这个过程叫做“封送”。 如果您定义了自定义接口,您需要使用以下方法之一来实现它的封送。 标准封送:让MIDL编译器生成代理和存根,您必须在系统上注册它。这可能是最好的选择,因为您已经定义了接口。 OLE自动化封送:您定义一个自动化兼容的自定义接口,并使用已经成为COM框架一部分的封送器。 自定义封送:您实现IMarshal的方法。 当您调试您的COM服务器时,虽然您看到在QueryInterface的调用中返回了您的自定义接口,但它并没有跨越进程边界,因为COM无法弄清楚如何封送该接口,因此客户端会看到E_NOINTERFACE。 更新(基于您的评论): 如果这是一个现有的COM服务器应用程序,那么您可能已经拥有了代理/存根。您需要在客户端和服务器上都注册它。难道您是在新机器上测试这个吗?您是否忘记注册?要注册,只需在代理/存根dll上执行regsvr32即可。

正如我所说,服务器应用程序不是新代码。它已经在一个实时系统中使用-没有对接口或任何内容进行更改。在服务器端更改任何内容都不可行,因为其他客户端以某种方式成功地使用它,我需要让我的新客户端应用程序与现有的内容一起工作。 - Mr. Boy
我认为这是最好的答案。原来我们确实有一个代理存根DLL,只是我不知道而已。使用regsvr32修复了它。 - Mr. Boy

2
之前关于因缺少封送接口而返回 E_NOINTERFACE 的评论非常有帮助,但对我们来说,答案/解决方法是强制主应用程序(调用 CoCreateInstance 的应用程序)为 STA(单线程公寓),这是通过设置高级链接器选项完成的,即:

“CLR 线程属性”设置为“STA 线程属性”

或者在链接命令行上执行:

"/CLRTHREADATTRIBUTE:STA"

这可以防止MTA和STA混合,从而导致跨线程调用。
希望其他人也会发现这个有用。

2

这是否是与线程模型相关的问题,正如Raymond Chen所写的

针对评论的编辑:

如果您的线程模型与正在创建的对象的线程模型不兼容,则会启用COM封送处理。如果没有封送处理,则出现的错误是E_NOINTERFACE,因为缺少封送处理接口。

这实际上更多涉及线程模型而不是封送处理。


也许吧,但我从未使用过Marshalling,并且在这个项目上工作了3年,使用了超过100个COM服务器EXE/DLL,没有任何自定义的marshalling或花哨的线程处理。所以我很困惑为什么现在会成为一个问题。 - Mr. Boy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接