我正在编写一个使用单个对话框的(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
- 语句移到消息泵中(在RunApp()
中),但我不确定它是如何工作的,因为我有相对一般来说,对Windows的经验很少。
也许我看这种情况都错了。当你在消息处理程序中时,你通常会在某些致命事件(即获取关键资源失败,并且没有恢复机会)时纾困?
答案 0 :(得分:6)
AFAIK WinAPI回调(如窗口/对话框/线程过程)不得传播异常。这是因为WinAPI内部(调用回调)不准备处理异常。它们无法准备,因为异常实现是特定于编译器的,而WinAPI DLL中的代码是固定的,因此它无法处理所有可能的异常传播实现。
在一些简单的情况下(特别是当您使用Visual Studio进行编译时),您可能会发现异常的传播看似正确。然而,这是巧合。即使你的应用程序没有崩溃,你也不确定在它们之间调用的WinAPI函数是否没有分配由于他们没有准备好的例外而没有发布的任何资源。
通过不知道回调的来源(通常 - 对于某些消息可以推断出来)来添加额外的复杂性层。当且仅当它们被发布到对话框时,对话程序处理的消息才会通过消息循环。如果他们被发送,那么他们跳过循环并直接执行。此外,如果发送了一条消息,那么您不知道是谁发送了消息 - 是吗?或者Windows?或者在其他过程中尝试做某事的其他窗口? (但这样做会有风险。)您不知道调用站点是否已为异常做好准备。
Boost.Exception提供了一些解决方法。该库允许以某种方式存储捕获的异常并使用(特别是重新抛出)后者。通过这种方式,您可以将对话框过程包装在throw { ... } catch(...) { ... }
中,在catch中捕获异常并将其存储起来供以后使用。
编辑:从C ++ 11开始,用于存储异常对象的Boost.Exception的功能是STD的一部分。您可以使用std::exception_ptr
。
然而,仍然存在一些问题。您仍然需要以某种方式恢复并返回一些值(对于需要返回值的消息)。而且您必须决定如何处理存储的异常。如何访问它?谁会这样做?他/她将用它做什么?
如果是WM_INITDIALOG
,您可以使用此消息将任意参数传递给对话框过程(使用适当形式的对话框创建函数),这可能是一个指向结构的指针,该结构将保存存储的异常(如果有的话) 。然后,您可以调查该结构,以查看对话框初始化是否失败以及如何。但是,这不能简单地应用于任何消息。
答案 1 :(得分:1)
简而言之,我从未使用过例。但是,有几种方法可以报告任何错误,所有错误都以某种形式或形式使用记录。
方法1.使用OutputDebugString()。
这很好,因为只有拥有调试器的人才能真正注意到在所有现实中都不应该失败的东西。
对于试图使用异常处理的人来说显然有很多缺点
方法2.使用MessageBox 这并不比方法1好多少,但它确实允许非开发人员看到错误。
方法3.使用错误记录器 而不是使用'throw'然后被捕获然后'记录',您可以在失败时添加日志记录并使用标准的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;
}
答案 2 :(得分:0)
我会远离注册自定义窗口消息以进行错误处理。我的意思是这种方法可以正常工作,但并不是真的需要。
顺便说一句,上面的catch处理程序应该捕获所有3个异常。对话框过程在调用CreateDialog的同一线程上运行。创建无模式对话框不会产生工作线程。无模式对话框仍然通过GetMessage / Translate / Dispatch循环获取其消息。那里有一个堆栈框架,这意味着当你抛出时,它应该一直展开到你的WinMain try / catch块。
这不是你所看到的行为吗?
答案 3 :(得分:0)