为什么从C#调用C ++ COM函数总是在主线程中执行?

时间:2015-02-12 10:32:58

标签: c# c++ multithreading com

我有一个MFC C ++ Activex控件,我从后台线程DoWork()函数调用一个Activex类函数。我发现它阻止了主线程。另一方面,如果我在C#中用Thread.Sleep(5000)替换对c ++ COM函数的调用(需要5秒钟才能执行),它在后台线程中工作。

我尝试了很多东西,比如在工作线程中创建activex类的实例。但似乎COM函数总是在主线程上执行。我还附上了示例项目。

/* Here's the C# code */
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void exit_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private void cpp_Click(object sender, EventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Marquee;
        this.backgroundWorker1.RunWorkerAsync(1);
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        if (Convert.ToInt16(e.Argument) == 1)
        {
            // C++ function that takes 5 seconds to execute.
            this.axActivexComponent1.AboutBox();
        }
        else
        {
            /* Normal C# code, for this it works fine */
            System.Threading.Thread.Sleep(5000);
        }
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Blocks;
        MessageBox.Show("Thread Completed!!!");
    }

    private void csharp_Click(object sender, EventArgs e)
    {
        this.progressBar1.Style = ProgressBarStyle.Marquee;
        this.backgroundWorker1.RunWorkerAsync(2);
    }
}

3 个答案:

答案 0 :(得分:2)

它是公寓的COM概念 - ActiveX控件属于单线程公寓,与您引用的线程相关联,#34;主线程"。从该公寓对COM接口的所有调用都将传输到主线程,包括从后台工作线程进行的调用。

COM为您提供编组以创建透明的代理/存根通道以使用来自其他线程的那些接口,但是在它们下面它们仍然会阻塞主线程,因为无论如何都会在真实对象上进行最终调用。

要使用多个线程,您需要多个线程的公寓感知COM对象(尽管所有或几乎所有ActiveX控件都使用单线程单元模型),或者控件应该提供自定义编组器,例如自由线程编组器,能够绕过默认的线程同步。

答案 1 :(得分:1)

COM对象的threading model听起来像是STA(单线程公寓):

标记为STA的COM对象必须在STAThread上运行,并且不能传递给其他线程,这是MFC C ++中任何UI元素的情况。但是,您的程序仍然可以有很多线程。

公寓状态默认为STA。例如,您的客户端应用程序中的主要入口点很可能标记为:

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {         

如果对COM对象使用单独的/非UI线程,则需要在STA线程中显式,而背景/线程池工作线程不是这种情况。您必须自己创建线程,然后在该线程上实例化COM对象,例如:

Thread t = new Thread(new ThreadStart(ThreadProc));
t.SetApartmentState(ApartmentState.STA);

t.Start();

此外,STA要求必须有某种形式的“消息泵”(很像主要形式的UI线程使用的那种)。有关详细信息,请参阅this

答案 2 :(得分:0)

通常的原因是COM对象无法在工作线程上运行。 COM是一种古老的技术,在线程化之前很常见。如果从多个线程调用,很多COM对象都会中断。因此,COM默认是在主线程上调用一个对象,除非它被标记为线程安全。

您无法通过C#代码修复此问题。