C# STAThread COMException

7

我有一个外部组件(C++),我想从我的C#代码中调用它。

代码大致如下:

using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace dgTEST
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            ExtComponentCaller extCompCaller = new ExtComponentCaller();
            result = extCompCaller.Call(input);

            Thread t = new Thread(new ThreadStart(() =>
            {
                try
                {
                    result = extCompCaller.Call(input);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }));

            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            t.Join();
        }
    }
}

问题在于第一次调用时它能够正常工作,外部组件被调用并返回结果。但是当我尝试在另一个线程中调用它时,就会出现异常:System.InvalidCastException: 无法将类型为 'System.__ComObject' 的COM对象强制转换为... 。我确定这个异常是由STAThread引起的。因为如果我从Main函数中删除[STAThread]属性,第一次调用该外部组件时也会出现相同的问题,而这次调用之前一切都很顺利。 我该如何在其他线程中调用此外部组件以摆脱这个异常?
更新 ----------------------------
现在发生了另一件疯狂的事情。 当我使用F5从Visual Studio启动程序时,第一个调用也会出现问题,但是当我直接执行二进制.exe文件时,它可以正常工作(从另一个线程中则不行 :( )。 如果我将构建从Debug切换到Release并从Visual Studio使用F5启动,则第一个调用再次正常工作。
这是为什么呢?谢谢您的帮助!

当您在标记为STA的线程中创建COM实例并运行方法时会发生什么?可能由于编码方式存在错误,此COM对象在注册表中被标记为STA,并且在不同的COM公寓之间(从MTA到STA或从STA到MTA)无法正常工作。 - Simon Mourier
我也遇到了同样的异常 :( 。 但是Main函数是STA,线程是在其中创建的。线程也被设置为STA,所以我不明白。 - Zoltán Barna
这可能是组件中的一个错误导致的。当所有操作都在一个单独的STA线程中完成时,它应该可以正常工作。 - Simon Mourier
你的意思是主线程是“单线程公寓”,并且在该线程上创建了一个COM对象。然后,你启动另一个线程(现在是多线程),并尝试从多个线程访问该COM对象(再次在单线程公寓中创建)。这不是真正支持的。 - Peter Ritchie
是的,我尝试在另一个线程中创建所有内容,但在这种情况下,Main函数不应该是[STAThread],否则我会得到相同的异常。感谢您的留言! - Zoltán Barna
1个回答

2

线程永远不是一个小细节。如果代码没有明确记录支持线程,则99%的可能性是它不支持线程。

很明显,这个组件不支持线程。创建另一个STA线程并不是解决方案,因为它仍然是一个不同的线程。InvalidCastException告诉您它也缺少代理/存根支持,这些支持是从工作线程(如您正在尝试创建的线程)调用调度所需的。对于不是线程安全的代码进行线程安全调用是必要的。虽然您已经打破了[STAThread]的契约,但它必须泵送消息循环。正是消息循环允许从工作线程向不是线程安全的组件进行调用。您可以从Application.Run()获取消息循环。

这就是止步之地。它不是线程安全的,无论如何。即使您修复了主线程或请求供应商或作者提供代理/存根,您仍然没有实现您想要做的事情,它实际上不会在您创建的工作线程上运行。因此,它必须像这样:

    static void Main(string[] args)
    {
        Thread t = new Thread(new ThreadStart(() =>
        {
             ExtComponentCaller extCompCaller = new ExtComponentCaller();
             result = extCompCaller.Call(input);
        }));

        t.SetApartmentState(ApartmentState.STA);
        t.Start();
        t.Join();
    }

这种方式在同一线程上创建对象,因此是线程安全的。但是,仍然存在一个问题,即此工作线程不会泵送消息循环,COM组件通常依赖于它。您可以通过死锁或未运行的事件来了解是否存在问题。如果在从主线程调用时已经在测试程序中正常工作,则可能无需进行泵送。


非常好的描述,感谢您的帮助! :)现在它在我的测试程序中运行。我将把它放入真实环境中,并希望一切都会很好 :)。 - Zoltán Barna

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