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

我正在使用来自第三方库的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(); } } } 

我也尝试过以下方法:

  public MyComObjectWrapper() { CreateObject(); } 

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

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

Thread.Join的MSDN条目中,您可以看到这是您想要的:

阻塞调用线程直到线程终止,同时继续执行标准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) { } } 

在此示例中,控制台应用程序将处于永无止境的循环中,该循环不应再执行任何操作,然后响应COM组件中的事件。 如果这不起作用,您应该尝试获得COM组件供应商的支持。

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

需要注意的一点是,使用MTA,创建对象的线程无关紧要; 由MTA线程创建的所有对象同样属于所有MTA线程。 (或者,在COM中,一个进程只有一个multithreading单元,其中所有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(); } } 

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

IIRC,COM事件需要一个事件循环才能工作,这会抽取消息并调用Win32 GetMessage函数。

Winforms会为您执行此操作,或者您可以使用Win32调用来模拟它。 这个问题/答案有一个很好的例子,你可以建立 。

我认为以下应该有效:

 [STAThread] Main(...) { var comObject = new YourComObject(); comObject.Event += EventHandler; Console.WriteLine("Press enter to exit."); Console.ReadLine(); } void EventHandler(...) { // Handle the event } 

你定义了线程公寓模型吗?

  [STAThread] static void Main(string[] args) { // Create the thread that will manage the COM component Thread th = new Thread(...); // Before starting the thread th.SetApartmentState (ApartmentState.STA); } 

在线程中,只需等待事件发出信号即可终止。 当线程在等待事件时,我认为它应该在线程循环上处理消息。

你能试一试吗:

 static class Program { MyComObject m_Object; [STAThread] static void Main() { m_Object = new MyComObject(); m_Object.OnEvent += ObjectEvt; System.Windows.Forms.Application.Run(); } void ObjectEvt(/*...*/) { // ... } }