如何从控制台应用程序处理COM事件?

rav*_*ven 12 c# com events interop

我正在使用来自第三方库的COM对象来生成定期事件.当我使用Winforms应用程序中的库,将对象作为类成员并在主窗体线程中创建它时,一切正常.但是,如果我从另一个线程创建对象,我不会收到任何事件.

我的猜测是我需要在用于创建对象的同一个线程中有某种事件循环.

我需要从控制台应用程序中使用此对象.我想我可以使用Application.DoEvents,但我宁愿不在控制台App中包含Winforms命名空间.

我怎么解决这个问题?

更新3(2011-06-15):供应商终于回答了.简而言之,他们说Application.Run创建的消息泵与Thread.Join创建的消息泵之间存在一些差异,但他们不知道这有什么区别.

我同意他们; 任何关于此事的光明都会非常感激.

更新:

从理查德评论到mdm回答:

如果其他组件是单线程并从MTA实例化,则Windows将创建工作线程+窗口+消息泵并执行必要的编组.

试着听从他的建议,我正在做以下事情:

更新2:

我改变了JoãoAngelo回答后的代码.

using System;

namespace ConsoleApplication2
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            MyComObjectWrapper wrapper = new MyComObjectWrapper();
        }
    }

    class MyComObjectWrapper
    {
        MyComObject m_Object;
        AutoResetEvent m_Event;

        public MyComObjectWrapper()
        {
            m_Event = new System.Threading.AutoResetEvent(false);

            System.Threading.Thread t = new System.Threading.Thread(() => CreateObject());
            t.SetApartmentState (System.Threading.ApartmentState.STA);
            t.Start();

            Wait();
        }

        void ObjectEvt(/*...*/)
        {
            // ...
        }

        void Wait()
        {
            m_Event.WaitOne();
        }

        void CreateObject()
        {
            m_Object = new MyComObject();
            m_Object.OnEvent += ObjectEvt;

            System.Threading.Thread.CurrentThread.Join();
        }    
    }
}
Run Code Online (Sandbox Code Playgroud)

我也尝试过以下方法:

        public MyComObjectWrapper()
        {
            CreateObject();
        }
Run Code Online (Sandbox Code Playgroud)

Bre*_*McK 5

如果您正在使用STA,那么您将需要以某种方式消息循环.如果您不需要消息循环,MTA可能是最简单的方法,也是控制台式应用程序的最佳选择.

需要注意的一点是,使用MTA,创建对象的线程无关紧要; 由MTA线程创建的所有对象同样属于所有MTA线程.(或者,在COM中,一个进程只有一个多线程公寓,其中所有MTA线程都存在.)这意味着如果您采用MTA方法,则根本不需要创建单独的线程 - 只需从主线程创建对象.但是你还需要知道传入的事件将在"随机"线程上传递,因此你必须采取单独的步骤来与主线程进行通信.

using System;
using System.Threading;

class Program
{
    static MyComObject m_Object;
    static AutoResetEvent m_Event;


    [MTAThread]
    static void Main(string[] args)
    {
        m_Event = new AutoResetEvent(false);

        m_Object = new MyComObject();
        m_Object.OnEvent += ObjectEvt;

        Console.WriteLine("Main thread waiting...");
        m_Event.WaitOne();
        Console.WriteLine("Main thread got event, exiting.");
        // This exits after just one event; add loop or other logic to exit properly when appropriate.
    }

    void ObjectEvt(/*...*/)
    {
        Console.WriteLine("Received event, doing work...");

        // ... note that this could be on any random COM thread.

        Console.WriteLine("Done work, signalling event to notify main thread...");
        m_Event.Set();
    }
}
Run Code Online (Sandbox Code Playgroud)

您对以前版本的代码有几点评论:您在CreateObject和MycomObjectWrapper构造函数中调用了Wait(); 似乎你应该只有一个 - 如果你有两个,当调用m_Event.Set()时,只有其中一个会被释放,而另一个仍然在等待.另外,建议添加一些调试代码,以便了解您获得了多少.这样你至少可以告诉你是否从COM获得事件,并且单独地说,你是否成功地将它传回主线程.如果对象在注册表中标记为中性或两者都标记,那么从MTA创建它们应该没有问题.


Joã*_*elo 4

正如其他答案中已经指出的那样,STA COM 组件需要运行消息循环,以便将其他线程中发生的调用正确编组到拥有该组件的 STA 线程。

在 Windows 窗体中,您可以免费获得消息循环,但在控制台应用程序中,您必须通过调用Thread.CurrentThread.Join拥有 COM 组件且可能也是应用程序的主线程的线程来显式执行此操作。该线程必须是 STA。

从MSDN的条目中Thread.Join你可以看到这就是你想要的:

阻止调用线程直到线程终止,同时继续执行标准 COM 和 SendMessage 泵送。

如果您不想在主控制台线程中执行任何其他操作,您只需无限期地等待,否则您可以在定期调用Thread.CurrentThread.Join泵消息时执行其他操作。

旁注:这假设您正在处理 STA COM 组件。


一个简化的例子:

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        var myComObj = new MyComObject();

        myComObj.OnEvent += ObjectEvt;

        Thread.CurrentThread.Join(); // Waits forever
    }

    static void ObjectEvt(object sender, EventArgs e) { }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,控制台应用程序将处于永无休止的循环中,除了响应来自 COM 组件的事件之外,什么也不做。如果这不起作用,您应该尝试获得 COM 组件供应商的支持。