在64位Windows系统上,VS2010在WinForms应用程序中不显示未处理的异常消息。

78

当我创建一个新项目时,未处理的异常会出现奇怪的行为。以下是我可以重现问题的方法:

1)创建一个新的 Windows Forms Application(C#、.NET Framework 4、VS2010)

2)将以下代码添加到 Form1_Load 处理程序中:

int vara = 5, varb = 0;
int varc = vara / varb;
int vard = 7;
我原本期望在第二行时,VS会崩溃并显示一个未处理的异常消息。但实际情况是,第三行被跳过了,没有任何消息提示,应用程序继续运行。
我的现有C#项目中没有这个问题,所以我猜测我的新项目可能使用了一些奇怪的默认设置。
有没有人知道我的项目出了什么问题?
我尝试勾选调试->异常中的框,但这样执行后即使我在try-catch块中处理了异常,也会导致中断;这也不是我想要的结果。如果我没记错的话,该对话框中有一列名为"未处理的异常"或类似的内容,可以做到我想要的效果。但在我的项目中只有一列("Thrown")。

同样的问题在这里!表单加载已经在内部捕获了异常.. - Pedro77
5个回答

126

这是一个讨厌的问题,由wow64仿真层引起,它允许32位代码在Windows 7的64位版本上运行。当响应64位窗口管理器生成的通知时运行的代码中会吞噬异常,如Load事件。这使得调试器无法查看并进入其中。这个问题很难解决,微软的Windows和DevDiv团队互相推诿责任。DevDiv对此无能为力,Windows则认为这是正确且有文档记录的行为,尽管听起来很神秘。

虽然有文档记录,但几乎没有人能理解其后果或认为它是合理的行为。特别是当窗口过程被隐藏在视野之外时,例如在任何使用包装类来隐藏窗口管道的项目中,例如任何Winforms,WPF或MFC应用程序。潜在的问题是Microsoft无法弄清楚如何将异常从32位代码流回触发通知的64位代码,以便再次回到尝试处理或调试异常的32位代码。

只有在调试器附加时才会出现问题。如果没有调试器,你的代码将像往常一样崩溃。

项目 > 属性 > 构建选项卡 > 平台目标 = AnyCPU,取消选中“偏好32位”复选框。您的应用程序现在将作为64位进程运行,消除了wow64故障模式。一些后果是,它会禁用VS2013之前版本的编辑和继续功能,并且当你依赖32位代码时可能并不总是可行。

其他可能的解决方法:

  • 调试 > 异常 > 选中CLR异常框以强制调试器停在抛出异常的代码行。
  • Load事件处理程序中编写try/catch,并在catch块中快速失败。
  • Main() 方法中使用 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException),这样可以使消息循环中的异常捕获在调试模式下不被禁用。但是这会使所有未处理的异常难以调试,ThreadException 事件几乎没用。
  • 请考虑您的代码是否真的属于 Load 事件处理程序。虽然在 VB.NET 中它非常流行并且默认情况下双击轻松添加事件处理程序,但实际上用到的地方非常少。只有当您关心用户偏好和自动缩放应用后的窗口尺寸时才需要使用 Load。其他内容都应该放在构造函数中。
  • 升级到 Windows 8 或更高版本,它们已经解决了这个 wow64 问题。

  • 6
    有关此事,已有多篇Connect文章可供参考。其中这一篇可能是最好的:https://connect.microsoft.com/VisualStudio/feedback/details/357311/silent-exceptions-on-x64-development-machines - Hans Passant
    5
    这里有一个建议表明他们并不太清楚正在发生什么:https://connect.microsoft.com/VisualStudio/feedback/details/589858/form-load-event-unhandled-exception-not-caught-from-inside-visual-studio-but-caught-when-app-started-outside-visual-studio - Hans Passant
    1
    我已经检查过了,同样的错误存在于一个Winforms应用程序中,在OnFormClosed事件上。并且我也无法通过将应用程序目标更改为x86来解决它。但至少我有一个问题的解释。 - ferpega
    1
    这是因为在调试器下运行会禁用正常的异常处理程序,该处理程序捕获异常并引发ThreadException事件,然后Windows才能吞噬它。 - Hans Passant
    7
    Windows 8确实存在这个问题。仅供参考。 - ThunderGr
    显示剩余10条评论

    10

    根据我的经验,只有在调试器附加时才会出现该问题。独立运行应用程序时,异常不会被吞掉。

    通过引入KB976038,您可以再次如预期地正常工作。我从未安装过此热修复程序,因此我认为它是包含在Win7 SP1中的。

    这在此帖子中提到:

    这里是一些代码,将启用热修复程序:

    public static class Kernel32
    {
        public const uint PROCESS_CALLBACK_FILTER_ENABLED = 0x1;
    
        [DllImport("Kernel32.dll")]
        public static extern bool SetProcessUserModeExceptionPolicy(UInt32 dwFlags);
    
        [DllImport("Kernel32.dll")]
        public static extern bool GetProcessUserModeExceptionPolicy(out UInt32 lpFlags);
    
    
        public static void DisableUMCallbackFilter() {
            uint flags;
            GetProcessUserModeExceptionPolicy(out flags);
    
            flags &= ~PROCESS_CALLBACK_FILTER_ENABLED;
            SetProcessUserModeExceptionPolicy(flags);
        }
    }
    

    在你的应用程序开头调用它:

        [STAThread]
        static void Main()
        {
            Kernel32.DisableUMCallbackFilter();
    
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    

    我已确认(通过下面展示的简单示例)这个方法可以正常工作,就像你期望的那样。

    protected override void OnLoad(EventArgs e) {
        throw new Exception("BOOM");   // This will now get caught.
    }
    

    所以,我不明白的是,为什么调试器以前无法处理跨越内核模式堆栈帧,但是通过这个热修复,他们想出了解决办法。


    “Set/GetProcessUserModeExceptionPolicy”仍未在MSDN上记录,并且Windows 8的Kernel32.dll没有导出它们。 - Martin
    有没有一种通过编辑注册表或其他方式来完成相同操作的方法? - ThunderGr
    1
    我已经阅读了它们。我也看到了评论,称Windows 8内核不导出这些方法。我碰巧在Windows 8上运行。我只是问一下,以防万一。 - ThunderGr
    1
    如果您应用了您指向的热修复补丁,您可以按照他们所描述的方式编辑注册表来解决问题,而不是使用其他方法。我记得以前在Win7机器上就是这样解决过这个问题。不幸的是,在Win 8中您无法这样做。 - ThunderGr
    1
    @ThunderGr 感谢您指出这一点。我想在睡觉时间之后就不应该再发表评论了 :-) - Jonathon Reinhart

    3
    正如Hans所提到的那样,编译应用程序并在未附加调试器的情况下运行exe文件。
    对我来说,问题是改变了一个BindingSource控件绑定的类属性名称。在没有集成开发环境的情况下运行,我能够看到错误:
    无法绑定到DataSource上的属性或列SendWithoutProofReading。参数名称:dataMember 将BindingSource控件修复为绑定到更新后的属性名称可解决该问题: enter image description here

    1
    我正在使用WPF并遇到了同样的问题。 我已经尝试过Hans的1-3个建议,但不喜欢它们,因为studio不会停在错误发生的位置(所以我无法查看我的变量并查看问题所在)。 因此,我尝试了Hans的第四个建议。 令人惊讶的是,我的代码中有多少可以移动到MainWindow构造函数而没有任何问题。 不确定为什么我养成了将这么多逻辑放在Load事件中的习惯,但显然很多工作可以在ctor中完成。 但是,这与1-3号有相同的问题。 在WPF的ctor期间发生的错误被包装成通用的Xaml异常。(内部异常有真正的错误,但我想要studio在实际的麻烦点处中断)。 最终对我起作用的是创建一个线程,睡眠50ms,返回到主线程并执行需要做的事情...
        void Window_Loaded(object sender, RoutedEventArgs e)
        {
            new Thread(() =>
            {
                Thread.Sleep(50);
                CrossThread(() => { OnWindowLoaded(); });
            }).Start();
        }
        void CrossThread(Action a)
        {
            this.Dispatcher.BeginInvoke(a);
        }
        void OnWindowLoaded()
        {
            ...do my thing...
    

    这样,当未捕获的异常发生时,该工作室将会中断。

    0
    一个简单的解决方法是将初始化代码移动到另一个事件中,例如Form_Shown,该事件比Form_Load稍后调用,并使用标志在第一次显示表单时运行启动代码:
    bool firstLoad = true; //flag to detect first form_shown
    
    private void Form1_Load(object sender, EventArgs e)
    {
        //firstLoad = true;
        //dowork(); //not execute initialization code here (postpone it to form_shown)
    }
    
    private void Form1_Shown(object sender, EventArgs e)
    {
        if (firstLoad) //simulate Form-Load
        {
            firstLoad = false;
    
            dowork();
        }
    }
    
    void dowork()
    {
        var f = File.OpenRead(@"D:\NoSuchFile756.123"); //this cause an exception!
    
    }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接