如何从控制台应用程序处理COM事件?
如何从控制台应用程序处理COM事件?
我正在使用一个第三方库的COM对象,它会生成周期性事件。当我在Winforms应用程序中使用该库时,如果将该对象作为类成员并在主窗体线程中创建它,一切正常。然而,如果我从另一个线程创建该对象,我就收不到任何事件。
我猜我需要在用于创建该对象的同一线程中有某种类型的事件循环。
我需要从控制台应用程序中使用这个对象。我猜我可以使用Application.DoEvents,但我宁愿不在控制台应用程序中包含Winforms命名空间。
我该如何解决这个问题?
更新3(2011-06-15):供应商终于回复了。简而言之,他们说Application.Run创建的消息泵与Thread.Join创建的消息泵之间存在一些差异,但他们不知道具体差异是什么。
我同意他们的观点;如果对这个问题有所了解,将非常感激。
更新:
根据Richard对mdm答案的评论:
如果其他组件是单线程的,并从MTA实例化,则Windows将创建工作线程+窗口+消息泵,并执行必要的封送。
尝试遵循他的建议,我正在进行以下操作:
更新2:
我根据João Angelo的答案更改了代码。
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(); }
在控制台应用程序中处理COM事件的方法是使用消息循环来确保在其他线程中发生的调用能够正确地转发到拥有该组件的STA线程。在Windows Forms中,消息循环是自动完成的,但在控制台应用程序中,您必须显式地调用拥有COM组件的线程上的Thread.CurrentThread.Join方法。这个线程必须是STA线程。
以下是一个简化的示例:
class Program { [STAThread] static void Main(string[] args) { var myComObj = new MyComObject(); myComObj.OnEvent += ObjectEvt; Thread.CurrentThread.Join(); // 永久等待 } static void ObjectEvt(object sender, EventArgs e) { } }
在这个示例中,控制台应用程序将处于一个永不结束的循环中,只需响应来自COM组件的事件。如果这不起作用,您应该尝试从COM组件供应商那里获取支持。
如果您不想在主控制台线程中做其他事情,您可以无限期地等待,否则您可以在定期调用Thread.CurrentThread.Join来处理消息时做其他事情。
如果您不想在类的构造函数中永远不返回,您应该在构造函数外部调用Wait方法。
这种方法无法工作,您需要在Windows消息循环上等待,而不是"无所事事地等待"。
Mourier,您正在等待Thread.Join,它执行标准的COM和SendMessage消息泵。
Mourier,对于OP正在尝试做的事情以及他使用的COM组件来说,这是行不通的。如果您对此有疑问,我可以给您一个使用Word自动化的示例。
João,我将尝试从供应商那里获取支持。我得到答复后会更新。非常感谢。
我无法解决我的问题,但我认为这个回答以一种普遍的方式回答了问题,所以我会接受它。
有趣的是,MSDN页面明确表示不要做您正在做的事情;-) "您不应该从当前线程调用表示当前线程的Thread对象的Join方法。这会导致您的应用程序挂起,因为当前线程无限期地等待自己。"
Mike,你注意到了,此外,如果这个调用会阻塞直到线程终止,那么如何按照OP的建议定期调用Thread.CurrentThread.Join()?这个答案很有用,因为它提到了Thread.CurrentThread.Join(),但是不正确...
一个解决方法是在循环中使用有时间限制的Thread.Join(Int32)方法。
如何从控制台应用程序处理COM事件?
如果您正在使用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(); } }
对于你之前的代码版本,有几点评论:你在CreateObject和MycomObjectWrapper构造函数中都调用了Wait();似乎应该只有一个-如果有两个,当调用m_Event.Set()时,只有一个会被释放,而另一个仍然等待。此外,建议添加一些调试代码,以便您知道您的进展如何。这样,您至少可以知道是否从COM获取了事件,并且另外,您是否成功将其传递给主线程。如果对象在注册表中标记为中性或双重,则从MTA创建它们应该没有问题。
感谢您的回答。我使用断点来检查事件;构造函数中的wait调用只是为了防止主线程结束。无论如何,我复制了您的代码并且在“Main thread waiting...”处停止了,事件并未被调用。与此同时,我仍在等待供应商的答复。
问题的出现原因是COM事件需要一个事件循环来工作,这需要通过调用Win32的GetMessage函数来实现。在Winforms中,这一切都由系统自动处理,或者可以使用Win32调用来模拟。但是在控制台应用程序中,没有自动的事件循环,因此需要手动处理。
解决方法有以下几种:
1. 可以使用Win32调用来模拟事件循环。可以参考这个问题/答案stackoverflow.com/questions/764869中的示例代码进行参考。
2. 另一种方法是在MTA中创建一个运行在另一个线程中的工作线程,这样COM调用可以直接进行,而不需要窗口、消息队列和消息循环。
3. 如果COM对象不能被修改,那么可以尝试创建一个匹配其模型的测试组件,以及足够理解发生的情况的回调接口。
以上是对于这个问题的原因和解决方法的整理。需要注意的是,具体的解决方法可能因为不同的情况而有所不同,如果遇到具体的问题,最好提出一个详细的新问题进行讨论。