【问题标题】:Exception in WindowProcWindowProc 中的异常
【发布时间】:2013-01-16 13:18:10
【问题描述】:

是否可以在WindowProc 回调中捕获错误? try / catch 不起作用。看起来像 __try __except 和硬件异常(例如AV)也不起作用。


更新:

我发现确实有可能在WindowProc 回调中抛出异常并在WindowProc 之外使用catch 块捕获它。经过测试并在Windows XP x86 上工作。我发现了相关问题64bit exceptions in WndProc silently fail 这个问题似乎只存在于Windows 7 x64 上(并且根据这个问题也存在于其他 x64 Windows 版本上)。

所以问题是有可能以某种方式在WindowProc 中抛出异常并在WindowProc 之外用catch 块捕获它吗?我安装了 microsoft 修补程序,在注册表中将 DisableUserModeCallbackFilter 设置为 1,我得到的最好的是 FATAL_USER_CALLBACK_EXCEPTION,这不是我的例外。

【问题讨论】:

  • winapi 函数不使用异常。它们是用 C 语言编写的。
  • 是否可以在 WindowProc 回调中确定错误而无异常?
  • 当然,从那里调用函数,检查失败,并适当地使用GetLastError等。如果从 WindowProc 中调用的函数调用,则传播结果。

标签: c++ winapi exception-handling seh


【解决方案1】:

MSDN documentation for WindowProc 包含有关从 WindowProc 引发/传播的异常的详细信息。似乎异常只在 32 位版本的 Windows 中传播。

但是,您的原始问题与更新中的问题不同。第一个是关于在 WindowProc 中捕获异常,这将始终正常工作。第二个是关于从 WindowProc 中抛出异常。

我不确定第二个的有用性/必要性。窗口过程通常被调用作为:

  1. 在消息循环中调用 DispatchMessage。在这种情况下不需要抛出异常,因为这样做只会导致应用程序退出。如果您遇到应该导致应用程序退出的错误,只需调用 PostQuitMessage(0)
  2. 调用 SendMessage。在这种情况下,您并不想抛出异常,因为窗口过程将在 UI 线程中执行,如果调用线程与 UI 线程不同,则调用线程无论如何都不会得到异常
  3. 直接调用窗口过程。在这种情况下,异常可以正常工作。

【讨论】:

  • 我想在调用 DispatchMessage 的结果调用窗口过程时抛出异常。我知道这是可能的 PostQuitMessage(0);或使用全局变量来指示错误,但我想使用异常来提供有关错误的更多信息,如错误代码、WindowProc 中的行号等。所以我的问题是甚至可以在 x64 版本上从 WindowProc 抛出异常?也许可以用 __try __except 来实现?
  • 如果 MSDN 链接正确并且我没看错,这是不可能的。不过,为什么不直接在 WindowProc 中完成所有错误报告,然后调用 PostQuitMessage(0);?
  • 因为在两个不同的地方处理错误很不方便,一个在WindowProc,另一个在catch块。我认为异常优势之一是您可以在一个地方处理所有错误。但这并不是真正无关紧要的问题。无论如何感谢您的回答。
  • 这个答案主要是基于意见的。谁来决定什么时候抛出异常有意义?我认为使用异常应该总是安全的,否则异常不应该是语言的一部分!我依靠异常来识别错误是没有意义的;当他们自己是一个潜在的错误时。
【解决方案2】:

使用 C++11,您可以通过手动转发任何异常来处理您的情况,如下所示:

#include <exception>
std::exception_ptr windowProcException = nullptr;

LRESULT windowProc(){
  try {
    yourcode();
  catch(...){
    windowProcException = std::current_exception();
  }   
}

然后您可以像这样在主循环中重新抛出异常:

windowProcException = nullptr;
DispatchMessage();
if (windowProcException)
  std::rethrow_exception(windowProcException);

【讨论】:

  • Chronial 先生,我永远爱你。你带着一个非常……令人沮丧的问题的实际解决方案来到这里。我在嵌套窗口过程调用中测试了您的解决方案并且它有效。比如子窗口的WM_SIZE和WM_CREATE在父窗口的WM_CREATE里面。这应该是公认的答案。
【解决方案3】:

Chronial 给出了最佳答案。我会给出我认为有用的改进。

Chronial 的概念是允许在你的窗口过程中使用 cpp throw 机制,但不要让它在窗口过程之外传播;它在 C 库中调用并导致 64 位 Windows 上的未定义行为,即 64 位 win 7 或 Windows 8。而是在窗口过程中捕获异常,并将其保存在全局变量中,然后在 cpp main 中重新抛出作用和利用。有关代码示例,请参阅 Chronial 的答案。

这个概念很简单,但需要一些细节才能 100% 正确。

  • 要避免的一个陷阱是不要破坏您抛出的窗户。您的代码将清除在 try 块中声明的所有对象,但您创建的窗口对象仍将处于活动状态并处理消息。即使您不再发送消息。如果你在窗口过程中使用指针,当你的代码在你的 catch 块中时,这些指针可能是无效的,而 windows 仍在向你的窗口发送消息,而你没有销毁。

  • 每个窗口过程都需要有这种try catch,保存异常技术。如果只准备您的顶级窗口,它将不起作用,但在其子窗口的过程中会引发异常。

  • 如果前两个非常明显,那么这个就有点不明显了。对于您的顶级窗口的程序;除了尝试捕获整个 switch 语句之外,您还应该尝试捕获 WM_CREATE 消息,如果捕获到异常则返回 -1。这将阻止创建窗口及其子项;并且一旦您重新抛出异常,您就不必销毁窗口。

  • 最后再次在顶层窗口的 WM_CREATE 消息中,运行创建子窗口的代码后,检查这些子窗口是否设置了全局 windowProcException 变量。子窗口的创建将运行它们自己的 windowProcedure,并且在这些窗口过程中捕获的异常不会自动传播到您的顶级窗口的过程。如果子窗口过程发生异常,从顶层窗口返回 -1 将取消所有窗口的创建。除非,您认为某个特定的子窗口不是超级重要。

    void createChildWindows();

    windowProcedure(hwnd,msg,wparam,lparam) { 尝试 { 尝试 { 如果(味精 == WM_CREATE) { createChildWindows(); 返回 windowProcException ? -1:0; } } 抓住(...) { windowProcException = std::current_exception(); 返回-1; }

         return DefWindowProc(hwnd,msg,wparam,lparam);
     }
     catch(...)
     {
      windowProcException = std::current_exception();
      //MingGw won't complain if you don't return a value;
      //MSVC might
      //As far as I am concerned, we are throwing, 
      //so any value returned is undefined
      //We must however be careful with WM_CREATE as that return code
      //dictates whether or not window creation continues
     }
    

    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多