首先我将解释消息循环在互操作场景中的工作原理,然后我将回答您的问题并给出一些建议。
场景中的消息循环实现
您的方案涉及三种不同的技术:VB 6、WinForms 和 WPF。这些技术中的每一项都是在 Win32 之上实现的。每个都有自己的GetMessage()/DispatchMessage() 循环来泵送 Win32 窗口消息。
这里是每个GetMessage()/DispatchMessage() 循环的实现位置:
- VB 6 在内部实现它
- WinForms 在
System.Windows.Forms.Application 中实现它
- WPF 在
System.Windows.Threading.Dispatcher 中实现它
WPF 应用程序对象是可选的
您的问题假设 WPF 在 Application 对象中实现消息循环。不是这种情况。在 WPF 中,WinForms 在 Application 对象中处理的所有基本功能都已移至其他对象,例如 Dispatcher、HwndSource、InputManager、KeyboardDevice、MouseDevice 等。
在 WPF 中,Application 对象是完全可选的。您可以构建具有复杂 UI 的完整 WPF 应用程序,而无需创建 Application 对象。应用程序对象仅在您需要它提供的服务之一时才有用,例如:
- 一个普通的
ResourceDictionary
- 将
WM_APPACTIVATE 消息映射到Activated 和Deactivated 事件中
- 将
WM_QUERYENDSESSION 消息映射到OnSessionEnding 事件中
- 生命周期管理(启动/运行/关闭/退出)
- 当最后一个窗口或主窗口关闭时自动关闭
- WPF 窗口的默认图标
- 记住打开的第一个窗口 (
MainWindow)
- NavigationService 事件的通用注册(
Navigated 等)
StartupUri
Application 类还提供了几个有用的静态成员,例如 FindResource、GetResourceStream 和 LoadComponent,它们不需要 Application 对象存在。
当您拨打Application.Run() 时,它所做的只是:
- 安装处理
WM_APPACTIVATE和WM_QUERYENDSESSION的机制,并且
- 执行
Dispatcher.Run()
所有实际的消息循环功能都在Dispatcher.Run() 中。
在 WPF 消息循环中注册未处理的异常
您尝试使用的Application.DispatcherUnhandledException 事件是Dispatcher.UnhandledException 事件的简单包装器。我认为他们将它包含在 Application 对象中是因为 WinForms 程序员希望它存在,但您的问题表明这可能适得其反。
要从 WPF 的 Dispatcher 注册未处理的异常,您所要做的就是:
Dispatcher.Current.UnhandledException += ...;
与Application.Current 不同,Dispatcher.Current 不能为空:如果您从一个还没有 Dispatcher 的线程访问 Dispatcher.Current,则会自动创建一个。
一旦您订阅了Dispatcher.UnhandledException,当前线程上的Dispatcher 消息循环 中的任何未处理的异常都会导致您的事件处理程序被调用。请注意,这仅适用于Dispatcher.Run() 正在泵送消息时的未处理异常:当 VB 6 或 WinForms 等其他技术正在泵送消息时,将使用该技术的异常处理机制。
WPF 消息循环也是可选的
WPF 不仅可以在不创建应用程序对象的情况下运行,它还可以在不使用Dispatcher.Run() 的情况下运行,只要另一种技术正在泵送 Win32 窗口消息。这是通过创建一个虚拟窗口和/或子类化 WPF 窗口来安装消息挂钩来完成的。因此,无论哪个消息循环在泵送消息,WPF 都会按预期工作。
实际上,当您使用ElementHost 时,WPF Dispatcher 不会用于消息泵送,除非您使用以下方法之一:
Window.ShowDialog
Dispatcher.Invoke
-
Dispatcher.Run(或等效的 Application.Run)
DispatcherOperation.Wait
因此,您的 WPF 异常处理程序可能不会被调用。相反,您需要在 VB 6 或 WinForms 级别安装异常处理程序。
回答您的问题
在这种情况下,是否曾经创建过 WPF 应用程序对象?
没有。
如果不是,是什么导致消息被抽出?
VB 6 正在发送消息。
如果我在后台线程上启动 Application 对象会发生什么?
很少:
如果您有应用程序资源,这些资源将在后台线程上创建,当它们在主线程上使用时可能会导致异常。
如果将处理程序添加到 Application.Current.DispatcherUnhandledException,它将仅适用于后台线程。换句话说,除非您在后台线程上创建窗口,否则永远不会调用处理程序。
将从后台线程调用您的 Application.Startup,这可能是一件坏事。 StartupUri 也是如此。
推荐
从您的要求看来,您在加载 WPF 控件期间遇到了未处理的异常,并且您希望捕获该异常。在这种情况下,最好的计划可能是将您的 WPF 控件包装在一个简单的 ContentControl 中,其构造函数使用这样的代码来构造子对象:
Dispatcher.Current.UnhandledException += handler;
Disptacher.Current.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
{
Content = CreateChildControl();
Dispatcher.Current.Invoke(DispatcherPriority.ApplicationIdle, new Action(() => {});
});
它是如何工作的:BeginInvoke 会延迟子项的构建,直到 VB 6 和/或 InteropControl 完成所有处理。创建子控件后的 Invoke 调用以低优先级调用空操作,导致所有待处理的 DispatcherOperations 完成。
最终结果是,在构造函数内或之后引发的任何异常现在都会传递给您的异常处理程序。