Search code examples
.net-4.0c++-cli.net-2.0derived-classstatic-constructor

c++/cli static constructor of derived class is not called


As described in another SO post of me I saw a strange behaviour of my application after moving from VS 2008 (.net 3.5) to VS 2013 (and using .net 4.0, not 4.5). I found that the static constructor (cctor) of a class was not called any more. Therefore I broke the application down into a small test program:

DLLs testAssembly_2-0 and testAssembly_4-0
(similar content; testAssembly_4-0 has names with 40instead of 20)

namespace testAssembly_20
{
public ref class Class20
{
public:
  Class20 ()
  { Console::WriteLine (__FUNCTION__"()"); }

  static Class20 ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue);
    ms_iValue = 2;
    Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

  void func20 ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

protected:
  static int ms_iValue = 1;
};
}

main VS2008
When compiling testAssembly_2-0 and main in VS 2008 (making .net 2.0 assembly and application of it), it runs as expected in both execution ways (starting debugging mode in IDE, starting exe directly):

int main ()
{
  testAssembly_20::Class20^ oC20 = gcnew testAssembly_20::Class20;
  oC20->func20 ();
}
// output:
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20::func20() ms_iValue=2

main VS2013
When compiling testAssembly_4-0 and main in VS 2013 (creating .net 4.0 assembly and application), and including the existing .net 2.0 testAssembly_2-0 (using app.config, see my linked post), it still works, but it behaves differently compared IDE debugging to exe start.
IDE debugging produces the result as above (once with Class20 and once with Class40).
exe start calls the cctor not at class instantiation, but when the static member is first time accessed. This must be due to the so-called lazy initialization that was introduced with .net 4.0, as far as I know now by my research during the last couple of hours.

int main ()
{
  testAssembly_40::Class40^ oC40 = gcnew testAssembly_40::Class40;
  oC40->func40 ();
  testAssembly_20::Class20^ oC20 = gcnew testAssembly_20::Class20;
  oC20->func20 ();
}
// output of exe start:
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=1
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=2
// testAssembly_40::Class40::Class40()
// testAssembly_40::Class40::func40() ms_iValue=2
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20::func20() ms_iValue=2

DLLs enhanced
As this did not yet reproduce my failure, I added a property to the class to access the static member, as I also do it in my original application. Querying this property in main() just led to a different order of function calls (the Class20 cctor was now called first of all functions, directly at the beginning of main()). But the behaviour was correct.

Therefore I went one step further towards my original application and added derived classes to both assemblies:

public ref class Class20derived : Class20
{
public:
  Class20derived ()
  { Console::WriteLine (__FUNCTION__"()"); }

  static Class20derived ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue);
    ms_iValue = 3;
    Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }

  void func20derived ()
  { Console::WriteLine (__FUNCTION__"()" + " ms_iValue=" + ms_iValue); }
};

Class40derived is similar again.

main VS2008 new
The test program now creates an object of the derived class. It runs as expected in both execution ways (IDE, exe directly):

int main ()
{
  testAssembly_20::Class20derived^ oC20D = gcnew testAssembly_20::Class20derived;
  oC20D->func20 ();
}
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
// testAssembly_20::Class20derived::Class20derived (static class constructor)() ms_iValue=2
// testAssembly_20::Class20derived::Class20derived (static class constructor)() ms_iValue=3
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20derived::Class20derived()
// testAssembly_20::Class20::func20() ms_iValue=3

main VS2013 new
The test program now creates objects of both derived classes. It runs as expected when being started from the IDE (same result as in VS2008 new, once with Class40 and once with Class20).
But when starting the exe, the result is faulty:

int main ()
{
  testAssembly_40::Class40derived^ oC40D = gcnew testAssembly_40::Class40derived;
  oC40D->func40 ();
  testAssembly_20::Class20derived^ oC20D = gcnew testAssembly_20::Class20derived;
  oC20D->func20 ();
}
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=1
// testAssembly_40::Class40::Class40 (static class constructor)() ms_iValue=2
// testAssembly_40::Class40derived::Class40derived (static class constructor)() ms_iValue=2
// testAssembly_40::Class40derived::Class40derived (static class constructor)() ms_iValue=3
// testAssembly_40::Class40::Class40()
// testAssembly_40::Class40derived::Class40derived()
// testAssembly_40::Class40::func40() ms_iValue=3
// testAssembly_20::Class20::Class20()
// testAssembly_20::Class20derived::Class20derived()
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=1
// testAssembly_20::Class20::Class20 (static class constructor)() ms_iValue=2
--> where is the Class20derived cctor??
// testAssembly_20::Class20::func20() ms_iValue=2

Why is the derived cctor() of the .net 2.0 assembly not called?
Is this an intended behaviour of the .net 4.0 lazy initialization or, which I assume, is it a bug in the compiler? The strange thing here is, that the .net 4.0 assembly is used correctly, but the .net 2.0 assembly isn't.

Also, at the base classes at the top:
Why is the .net 4.0 cctor called at class instantiation, but the .net2.0 cctor is called on demand?

Edit 1

I just found that one and the same application (VS2008, DLLs enhanced) behaves differently when being executed as exe with or without an app.exe.config!
When the app.config is present, the application works as being compiled in VS2013, which means, it is faulty.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

But as soon as I delete the app.config, the application works well.
So I think that the bug is not inside the VS C++/CLI compiler, but inside the .net 4.0 CLR itself...


Solution

  • I have found a workaround by calling the static constructor manually. I didn't know that this is even possible until I read it a few minutes ago:

    System::Runtime::CompilerServices::RuntimeHelpers::RunClassConstructor (
      testAssembly_20::Class20derived::typeid->TypeHandle);
    

    Edit:
    Recently I had an issue that my program behaved differently depending on being run from VS2008 (not VS2013 this time!) or from exe, even though I called the static cctor manually.
    The problem was that the cctor of the WRONG CLASS was executed! Very weird!
    My design:

    base A
    derived B : A
    derived C1 : B
    derived C2 : B
    

    I called C2.cctor, but C1.cctor was run. When adding some arbitrary logging commands it behaved different again and eventually worked. That's when I decided to remove the cctors completely and introduce a static Init() instead.

    Be prepared to encounter the same!
    You can still call the cctor manually, it had worked for me for a long time.

    I would have loved to investigate further and analyse the MSIL but I was too busy at that time.