如何处理来自控制台应用程序的 COM 事件?
Posted
技术标签:
【中文标题】如何处理来自控制台应用程序的 COM 事件?【英文标题】:How to handle COM events from a console application? 【发布时间】:2011-09-05 08:23:39 【问题描述】:我正在使用来自第三方库的 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 代码的线程模型是什么?你在哪个 COM 公寓里跑? @Richard:我在 STA 中运行。如何检查第 3 方组件的型号?和我的 COM 代码?什么COM代码? 第 3 方组件的线程模型将在注册表中。 “你是 COM 代码”:你传递给第 3 方组件的接口的实现,以便它用事件回调。 @Richard:我不确定。我在 HKCR/CLSID/UUID/InProcServer32 下找到了对具有不同 UUID 的对象的引用。其中一些具有 ThreadModel = "Neutral",一些具有 ThreadModel = "both"。关于我的COM代码,我仍然不知道你在说什么。我只是在我的类中实例化对象并添加一个事件处理程序,就像在 Daniel Hilgarth 代码中一样...... 【参考方案1】:如果您使用的是 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();
之前版本代码中的几个 cmets:在 CreateObject 和 MycomObjectWrapper 构造函数中都调用了 Wait();似乎你应该只有一个 - 如果你有两个,当 m_Event.Set() 被调用时,只有一个会被释放,另一个仍然会等待。另外,建议添加一些调试代码,这样你就知道你走了多远。这样,您至少可以判断您是否从 COM 获取事件,以及您是否成功地将事件传回主线程。如果对象在注册表中被标记为中性或两者都标记,那么从 MTA 创建它们应该没有问题。
【讨论】:
感谢您的回答。我使用断点来检查事件; ctor() 等待调用只是为了让主线程没有结束。无论如何,我已经复制了您的代码,但它卡在“主线程等待...”中,没有调用该事件。同时,我还在等待供应商的答复。【参考方案2】:正如在其他答案中已经说明的那样,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。如果是这样,我该怎么办? 我认为永远不要从班上的 ctor 回来不是一个好主意。您应该从 ctor 外部调用Wait
方法。
这行不通,您需要等待 Windows 消息循环,而不是等待“什么都没有”。
@Simon Mourier,不适用于 OP 试图做的事情以及他正在使用的 COM 组件。如果您对此有疑问,我可以举一个与 Word 自动化配合使用的示例。
有趣的是那个 MSDN 页面明确说不要做你正在做的事情 ;-) “你永远不应该从当前线程调用代表当前线程的 Thread 对象的 Join 方法。这会导致你的应用程序挂起,因为当前线程无限期地等待自己,“【参考方案3】:
IIRC,COM 事件需要一个事件循环才能工作,它可以泵送消息并调用 Win32 GetMessage
函数。
Winforms 会为您执行此操作,或者您可以使用 Win32 调用来模拟它。 This question/answer has a good example you can build on.
【讨论】:
IIRC 另一种选择是在 MTA 中运行的另一个线程中完成工作。在 MTA 中,COM 调用是直接进行的,不需要窗口、消息队列和消息泵。 很好的调用 - 但不是必须专门构建 COM 对象才能在一种模式或另一种模式下工作吗?如果 OP 无法更改 COM 对象怎么办? 哪个,顺便说一句,不能。总之很高兴知道。 @mdm:如果有其他组件是单线程的并从 MTA 实例化,那么 Windows 将创建工作线程 + 窗口 + 消息泵并进行必要的编组。 @Richard:我已经尝试按照您说的做,但没有成功。查看更新的问题。【参考方案4】:我认为以下应该可行:
[STAThread]
Main(...)
var comObject = new YourComObject();
comObject.Event += EventHandler;
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
void EventHandler(...)
// Handle the event
【讨论】:
不会的。您至少必须调用 Application.DoEvents() 因此这并不总是有效【参考方案5】:你定义了线程单元模型吗?
[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);
在线程中,只需等待一个事件发出终止信号。当线程等待事件时,我认为它应该在线程循环上处理消息。
【讨论】:
【参考方案6】:你可以试试这个:
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(/*...*/)
// ...
【讨论】:
OP 倾向于不将System.Windows.Forms
引用添加到控制台应用程序项目,因为您可以在没有该引用的情况下发送消息,我倾向于同意他的观点。
@Simon,这行得通,但正如 João 所写,我试图避免 Winforms 引用。还是谢谢你。
@Jaime - 如果它正常工作并且您只使用为此专门设计的方法,我看不到引用 Windows 窗体的问题,因为您已经添加了对依赖于 COM 对象的引用Windows 基础设施工作。 PS:这和Application.DoEvents不一样。
@Simon,这就是我现在正在做的事情。但是如果我需要重新创建 COM 对象怎么办?我不需要终止 Application.Run 线程吗?我该怎么做?
@Jaime - Application.Run 不是一个线程,它是一个模态窗口消息循环(PeekMessage、TranslateMessage、DispatchMessage),独立于确保窗口消息可以在应用程序中流动的对象。您可以通过调用 PostQuitMessage windows 函数退出循环(您可以使用 P/Invoke 来声明它)。您对对象重新创建有什么(或认为会有)什么问题?以上是关于如何处理来自控制台应用程序的 COM 事件?的主要内容,如果未能解决你的问题,请参考以下文章
如何处理来自 Presenter 的 Datagrid 列的事件?
如何处理来自 Google Cloud 存储的应用程序/八位字节文件?