Search code examples
c++dllclrnative

Why native C++ app that imports Mixed Native/CLR lib/dll is not calling ctor/dtor on extern vars in Mixed lib/dll


Writing (further in text: logger) comprehensive logger/diagnostics/performance profiler/debugger functionality with Native stack walker/managed stack walker function among zillion other features, and library has to be shared between different modules living on Native or Managed heap (written in asm/native C++/managed C++/.Net/...). So far everything was working fine when this logger and importer/implementer/caller application are both compiled in Native or Managed code. But if I compile logger library as Managed with /CLR, and use it in Native C++ project W/O /CLR, classes exported from logger library with 'extern' are not invoking Constructor or Destructor inside logger dll, during dll initialization. Actually Constructors/Destructors are never called in logger library (noticed also even extended classes constructors aren't called), only empty shell of class exists half initialized.

To be more clear about logger library: native part implements full functionality of logger library. While the managed part is actually "simple" wrapper for native code, which I need to be implemented in fully in same dll for portability and maintenance reasons. Logger library is meant to replace 10 year old similar library that has only 20% of this new logger library functionalities.

Now this is not the first time I have experienced this or similar problem, and in past solutions were either to split to pure native and pure managed code, and one to has wrapper for the other (two projects to maintain, portability none). Or to compile both version of library one for native application, and the other for managed application. Now in this case those are not solutions but would be limitations since I need to have code working trough same process pipe whether it's native dll, native app, managed dll, managed app, ..., I need to have all in one for simplicity sake.

Also I could rewrite extern classes not to use constructor/destructor and write some elastic simulation of same, but after wasting whole day on this I would like to know the reason behind this problem, and are there any other solutions that are more elegant or if I making mistake somewhere: ie. using #pragma managed(push, off) is producing this symptoms or similar?

Does anyone know the reason behind this?


Solution

  • There are two devils making code miss behave.

    First devil: warning message was the first clue that exported initializers will not be run before MANAGED CODE is first executed (guess when compiling managed nothing get initialized when DLL is loaded). Example of warning while I was testing loading order of mixed code:

    1>CBla.cpp(8): warning C4835: '_bla1_' : the initializer for exported data will not be run until managed code is first executed in the host assembly.
    

    Which I was ignoring since everything seamed to work.

    Second devil: some of standard c/c++ code can't be compiled to managed. Although I was under impression this shouldn't be compiled as managed. But I started receiving warning messages in functions using variadic arguments and started putting everywhere around native code pragmas to compile native code to native!!!

    #pragma managed(push, off)
    // native code
    #pragma managed(pop)
    

    Now this compiled fine, but in this case all "native" classes were actually only living as native, no calls to managed code was ever placed - managed VFTABLE was never created. Managed functions have different VFTABLE's until they get loaded, every function is rewritten to load CLR before executing actual managed function. Which you can only discover with IDA dissasembler or similar utils... but also briefly explained at the bottom of following page: https://msdn.microsoft.com/en-us/library/ms173266.aspx?f=255&MSPPError=-2147217396 "Initialization of Mixed Assemblies". There for no initializer for extern native classes were ever called, what was statically stored to LIB/DLL, that was hold in memory (empty shell). So pure native application calling mixed code LIB/DLL that never uses/triggers CLR code will never call Constructor/Destructor in native compiled classes.

    SOLUTION: Only way to overcome this limitation was to place some managed function in all of this to force CLR to be loaded, in which case you will notice one exception during debuging process:

    First-chance exception at 0x7620c41f in ManagedNativeNatTest.exe: 0x04242420: CLRDBG_NOTIFICATION_EXCEPTION_CODE.
    

    This is actuall moment when CLR code gets initialize notification, and starts loading exported native classes for LIB/DLL. I was able to trigger this by placing empty function belonging to native class into managed code:

    ...
    #pragma managed(push, off)
    ...
    #pragma managed(pop)
    
    void CBla1::ManagedCall()
    { }
    
    #pragma managed(push, off)
    ...
    

    And calling this function resulted in executing CLR loader in return initializing my extern vars.

    Why this is done like this I am not sure, maybe because CLR never loads anything until it's used. Wonder will it be the same case if I compile CLR to native code using NGen but this might be another adventure.

    This completes my answer why extern vars are not getting calls to Constructor/Destructor in mixed LIB/DLL.