无法将类型为COM对象进行转换的异常

21

我有以下代码:

public void Test(IMyInterface iInterface)
{
  iInterface.CallMethod ( );
}

这段代码是正常工作的。然而,如果我将代码改成线程化:

private IMyInterface myInterface;
public void Test(IMyInterface iInterface)
{
  myInterface = iInterface;
  new Thread ( new ThreadStart ( CallInterfaceMethod) ).Start ( );
}

public void CallInterfaceMethod ( )
{
  myInterface.CallMethod ( )
}

使用线程时,我收到以下异常:

无法将类型为“System.__ComObject”的COM对象强制转换为接口类型“IMyInterface”。 此操作失败,因为对具有IID“{GUID}”的接口的COM组件进行QueryInterface调用时出错:不支持此类接口

但是该接口应该被很好地支持?任何人对这里发生了什么有什么想法吗?


1
http://blogs.msdn.com/b/oldnewthing/archive/2004/12/13/281910.aspx - EricLaw
4个回答

22

这个可恶的异常是由于一个叫做COM marshalling的概念引起的。问题的本质在于为了从任何线程使用COM对象,该线程必须访问描述COM对象的类型信息。

在您描述的情况下,它在第二个线程中失败的原因是第二个线程没有该接口的类型信息。

您可以尝试将以下内容添加到您的代码中:

[ComImport]
[Guid("23EB4AF8-BE9C-4b49-B3A4-24F4FF657B27")]
public interface IMyInterface
{
    void CallMethod();
}
基本上,上面的声明指示.NET框架COM加载器使用传统技术从注册表中加载类型信息并定位相关的类型库,然后从那里继续。
您还应该将创建COM对象的限制为单个线程(以防止线程封送),以帮助解决此问题。
总之,这个错误围绕着类型信息和线程封送。确保每个想要访问COM对象的线程具有相关信息,以从源线程取消封送对象。
附注:.NET 4.0使用称为“类型等效”的技术解决了这个问题。

谢谢回复。你的解释很有道理,查阅MSDN上的ComImport声明也很有道理。干杯。 - Kyle
很高兴能帮忙:) 我之前遇到过这个问题,尝试解决起来非常困难,直到我恍然大悟,从创建它的线程中使用了COM对象。 - Mike J
1
非常感谢大家!对我来说,错误在于缺少[STAThread]。这个问题和答案让我在阅读线程问题后找到了它。 - Marc
ComImport和Guid就是它!我曾经为了让我的包装器可测试并隔离COM组件而苦苦挣扎,遇到了同样的转换异常(与线程无关)。谢谢! - J Pollack

4

我得到了一个建议,它帮助了我!

在主线程(Program.cs)中找到[STAThread]这一行,并将其更改为[MTAThread]。


它像魔法一样奏效了。你能解释一下这个变化吗? - Mohammad Qasim

1
我一直在开发一个使用7-zip的C#应用程序,通过COM接口。我遇到了这样一个有趣的问题,在一个实例中我能够从工作线程中提取存档,但在另一个实例中却不能,并得到了相同的异常。
我发现,只要在使用该对象的线程中初始化有问题的COM对象,就不会抛出异常。我的解决方案是处理利用COM接口的对象并在它们在线程之间传递时重新初始化它们。

-1
首先,你在没有锁定对象的情况下进行了跨线程的调用,这会自动导致一些问题。你的代码应该像下面这样:
private IMyInterface myInterface;
private static readonly object _myObjectLock = new object();

public void Test(IMyInterface iInterface)
{
     myInterface = iInterface;
     new Thread ( new ThreadStart ( CallInterfaceMethod) ).Start ( );
}

public void CallInterfaceMethod ( )
{
     lock(_myObjectLock)
     {
        myInterface.CallMethod ( );
     }
}

据我所知,你列出的错误有时会发生在无法访问资源时,特别是像这样的跨线程操作。但是不要引用我的话,我不是COM专家。
说实话,我认为我不会以这种方式调用此方法,这样做风险太大了。您是否考虑使用ParameterizedThreadStart并通过该方式传递对象?您仍需要安全地锁定对象以进行跨线程操作,但这样更安全。
此外,请检查您的“myInterface”类是否仍然可以调用“CallMethod()”方法。接口没有实现,当您设置“myInterface = iInterface”时可能会遇到问题。

谢谢您的回答,但使用ParameterizedThreadStart也不起作用(无论是否使用锁定)。此外,我已经检查过,一旦设置了myInterface(myInterface = iInterface),它仍然可以“CallMethod”。 - Kyle
这个答案中有两个错误信息:1. 跨线程对象访问并非总是会自动引起问题。例如,只读访问不需要锁定。 2. 关于最后一段,你不能将接口作为对象传递,因此,即使你向一个具有接口(静态)类型的变量分配了一个非空对象,并且代码编译通过了,那么所有这些方法仍然可以被调用。将静态类型(接口)视为另一种实现类型的外观。 - stakx - no longer contributing

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