Initializing code


style="FONT-FAMILY: Arial">A common question is how to initialize
code before it is called.  In the
unmanaged world, this is done with a DLL_PROCESS_ATTACH notification to your
DllMain routine.  Managed C++ can
actually use this same technique. 
However, it has all the usual restrictions and problems related to the
operating system’s loader lock. 
This
approach is not recommended for managed code.


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">However, we don’t have a good
assembly-level or module-level replacement for this
technique.


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">One possible option is to hook up to the
AppDomain AssemblyLoad event.  This is great for telling you when other
code has loaded.  But there’s a
chicken and egg problem with initializing your own code with this
technique.  You can’t register for
the event before you’ve loaded and initialized!


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">Another option is to use a static
constructor (aka class constructor method. 
This is given the cryptic name .cctor in the metadata. style="mso-spacerun: yes">  However, a .cctor only gets invoked
prior to usage of the class that it is declared on.  So you would have to
add one to every class in your assembly… and you still wouldn’t be able to
trap all usage of e.g. ValueTypes in your assembly.


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">If you go this route, be careful about
the different semantics the CLR associates with .cctor methods, based on whether
the tdBeforeFieldInit bit is set.  When this bit is set, we can be more
efficient.  But we won’t trap any accesses to your class except to static
fields.  When this bit is reset,
your .cctor will execute before any instance or static method or field is
accessed.  However, the CLR must
then give up on various optimizations. 
The impact can be particularly painful with code that is loaded as
domain-neutral (i.e. shared across AppDomains).


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">How do you know whether the bit is set or not? style="mso-spacerun: yes">  Your language is setting it one way or
the other on your behalf.  I believe
C# will set tdBeforeFieldInit if you just have initialization statements for
your static fields.  If you have an
explicit static constructor method, they will reset this bit. style="mso-spacerun: yes">  It’s easy enough to check with
ILDASM. style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'"> 


style="FONT-FAMILY: Arial">Neither of the above solutions is
particularly satisfying.  The CLR is under some pressure from various
language partners and other developers to provide a module-level equivalent to a
class constructor.


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">While we are on the subject of .cctor’s,
there are a couple of other interesting facts:


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">Some languages require that base class
.cctor methods will run before derived class .cctors. style="mso-spacerun: yes">  Or that interface .cctor methods will
run before the .cctors of the classes that implement those methods. style="mso-spacerun: yes">  The CLR has no such rules for chaining
(though it does have some important rules for managing circular references
between .cctors of different types). 
So there’s a service called
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor which your
language might call to explicitly trigger execution of base .cctor methods for
this purpose.


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">Any use of a .cctor has some impact on
system performance.  And, depending
on the semantics of the .cctor (tdBeforeFieldInit), whether the code is
domain-neutral, whether the code is NGEN’d, whether chaining is involved, etc.,
this cost can be measurable.  Only
use .cctor’s if you need them (duh).


style="FONT-FAMILY: Arial"> 


style="FONT-FAMILY: Arial">One common use of a .cctor is to
initialize a large array of scalars. 
Doesn’t it seem like a huge waste to use code to laboriously assign each
array element – and then never use that code again? style="mso-spacerun: yes">  In the unmanaged world, you would place
the data into the image as initialized data and avoid any code
execution.


style="FONT-FAMILY: Arial"> 


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma">We can’t
quite achieve such perfection with managed code, because the resulting managed
array must be allocated in the GC heap. 
However, there is a way to efficiently load up a managed array with
static scalar data.  The technique
is based on a service in System.Runtime.CompilerServices.RuntimeHelpers called
InitializeArray(Array array, RuntimeFieldHandle fldHandle). style="mso-spacerun: yes">  This service allows you to pass in a
reference to a managed array and the handle of a field in metadata. style="mso-spacerun: yes">  The field must be RVA-based. style="mso-spacerun: yes">  In other words, it must be associated
with an address in the image which presumably contains the scalar data. style="mso-spacerun: yes">  You are only permitted to copy as many
bytes as the metadata declares are associated with this
field.


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma">Clearly this
isn’t something you can take advantage of directly. style="mso-spacerun: yes">  But your language compiler will ideally
notice cases where the array size exceeds some threshold. style="mso-spacerun: yes">  I’m aware of at least one popular
managed compiler that will use this technique on your
behalf.


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma">Finally, a
.cctor method will execute at most one time in any AppDomain. style="mso-spacerun: yes">  If it fails to complete successfully, it
cannot be restarted.  That’s because
it contains arbitrary code, with arbitrary side effects. style="mso-spacerun: yes">  If an exception escapes out of a .cctor
execution, it is captured and latched as the InnerException of a
TypeInitializationException. 
Subsequent attempts to use that type in the same AppDomain may trigger
another attempted execution of the .cctor (depending on tdBeforeFieldInit of
course).  When this happens, the
TypeInitializationException will be thrown again. style="mso-spacerun: yes">  The type can never be initialized in
this AppDomain.


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"> style="mso-bidi-font-family: Tahoma">One day, it
would be nice if we could distinguish restartable .cctor methods from
non-restartable ones.  Until then,
be careful not to allow exceptions to escape your .cctor method unless the type
really is off limits.

Comments (5)

  1. Anonymous says:

    Unfortunately, there is a (small) bug in the current implementation. Here is an example that successfully instantiates a type after the .cctor threw an exception:

    using System;

    class cctor
    {
    static cctor()
    {
    new cctor();
    throw new Exception("barf");
    }
    }

    class Test
    {
    static void Main(string[] args)
    {
    try
    {
    new cctor();
    }
    catch(Exception x)
    {
    Console.WriteLine(x);
    }
    try
    {
    new cctor();
    Console.WriteLine("Hey, we got an instance!");
    }
    catch(Exception x)
    {
    Console.WriteLine(x);
    }
    }
    }

  2. Anonymous says:

    Yes, this is a known bug. Unfortunately, we are too aggressive in wiring up callsites. It’s on the list to get fixed.

  3. Anonymous says:

    Great blog! Quick question: do we need to worry about multiple threads entering a cctor or does the CLR ‘suspend’ all threads accessing a type while it runs the class constructor? This kind of race condition with static initialization is an issue in C++. Is it the same in the CLR?

  4. Anonymous says:

    Excepting the bug that Jeroen mentioned above, the CLR guards execution of a .cctor so that only one thread can proceed. You do not need to perform any synchronization yourself.