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 消息处理程序错误传播的主要内容,如果未能解决你的问题,请参考以下文章

Win10关机提示此应用程序阻止关机怎么处理

win32中窗口程序的运行过程

为啥我的程序停止并显示错误消息?

Win10关机提示此应用程序阻止关机怎么处理

为啥系统会弹出WIN32错误

为啥 CreateProcess 给出错误 193(%1 不是有效的 Win32 应用程序)