4

我遇到了这个问题中描述的问题: Unable to cast COM object of type exception 这表现为错误:

无法将“System.__ComObject”类型的 COM 对象转换为接口类型“IMyInterface”。此操作失败,因为 IID 为“{GUID}”的接口的 COM 组件上的 QueryInterface 调用因以下错误而失败:不支持此类接口

我的 WPF 应用程序正在调用最终调用 COM 对象的 .NET 库。这工作正常,但它在主线程上运行并阻塞 UI。当我生成一个新线程并从中调用库时,我收到了该错误。其他问题的这些解决方案都不适合我。我试图了解运行时如何加载类型信息,但不能在线程之间共享它。

我知道 WPF 应用程序是 STA,我知道这意味着在线程之间移动的任何对象都将被 COM 编组。我不明白如何说“这是一个 COM 对象,它的 GUID 是这个”的类型信息可以在 AppDomain 中,但第二个线程无法访问。

加载的类型信息在哪里?它是在 AppDomain 中,还是在每个线程中?无论如何,我怎样才能让线程共享类型信息?我怎样才能解决这个问题?

我读过这个: http:
//www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa
和这个:http:
//msdn.microsoft.com/en-us/ library/ms973913.aspx#rfacomwalk_topic10
和一堆其他关于 COM 互操作的东西,但没有帮助我修复它。

根据 Hans Passant 的回答,我正在第二个 STA 线程中创建我的库对象:

        var thread = new Thread(delegate()
        {
            var aeroServer = new AeroServerWrapper(Config.ConnectionString);
            var ct = new CancellationToken();
            aeroServer._server.MessageReceived += ServerMessageReceived;
            aeroServer.Go(@"M:\IT\Public\TestData\file.dat", ct);
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

我知道,如果事件未能触发,我可能需要调用 Application.Run() 来启动消息队列,但我并没有走那么远:它在尝试创建 COM 对象时立即崩溃。AeroServerWrapper 位于一个单独的 DLL 中,它调用第二个 DLL,最终尝试实例化 COM 对象。

任何提示或文章将不胜感激。我想按原样解决这个问题:我的 B 计划是将库包装在控制台应用程序中,从 UI 生成控制台应用程序,并通过命名管道从中获取状态消息。这会起作用,但它看起来很难看。

4

2 回答 2

6

这是错误的,因为您正在从另一个线程调用 COM 接口方法。COM 确保以线程安全的方式调用声明自己不是线程安全的 COM 类。注册表中的 ThreadModel 键指定了这一点,一个很常见的值是“Apartment”(或缺失),表示该类不是线程安全的。

因此,如果您从另一个线程调用接口方法,那么 COM 会介入并将调用编组到创建对象的线程,从而确保线程安全。一个非常不错的功能,完全从 .NET 类中丢失。但是,COM 在编组接口方法参数时需要帮助。必需,因为调用是在另一个线程上进行的,并且需要复制方法调用参数值。反射不是 COM 功能。它做的第一件事是查看注册表,HKCR\Interface\{guid}ProxyStubClsid 键的键,一个知道如何序列化参数的帮助类的 guid。

很明显,您的机器上缺少该密钥。接下来它会向 COM 对象询问 IMarshal 接口。显然您的 COM 服务器没有实现它。这会产生 E_NOINTERFACE 错误。产生好的错误信息从来不是 COM 的强项。

嗯,写在墙上。您使用的 COM 类不是线程安全的,并且缺少使其成为线程安全所需的所有管道。提供代理/存根通常很容易,但组件的作者没有打扰,并非完全不常见。并不是说如果他这样做会有所帮助,该管道确保该方法在 UI 线程上运行,这肯定是您首先要避免的。

您可以做的唯一合理的事情是创建自己的线程,调用其 SetApartmentState() 方法以切换到 STA 并在该线程上创建对象,以便以线程安全的方式使用该类。没有并发,但至少它与您的其余代码是并发的。该线程通常还必须泵送一个消息循环 Application.Run(),您可能会逃脱不泵送。当你看到死锁或事件没有被触发时,你就会知道你需要抽水。您可以在这篇文章中找到示例代码。

于 2012-07-13T14:15:12.210 回答
1

作为实际 COM 接口类型的包装器的 .NET 接口类型 IMyInterface 加载正常 - 您的问题不在于它在其他线程中以某种方式“不可见”。如您所见,您尝试转换的对象的实际类型是 System.__ComObject,它无论如何都没有实现该接口。在进行类型转换和类型检查(is/as 运算符)时,它不会像任何其他 .NET 对象一样被处理。发生的情况是 .NET 运行时通过调用QueryInterface向 COM 对象询问您尝试转换的接口。调用失败是因为 COM 对象没有正确编组(或其他原因:最终结果是失败)。它表现为 InvalidCastException,因为它是 .

于 2012-07-13T13:37:40.480 回答