Executable entry point methods in managed code (e.g. Main in C# or VB.Net) should be very simple. As an example, this Main is too complex and should be simplified.

class MyApp {

   public static void Main() {

      Application.Run(new MyForm()); // MyForm is a form-

                                     // derived type

   }

}

Furthermore in managed code an entry-point method should never diverge (very far) from the following code.

public static Int32 Main(String[] args) {

   try {

      return MainImpl(String[] args); // defined in same

                                      // type as Main

   } catch (Exception e) {

      HandleTheUnhandled(Object e);   // ditto

   } catch {

      HandleTheUnhandled(null);

   }

}

I will get to the anatomy of a stable entry method in a moment.

Until then let’s look closer at the problem. Here are the precepts which lead me to conclude that that first Main is too complicated:

  1. Applications should not be designed to crash.
  2. By design, if the CLR cannot Just-In-Time (JIT) compile Main, the results are an application crash (or at least that is what it will look like to the end-user).
  3. Methods that cause a type to load are less likely to JIT then methods that don’t.

Starting with number one, in an ideal world, applications should not crash. Taking this principle a step further, even if your application does encounter some failure that will prevent it from executing normally, it should still present the user with a notification of the problem, rather then “just crashing”.

So why the CLR’s designed crash in number two? In a JIT compiled environment, if a method doesn’t JIT it can’t be executed at all. There is no concept of partial execution here. So the CLR has no choice but to fail the execution of an application entirely if, for some reason, Main fails JIT compilation.

Alright, so that brings us to number three, which is really the crux of the problem. When managed code starts up it is inherently unstable. Methods get JIT compiled which causes types to be loaded. Types are loaded which cause assemblies to load. Assemblies are loaded, which involves file system operations, at minimum, and network operations in some deployments. You might imagine your application JIT compiling entirely, before Main is ever called; but that is not how it works. Instead, your methods are JIT compiled as they are called, and types and assemblies that your code references are loaded as your code executes.

Eventually the chaos settles down, and your application executes in a steady state. The trick is avoiding chaos in Main.

Let me reprint the too-complicated Main.

class MyApp {

   public static void Main() {

      Application.Run(new MyForm()); // MyForm is a form-

                                     // derived type

   }

}

This code is going to cause the System.Windows.Forms.Application type to load, as well as the application defined type MyForm. In the process of JITting a method, the types referenced by the method are loaded (if they have not already been, that is). Since Main is the first method to execute, we are fairly certain that any types that it references must be loaded.

If either type fails to load, then Main will most likely fail to JIT compile. Here are some situations that can cause a method to fail JIT compilation:

  1. The method fails verification or contains invalid Intermediate Language.
  2. Any type that the method references fails to load.
  3. The type containing the method itself fails to load or fails to initialize.
  4. The method cannot bind to a referenced type or method for security reasons.
  5. The CLR itself has failed or there is not sufficient memory for the native instructions.

Let’s immediately write-off reasons 1 and 5 as edge-cases. If you manage to get your compiler to produce an invalid Main method, you will notice before you ship your product. And number five represents genuine system instability that is beyond the scope of an application to manage.

Reasons 2 and 3 are almost the same problem. In both cases a type-load did not result in a usable type. In one case the type was referenced explicitly by the method being JIT compiled. In the other case the type in question is the type that contains the method being JIT compiled. Failed type-loads can be caused by both unusual and usual situations. Again, don’t burden your application logic with handling bona-fide edge cases. These are things like missing system assemblies, bizarre binding and security policy configurations, CLR errors and such ignorables.

However, an application that late-binds to assemblies or is installed in a network deployment, should remain stable even if an application assembly goes missing or fails to load for some reason. Similarly, default security policy enforces a number of scenarios where a type may fail to load for security reasons. Finally, any type with a static constructor (type initializer) is surprisingly susceptible to initialization failure which for the type containing Main is just as bad as type-load failure.

Here is a fun example:

using System;

using System.Windows.Forms;

 

class MainForm : Form {

 

   static Int32 num =

      Math.Abs(Int32.MinValue); // evil code!

 

   protected override void WndProc(ref Message msg) {

      base.WndProc(ref msg);

   }

 

   public static void Main() {

      try {

         Application.Run(new MainForm());

      }

      catch { // catch *any* exception

         Console.WriteLine(

            "Alert user of unexpected failure");

      }

   }

}

If you compile this code as a console application, and run it, here is what you see on the console:

Unhandled Exception: System.TypeInitializationException: The type initializer for "MainForm" threw an exception. ---> System.OverflowException: Negating the minimum value of a twos complement number is invalid.

   at System.Math.AbsHelper(Int32 value)

   at System.Math.Abs(Int32 value)

   at MainForm..cctor()

   --- End of inner exception stack trace ---

   at MainForm.Main()

The important detail, though, is that the phrase “Alert user of unexpected failure” is not part of the console output. The reason is that the exception occurred before Main could be JIT compiled, and Main’s exception handler is useless here.

A more subtle (and bothersome) case appears if you comment out the line of code commented as “evil code”, and then rebuild as a console application. Now if you run it, everything seems fine. Unless, that is, you copy the .exe to a share on your network, and run it from there. Then you get this on the command line:

Unhandled Exception: System.Security.SecurityException: Request for the permission of type System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 failed.

The state of the failed permission was:

<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

             version="1"

             Flags="UnmanagedCode"/>

Again, an exception has occurred in the process of loading the type that contains Main, and so the exception handler in Main is useless. Incidentally the reason for the security exception in the first place is that our type overrode the WndProc virtual method. Yes, sometimes just overriding a method represents a security breach (if your app is run partially trusted from a network share), that can cause a type to fail load. In this case we would like to present UI to the user letting them know that their deployment is causing the app to fail for security reasons. But we never get the chance to alert the user, because Main is too complex to even JIT.

A Stable Entry Method

Fortunately it doesn’t take too much effort to make your entry method stable. Here are some guidelines:

  1. Avoid type-loads of any kind in Main. Main should only call methods implemented in the same type definition.
  2. Do not implement a static constructor in the type that contains Main. (Avoiding static fields entirely is the safest way to go).
  3. Derive the type that contains Main from Object. This means that you need to hoist Main out of the type definition for your main form if you use Visual Studio .Net wizards to create your project.
  4. Keep the type that contains Main focused on getting your application started. Don’t think of it as the “Application” type that holds your application-level state.

The following code is a template for an entry point that follows these rules. Forty-two lines of code may seem like quite a few, just to get things up and running. Think of it as boilerplate logic that increases the stability and determinism of the first moments of application execution.

using System;

using System.Security;

 

public class AppEntry {

   public static Int32 Main(String[] args) {

 

      // Do we need to roll our own unhandled behavior

      // or was a handler successfully installed

      Boolean unhandledInstalled = false;

 

      try {

 

         // Run Main's usual logic and

         // install unhandled handler

         return MainImpl(args, out unhandledInstalled);

 

      } catch (SecurityException) {

         // Let user know that application

         // is shutting down due to

         // security restrictions

      } catch (Exception e) {

         if (unhandledInstalled)

            throw;

         HandleUnhandled(null,

            new UnhandledExceptionEventArgs(e, true));

      } catch {

         if (unhandledInstalled)

            throw;

         HandleUnhandled(null,

            new UnhandledExceptionEventArgs(null, true));

      }

      return -1;

   }

 

   static Int32 MainImpl(

      String[] args, out Boolean handlerInstalled) {

 

      // Try to install an unhandled exception handler

      // In v1.0/v1.1 failure to do this probably should

      // be reason to terminate the application

      // In v2.0 default unhandled behavior

      // substantially improved

      AppDomain.CurrentDomain.UnhandledException +=

         new UnhandledExceptionEventHandler(

         HandleUnhandled);

      handlerInstalled = true;

 

      // Perform main's "usual" 

      // application start-up logic

 

      return 0;

   }

 

   static void HandleUnhandled(Object sender,

      UnhandledExceptionEventArgs args) {

      // log exception object

      // let user know of unexpected failure

      Environment.Exit(-1);

   }

}