【问题标题】:Win32 Message Handler Error PropagationWin32 消息处理程序错误传播
【发布时间】: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 的经验相对较少一般。

也许我对这种情况的看法完全错了。当您在消息处理程序中遇到致命的事情(即获取关键资源失败,并且没有恢复的机会)时,通常如何摆脱困境?

【问题讨论】:

    标签: c++ winapi exception message-queue


    【解决方案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】:
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-07
        相关资源
        最近更新 更多