Win32 消息处理程序错误传播
Posted
技术标签:
【中文标题】Win32 消息处理程序错误传播【英文标题】:Win32 Message Handler Error Propagation 【发布时间】:2010-11-13 05:32:39 【问题描述】:我正在编写一个使用单个对话框的 (C++) 应用程序。
设置消息泵和处理程序后,我开始想知道如何将 C++ 异常传播到我的原始代码(例如,调用 CreateDialogParam
的代码)。
这是我的意思的一个骨架示例:
BOOL CALLBACK DialogProc(HWND, UINT msg, WPARAM, LPARAM)
if(msg == WM_INITDIALOG) //Or some other message
/*
Load some critical resource(s) here. For instnace:
const HANDLE someResource = LoadImage(...);
if(someResource == NULL)
---> throw std::runtime_error("Exception 1"); <--- The exception handler in WinMain will never see this!
Maybe PostMessage(MY_CUSTOM_ERROR_MSG)?
*/
return TRUE;
return FALSE;
//======================
void RunApp()
const HWND dlg = CreateDialog(...); //Using DialogProc
if(dlg == NULL)
throw std::runtime_error("Exception 2"); //Ok, WinMain will see this.
MSG msg = ;
BOOL result = 0;
while((result = GetMessage(&msg, ...)) != 0)
if(result == -1)
throw std::runtime_error("Exception 3"); //Ok, WinMain will see this.
//Maybe check msg.message == MY_CUSTOM_ERROR_MSG and throw from here?
TranslateMessage(&msg);
DispatchMessage(&msg);
//======================
int WINAPI WinMain(...)
try
RunApp();
//Some other init routines go here as well.
catch(const std::exception& e)
//log the error
return 1;
catch(...)
//log the error
return 1;
return 0;
如您所见,WinMain
将处理“异常 2”和“3”,但不会处理“异常 1”。
我的基本问题很简单;将这些错误传播到原始“调用”代码的优雅方法是什么?
我想过可能使用自定义消息并将实际的 throw
-statements 移到消息泵(在 RunApp()
中),但我不确定这将如何工作,因为我对 Windows 的经验相对较少一般。
也许我对这种情况的看法完全错了。当您在消息处理程序中遇到致命的事情(即获取关键资源失败,并且没有恢复的机会)时,您通常如何摆脱困境?
【问题讨论】:
【参考方案1】:AFAIK WinAPI 回调(如窗口/对话框/线程过程)不得传播异常。这是因为 WinAPI 内部(调用回调)没有准备好处理异常。它们无法准备好,因为异常实现是特定于编译器的,而 WinAPI DLL 中的代码是固定的,因此无法处理所有可能的异常传播实现。
在一些简单的情况下(尤其是当您使用 Visual Studio 编译时),您可能会观察到异常会以看起来正确的方式传播。然而,这是一个巧合。即使您的应用程序没有崩溃,您也不确定在这之间调用的 WinAPI 函数是否没有分配任何资源,而这些资源由于没有准备好异常而没有释放。
由于不知道回调的来源而增加了额外的复杂性(通常 - 对于某些消息,它可以推断出来)。当且仅当它们被发布到您的对话框时,由您的对话过程处理的消息才会通过消息循环。如果它们被发送,那么它们会跳过循环并直接执行。此外,如果发送了一条消息,那么您不知道是谁发送了这条消息——是您吗?或者也许是 Windows?或者其他进程中的其他窗口试图做某事? (但是这样做有风险。)您不知道调用站点是否已为异常做好准备。
Boost.Exception 提供了一些解决方法。该库允许以某种方式存储捕获的异常并在以后使用(特别是重新抛出)它。这样,您可以将对话过程包装在 throw ... catch(...) ...
中,在 catch 中您将捕获异常并将其存储以供以后使用。
编辑:自 C++ 11 起,Boost.Exception 存储异常对象的功能是 STD 的一部分。你可以使用std::exception_ptr
。
但是仍然存在一些问题。您仍然必须以某种方式恢复并返回一些值(对于需要返回值的消息)。而且您必须决定如何处理存储的异常。如何访问它?谁来做?他/她会用它做什么?
在WM_INITDIALOG
的情况下,您可以使用此消息将任意参数传递给对话过程(使用适当形式的对话创建函数),这可能是指向将保存存储的异常(如果有)的结构的指针。然后您可以调查该结构以查看对话框初始化是否失败以及如何失败。但是,这不能简单地应用于任何消息。
【讨论】:
【参考方案2】:简而言之,我从不使用异常。但是,有几种方法可以报告任何错误,所有这些方法都以某种形式或形式使用日志记录。
方法 1. 使用 OutputDebugString()。 这很好,因为只有拥有调试器的人才会真正注意到实际上不应该失败的东西。 然而,尝试使用异常处理的人显然有很多缺点
方法 2. 使用 MessageBox。 这并不比方法 1 好多少,但它确实允许非开发人员看到错误。
方法 3. 使用错误记录器。 您可以在失败时添加日志记录并使用标准 Win32 返回码退出应用程序,而不是使用“抛出”然后被捕获然后“记录”:
if(msg == WM_INITDIALOG) //Or some other message
/*
Load some critical resource(s) here. For instnace:
const HANDLE someResource = LoadImage(...);
if(someResource == NULL)
LogError("Cannot find resource 'foo');
*/
return TRUE;
【讨论】:
【参考方案3】:我会远离注册自定义窗口消息以进行错误处理。我的意思是这种方法可以正常工作,但实际上没有必要。
顺便说一句,您上面的 catch 处理程序应该捕获所有 3 个异常。您的对话过程在调用 CreateDialog 的同一线程上运行。创建无模式对话框不会产生工作线程。无模式对话框仍然通过您的 GetMessage/Translate/Dispatch 循环获取其消息。那里有一个堆栈帧,这意味着当你抛出时,它应该一直展开到你的 WinMain try/catch 块。
这不是您看到的行为吗?
【讨论】:
这太奇怪了,我不知道为什么,但是在重新启动 VS 后,我得到了所需的行为(即现在调用了 catch 处理程序)。我需要进一步调查发生了什么。感谢您让我仔细检查! @ssayq 我不确定。调用 WinAPI 函数时,您不知道它在内部做了什么。它不必准备好正确处理 C++ 异常,因为 C++ 异常实现是编译器内部的,而 WinAPI 实现是常见的。因此,如果 WinAPI 函数做了更复杂的事情(比如为某事分配内部资源),它不会很好地处理异常(因为它不会释放这些资源)。 AFAIK 异常不应传播到 WinAPI 回调(如窗口/对话框过程)之外,在许多情况下甚至在模块之间传播。【参考方案4】:见http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html。
【讨论】:
以上是关于Win32 消息处理程序错误传播的主要内容,如果未能解决你的问题,请参考以下文章