什么是消息泵(Message Pump)?

116
这个帖子(大约一年前发布),讨论了在非交互式会话中运行Word可能出现的问题。那里给出了(非常强烈的)建议不要这样做。其中一个帖子中指出:“Office API都假定您在桌面上以交互式会话运行Office,并拥有显示器、键盘和鼠标,最重要的是,一个消息泵。”我不确定那是什么。(我只学习C#编程约一年,我的其他编程经验主要是用ColdFusion。)
更新: 我的程序通过大量RTF文件提取两个信息来构建医疗报告编号。我决定不尝试弄清楚RTF中的格式说明,而是在Word中打开它们并从那里提取文本(但没有启动GUI)。偶尔,在处理一个文件时,程序会卡住,留下一个连接到该文档的Word线程(我还要弄清楚如何关闭它)。当我重新运行程序时,当然会收到一个通知,告诉我有一个正在使用该文件的线程,是否要打开只读副本?当我选择“是”时,Word GUI突然从无处弹出并开始处理文件。我想知道为什么会发生这种情况;但看起来一旦对话框弹出,消息泵就开始将主GUI推送到Windows上了?
6个回答

214

消息循环是存在于任何本地Windows程序中的一小段代码。它大致看起来像这样:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{ 
   TranslateMessage(&msg); 
   DispatchMessage(&msg); 
} 

GetMessage()是Win32 API中的一个函数,它从Windows系统获取消息。大多数情况下,程序会在此处等待Windows通知它发生了一些有趣的事情。TranslateMessage()是一个帮助函数,用于转换键盘消息。DispatchMessage()确保窗口处理过程被调用以处理这些消息。

每个启用GUI的.NET程序都具有消息循环,它由Application.Run()启动。

与Office相关的消息循环的重要性与COM有关。Office程序是启用COM的程序,这就是Microsoft.Office.Interop类的工作方式。COM代表COM coclass处理线程,在COM接口上进行的调用始终来自正确的线程。大多数COM类在注册表中都有一个声明其ThreadingModel的注册表键,其中最常见的(包括Office)使用“Apartment”。这意味着调用接口方法的唯一安全方式是从创建类对象的同一线程进行调用。换句话说,大多数COM类不是线程安全的。

每个启用COM的线程都属于一个COM公寓。有两种公寓,单线程公寓(STA)和多线程公寓(MTA)。必须在STA线程上创建公寓线程化的COM类。您可以在.NET程序中看到这一点,Windows Forms或WPF程序的UI线程入口点具有[STAThread]属性。其他线程的公寓模型由Thread.SetApartmentState()方法设置。

如果UI线程不是STA,Windows管道的大部分功能将无法正常工作。特别是拖放、剪贴板、像OpenFileDialog这样的Windows对话框、像WebBrowser这样的控件、屏幕阅读器等UI自动化应用程序以及许多COM服务器,如Office。

STA线程的严格要求是它不能阻塞并且必须抽象一个消息循环。消息循环很重要,因为这是COM用于从一个线程传递接口方法调用到另一个线程的方式。尽管.NET使调用易于处理(例如Control.BeginInvoke或Dispatcher.BeginInvoke),但实际上这是一件非常棘手的事情。执行调用的线程必须处于已知状态。您不能随意中断线程并强制它进行方法调用,否则会引起可怕的重入问题。线程应该是“空闲的”,没有忙于执行任何正在改变程序状态的代码。

也许您可以看到它的含义:是的,当程序执行消息循环时,它是空闲的。实际的传递通过COM创建的隐藏窗口进行,它使用PostMessage让该窗口过程在STA线程上执行代码。消息循环确保此代码运行。


1
非常好的详细回答。只是补充一下 - 还有一个特殊的STA称为主STA,它是第一个创建的STA。理想情况下,应该由您的UI线程创建主STA。主STA是创建具有线程模型= none组件的地方。如果您的主STA不是由您的UI线程创建的,则在使用具有线程模型none的较旧的ActiveX控件时可能会遇到有趣的问题。 - quixver

14

"消息泵"是任何Windows程序的核心部分,负责将窗口消息分派到应用程序的各个部分。这是Win32 UI编程的核心。由于其普及性,许多应用程序使用消息泵在不同的模块之间传递消息,这就是为什么如果没有UI运行Office应用程序会崩溃的原因。

维基百科有一个基本描述


我相信在Windows应用程序中没有消息循环是不可能的,因此所有应用程序都使用消息泵。 - Hogan
3
你也可以编写简单的GUI应用程序而不需要一个 - 例如,你可以弹出消息框而不需要在你的应用程序中拥有自己的消息循环。 - anon
1
如果您通过DialogBox或间接DialogBox创建对话框,则不需要消息循环,只需提供一个函数(dlgproc),Windows将调用该函数。 (而消息框只是一个简单的对话框) - quixver
1
@quixver 但是MessageBox有自己的隐藏默认循环,对吗? - ScienceDiscoverer

8
约翰正在谈论Windows系统(和其他基于窗口的系统 - X Window,原始Mac OS等)如何使用消息系统通过事件实现异步用户界面。对于每个应用程序,在幕后都有一个消息系统,其中每个窗口都可以向其他窗口或事件侦听器发送事件 - 这是通过将消息添加到消息队列来实现的。有一个主循环始终运行,查看此消息队列,然后将消息(或事件)分派给侦听器。维基百科文章Microsoft Windows中的消息循环显示了基本Windows程序的示例代码 - 正如您所看到的,最基本的Windows程序只是“消息泵”。因此,为了将所有内容汇总起来,支持UI的Windows程序之所以不能充当服务是因为它需要始终运行的消息循环以启用UI支持。如果按照所述将其实现为服务,则无法处理内部异步事件处理。

6

COM中,消息泵序列化和反序列化在公寓之间发送的消息。公寓是可以运行COM组件的小型进程。公寓有单线程和自由线程模式。单线程公寓主要用于不支持多线程的COM组件的应用程序的遗留系统。它们通常与Visual BASIC一起使用(因为它不支持多线程代码)和旧版应用程序。

我猜Word对消息泵的需求源于COM API或应用程序的某些部分不是线程安全的。请记住,.NET线程和垃圾收集模型与COM默认不兼容。COM具有非常简单的垃圾回收机制和线程模型,需要按照COM方式进行操作。即使使用标准的Office PIAs,仍需要显式关闭COM对象引用,因此需要跟踪每个创建的COM句柄。如果不小心,PIAs也会在幕后创建东西。

.NET-COM整合是一个独立的主题,甚至有专门的书籍介绍。即使从交互式桌面应用程序中使用COM API来操作Office,也需要您跳过一些障碍,并确保引用被显式释放。
可以假定Office不支持多线程,因此每个线程都需要一个单独的Word、Excel或其他Office应用程序实例。您必须承担启动开销或维护线程池的成本。线程池必须经过精心测试,以确保所有COM引用正确释放。即使是启动和关闭实例,您也需要确保所有引用都被正确释放。如果在这里有任何疏漏,将导致大量未释放的COM对象,甚至会泄漏整个运行中的Word实例。

2
你的回答有一些不准确。有三种类型的公寓 - STA(单线程)、MTA(多线程)和NTA(中性线程)。Free threaded用于描述聚合自由线程的组件,不存在所谓的自由线程公寓。COM使用消息与STA通信。对于生存在MTA中的组件(或聚合自由线程marshaller的组件),不需要消息循环。据我所知,lpc用于将数据从调用线程传输到来自RPC线程池的线程,然后实际调用该方法。 - quixver

2

1

我认为这个 Channel 9 的讨论有一个很好的简明解释:

窗口通信的这个过程是通过所谓的 Windows 消息泵实现的。将消息泵视为一种实体,它使应用程序窗口和桌面之间能够协作。


2
哇哦...那是一个可怕且误导性的引语。(“实体”?嗯..不对。) - Hogan
5
实体 - 对象:指存在或被感知为单独分离物的事物。 - Matthew Whited

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接