31

I'm maintaining a .NET 1.1 application and one of the things I've been tasked with is making sure the user doesn't see any unfriendly error notifications.

I've added handlers to Application.ThreadException and AppDomain.CurrentDomain.UnhandledException, which do get called. My problem is that the standard CLR error dialog is still displayed (before the exception handler is called).

Jeff talks about this problem on his blog here and here. But there's no solution. So what is the standard way in .NET 1.1 to handle uncaught exceptions and display a friendly dialog box?

Jeff's response was marked as the correct answer because the link he provided has the most complete information on how to do what's required.

Ijas Ameenudeen
  • 9,069
  • 3
  • 41
  • 54
Ray
  • 45,695
  • 27
  • 126
  • 169

6 Answers6

13

Oh, in Windows Forms you definitely should be able to get it to work. The only thing you have to watch out for is things happening on different threads.

I have an old Code Project article here which should help:

User Friendly Exception Handling

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
5

Unhandled exception behavior in a .NET 1.x Windows Forms application depends on:

  • The type of thread that threw the exception
  • Whether it occurred during window message processing
  • Whether a debugger was attached to the process
  • The DbgJitDebugLaunchSetting registry setting
  • The jitDebugging flag in App.Config
  • Whether you overrode the Windows Forms exception handler
  • Whether you handled the CLR’s exception event
  • The phase of the moon

The default behavior of unhandled exceptions is:

  • If the exception occurs on the main thread when pumping window messages, it's intercepted by the Windows Forms exception handler.
  • If the exception occurs on the main thread when pumping window messages, it will terminate the app process unless it's intercepted by the Windows Forms exception handler.
  • If the exception occurs on a manual, thread-pool, or finalizer thread, it's swallowed by the CLR.

The points of contact for an unhandled exception are:

  • Windows Forms exception handler.
  • The JIT-debug registry switch DbgJitDebugLaunchSetting.
  • The CLR unhandled exception event.

The Windows Form built-in exception handling does the following by default:

  • Catches an unhandled exception when:
    • exception is on main thread and no debugger attached.
    • exception occurs during window message processing.
    • jitDebugging = false in App.Config.
  • Shows dialog to user and prevents app termination.

You can disable the latter behavior by setting jitDebugging = true in App.Config. But remember that this may be your last chance to stop app termination. So the next step to catch an unhandled exception is registering for event Application.ThreadException, e.g.:

Application.ThreadException += new
Threading.ThreadExceptionHandler(CatchFormsExceptions);

Note the registry setting DbgJitDebugLaunchSetting under HKEY_LOCAL_MACHINE\Software.NetFramework. This has one of three values of which I'm aware:

  • 0: shows user dialog asking "debug or terminate".
  • 1: lets exception through for CLR to deal with.
  • 2: launches debugger specified in DbgManagedDebugger registry key.

In Visual Studio, go to menu ToolsOptionsDebuggingJIT to set this key to 0 or 2. But a value of 1 is usually best on an end-user's machine. Note that this registry key is acted on before the CLR unhandled exception event.

This last event is your last chance to log an unhandled exception. It's triggered before your Finally blocks have executed. You can intercept this event as follows:

AppDomain.CurrentDomain.UnhandledException += new
System.UnhandledExceptionEventHandler(CatchClrExceptions);
Fred
  • 3,365
  • 4
  • 36
  • 57
HTTP 410
  • 17,300
  • 12
  • 76
  • 127
5

AppDomain.UnhandledException is an event, not a global exception handler. This means, by the time it is raised, your application is already on its way down the drain, and there is nothing you can do about it, except for doing cleanup and error logging.

What happened behind the scenes is this: The framework detected the exception, walked up the call stack to the very top, found no handlers that would recover from the error, so was unable to determine if it was safe to continue execution. So, it started the shutdown sequence and fired up this event as a courtesy to you so you can pay your respects to your already-doomed process. This happens when an exception is left unhandled in the main thread.

There is no single-point solution to this kind of error. You need to put a real exception handler (a catch block) upstream of all places where this error occurs and forward it to (for example) a global handler method/class that will determine if it is safe to simply report and continue, based on exception type and/or content.

Edit: It is possible to disable (=hack) the error-reporting mechanism built into Windows so the mandatory "crash and burn" dialog does not get displayed when your app goes down. However, this becomes effective for all the applications in the system, not just your own.

Fred
  • 3,365
  • 4
  • 36
  • 57
Ishmaeel
  • 14,138
  • 9
  • 71
  • 83
3

Is this a console application or a Windows Forms application? If it's a .NET 1.1 console application this is, sadly, by design -- it's confirmed by an MSFT dev in the second blog post you referenced:

BTW, on my 1.1 machine the example from MSDN does have the expected output; it's just that the second line doesn't show up until after you've attached a debugger (or not). In v2 we've flipped things around so that the UnhandledException event fires before the debugger attaches, which seems to be what most people expect.

It sounds like .NET 2.0 does this better (thank goodness), but honestly, I never had time to go back and check.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
1

It's a Windows Forms application. The exceptions that are caught by Application.ThreadException work fine, and I don't get the ugly .NET exception box (OK to terminate, Cancel to debug? who came up with that??).

I was getting some exceptions that weren't being caught by that and ended up going to the AppDomain.UnhandledException event that were causing problems. I think I've caught most of those exceptions, and I am displaying them in our nice error box now.

So I'll just have to hope there are not some other circumstances that would cause exceptions to not be caught by the Application.ThreadException handler.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ray
  • 45,695
  • 27
  • 126
  • 169
0

The Short Answer, Looks like, an exception occurring in Form.Load doesn't get routed to Application.ThreadException or AppDomain.CurrentDomain.UnhandledException without a debugger attached.

The More accurate Answer/Story This is how I solved a similar problem. I can't say for sure how it does it, but here is what I think. Improvement suggestions are welcome.

The three events,

  1. AppDomain.CurrentDomain.FirstChanceException
  2. AppDomain.CurrentDomain.UnhandledException
  3. and Application.ThreadException

accumulatively catch most of the exceptions but not on a global scope (as said earlier). In one of my applications, I used a combination of these to catch all kinds of exceptions and even the unmanaged code exceptions like DirectX exception (through SharpDX). All exceptions, whether they are caught or not, seem to be invoking FirstChanceException without a doubt.

AppDomain.CurrentDomain.FirstChanceException += MyFirstChanceExceptionHandler;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); // not sure if this is important or not.
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // can't use Lambda here. need to Unsub this event later.
Application.ThreadException += (s, e) => MyUnhandledExceptionHandler(e.Exception);

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    MyUnhandledExceptionHandler((Exception)e.ExceptionObject);
}
private void CurrentDomain_FirstChanceException(object sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs eventArgs)
{
    // detect the pattern of the exception which we won't be able to get in Fatal events.
    if (eventArgs.Exception.Message.StartsWith("HRESULT"))
        MyUnhandledExceptionHandler(eventArgs.Exception);
}

and the handler looks like

static void MyUnhandledExceptionHandler(Exception ex)
{
    AppDomain.CurrentDomain.UnhandledException -= MyUnhandledExceptionHandler;  // this is important. Any exception occuring in the logging mechanism can cause a stack overflow exception which triggers the window's own JIT message/App crash message if Win JIT is not available.
    // LogTheException()
    // Collect user data
    // inform the user in a civil way to restart/close the app
    Environment.Exit(0);
}

Unmanaged code exceptions like DirectX exceptions appeared only in FirstChanceException where I had to decide for myself if the exception was fatal or not. I then use MyUnhandledExceptionHandler to log and let the user know in a friendly way that everything was "under control".

IMPORTANT NOTE! The scheme still didn't catch one kind of exception. It did appear in FirstChanceException, but it was hard to distinguish it from other kinds of exceptions hitting this handler. Any exception occurring directly in Form.Load had this different behavior. When the VS debugger was attached, these were routed to the UnhandledException event. But without a debugger, an old-school windows message will pop up, showing the stack trace of the exception that occurred. The most annoying thing was that it didn't let MyUnhandledExceptionHandlerr get kicked once it was done and the app continued to work in an abnormal state. The final solution I did was to move all the code from Form_load to another thread using MyForm.Load += (s,e) => new Thread(()=>{/* My Form_Load code*/ }).Start();. This way, Application.ThreadException gets triggered which is routed to MyUnhandledExceptionHandler, my safe exit.

Umar Hassan
  • 192
  • 3
  • 11