111

这个线程(大约一年前发布)中,讨论了在非交互式会话中运行 Word 可能带来的问题。那里给出的(相当强烈的)建议是不要这样做。在一篇文章中指出“Office API 都假设您在桌面上的交互式会话中运行 Office,带有监视器、键盘和鼠标,最重要的是,还有一个消息泵。” 我不确定那是什么。(我使用 C# 编程仅一年左右;我的其他编程经验主要是使用 ColdFusion。)

更新:

我的程序运行大量的 RTF 文件,提取两条信息用于构建医疗报告编号。我没有尝试弄清楚 RTF 中的格式化指令是如何工作的,而是决定只在 Word 中打开它们并从那里提取文本(而不实际启动 GUI)。有时,该程序在处理一个文件的过程中会打嗝,并留下一个打开的 Word 线程附加到该文档(我仍然需要弄清楚如何关闭该线程)。当我重新运行程序时,我当然会收到一个通知,说有一个线程在使用该文件,我想打开一个只读副本吗?当我说是时,Word GUI 突然从无处弹出并开始处理文件。我想知道为什么会这样;

4

6 回答 6

204

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

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

GetMessage() Win32 API 从 Windows 检索消息。您的程序通常将 99.9% 的时间花在那里,等待 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 窗体或 WPF 程序的 UI 线程的入口点具有 [STAThread] 属性。其他线程的单元模型由 Thread.SetApartmentState() 方法设置。

如果 UI 线程不是 STA,Windows 管道的大部分将无法正常工作。值得注意的是拖放、剪贴板、Windows 对话框(如 OpenFileDialog)、控件(如 WebBrowser)、UI 自动化应用程序(如屏幕阅读器)。还有很多 COM 服务器,比如 Office。

STA 线程的硬性要求是它不应该阻塞并且必须泵送消息循环。消息循环很重要,因为 COM 使用它来将接口方法调用从一个线程编组到另一个线程。尽管 .NET 使封送调用变得容易(例如 Control.BeginInvoke 或 Dispatcher.BeginInvoke),但实际上这是一件非常棘手的事情。执行调用的线程必须处于众所周知的状态。你不能随意中断一个线程并强制它进行方法调用,这会导致可怕的重入问题。线程应该是“空闲的”,而不是忙于执行任何改变程序状态的代码。

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

于 2010-02-08T16:54:30.150 回答
13

“消息泵”是任何 Windows 程序的核心部分,负责将窗口消息发送到应用程序的各个部分。这是 Win32 UI 编程的核心。由于其无处不在,许多应用程序使用消息泵在不同模块之间传递消息,这就是 Office 应用程序在没有任何 UI 的情况下运行会中断的原因。

维基百科有一个基本的描述

于 2010-02-08T14:59:52.733 回答
8

John 正在谈论 Windows 系统(以及其他基于窗口的系统 - X Window、原始 Mac OS ......)如何通过消息系统使用事件来实现异步用户界面。

每个应用程序的幕后都有一个消息传递系统,其中每个窗口都可以向其他窗口或事件侦听器发送事件——这是通过将消息添加到消息队列来实现的。有一个主循环始终运行查看此消息队列,然后将消息(或事件)分派给侦听器。

Wikipedia 文章Microsoft Windows 中的消息循环显示了基本 Windows 程序的示例代码 - 正如您在最基本的层面上看到的那样,Windows 程序只是“消息泵”。

所以,把它拉到一起。旨在支持 UI 的 Windows 程序不能充当服务的原因是因为它需要始终运行消息循环以启用 UI 支持。如果您按照描述将其实现为服务,它将无法处理内部异步事件处理。

于 2010-02-08T15:01:27.237 回答
6

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

我猜想Word的消息泵要求源于 COM API 或应用程序的某些部分不是线程安全的。请记住,.NET线程和垃圾收集模型不能很好地与开箱即用的 COM 配合使用。COM 有一个非常简单的垃圾收集机制和线程模型,要求您以 COM 方式做事。使用标准Office PIA仍然需要您显式关闭 COM 对象引用,因此您需要跟踪创建的每个 COM 句柄。如果您不小心,PIA 还会在幕后制造一些东西。

.NET-COM 集成本身就是一个完整的主题,甚至有关于该主题的书籍。即使在交互式桌面应用程序中使用 Office 的 COM API 也需要您跳过障碍并确保明确发布引用。

可以假定 Office 是线程不安全的,因此您需要为每个线程提供一个单独的 Word、 Excel或其他Office应用程序实例。您将不得不承担启动开销或维护线程池。必须仔细测试线程池以确保正确释放所有 COM 引用。即使启动和关闭实例也需要您确保正确释放所有引用。没有点你的 i 和交叉你的 t 将导致大量的死 COM 对象,甚至整个运行的 Word 实例被泄露。

于 2011-06-14T09:11:51.293 回答
2

维基百科暗示这意味着程序的主要事件循环

于 2010-02-08T14:58:31.510 回答
1

我认为这个第 9 频道的讨论有一个很好的简洁解释:

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

于 2010-02-08T15:05:21.640 回答