About Mixed Mode DLL with /NOENTRY crash since Visual Studio 2022 17.7.0 and the global static initializer problem (2024-01-15)
The problem had been originally reported https://developercommunity.visualstudio.com/t/Visual-Studio-2022-1770-CRT-crash-when/10441303.but also in other posts e.g. here https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475#T-ND10570501
After further investigation we found out, that the reason is that we used /NOENTRY in our Managed C++ DLL which worked fine with all compilers before we used VS17.7.
Now with VS17.7 there seems to be a change on how the atexit table/functionality in the CRT’s utility.cpp works. The functions __scrt_initialize_onexit_tables and __scrt_dllmain_before_initialize must now be called to work correctly whereas in previous VS versions it seems that this was not the case.
The internal analysis was much more complex and took a few days and advanced debugging tools.
The whole problem is related to "global static initializers" and if and their order when they are initialized.
There seems to be no guarantee in Microsoft C++ that they are actually initialized before usage i.e. the function ucrtbased.dll!_register_onexit_function crashed because the global static tables for the onexit code that is used by global static C++ class destructors and by `atexit`` functions were never initialized and the CRT wrote unitialized space.
The problem for other Visual Studio Customers seems to be, that /NOENTRY was there for a reason, because without /NOENTRY the mixed mode DLL used to crash for unknown reasons.
A recent suggested workaround has been posted here: https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475#T-N10570501 i.e. a fix from Hans Passant, see: https://stackoverflow.com/questions/41485935/entry-point-for-c-cli-x64-windowsforms-app-vs-2015/41489950#41489950 where he suggested to change the managed entry point to the (compiler mangled): ?mainCRTStartupStrArray@@FYMHP$01EAPE$AAVString@System@@@Z (64-bit only).
Our own solution to avoid those crashes was to get rid of ALL global static initializers in our code but also in any third-party code.
To help the development community that work with VS 2022, here are some notes from me (@mgexo):
- This is very hard to debug, even with enabled Microsoft Source Server and source codes of CRT (which by the way can be found e.g. in
C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\crt\src\vcruntime) - To find possible culprits, generate a "Map file" in the Linker process of the
.dll(or native.exe) by specifying/MAP:...filename to the linker e.g. add this parameter to the linker command line in Visual Studio/MAP:yourpathandfilename.map - In that
.mapfile then search for the tag "$initializer$" (dollar initializer dollar) and typically you can find all static initializers that are run by the CRT when the.dllor.exeis loaded. - You may be surprised in some cases about some of them accidentally happening i.e. when some programmer wrote static initializers into header files that were only included.
- Some of the static initalizers will also creep in through third party linked libraries e.g. from a staticed linked
.libor from the Microsoft .NET framework itself. - Tip: You can add your own variable for testing to see if/how it appears in the map file but make sure that it is a non-trivial class e.g. create a static
std::vector<std::vector<int>> g_myteststructure;at the top of a.cppfile and then search for it in the.mapfile.
Recommendation: Get rid of all such non-trivial global static initializers in DLLs that you can. Replace them with on-demand static initializers i.e. function calls that once calls they have a local static variable (also known as "Meyer's Singleton"). This avoids crashes and problems when a .dll (or .exe) is loaded/started and makes the startup faster in general.
Recommended Visual Studio Debugger Settings

Summary: It may happen, that DLLs crash on startup with random errors. This can happen if the Microsoft CRT is not initializing correctly inside the dll when it is started, which is related to the Entry Point see /ENTRY (Entry-Point Symbol). In fact, especially for managed DLLs the entry point normally needs to be empty, to allow correct initialization of the .dll. It then uses for example the _DllMainCRTStartup and _CRT_INIT function that is automatically generated by the compiler see dll_dllmain.obj in the .map file. In some cases however, these
are suddenly missing, which for example caused the issue in Visual Studio 2022 17.7.0 CRT crash when loading mixed mode DLL from .NET (regression since 17.6. and any earlier version) -- Developer Community link and the call stack trace above. If it is missing, then important initialization functions like __scrt_initialize_onexit_tables in utility.cpp of the CRT are not getting called and then standard functions like atexit that register a destructor will crash directly or randomly. This may happen if /NOENTRY is defined in the linker settings of the .dll.
Update 2024-01-23: The stories continues, see https://developercommunity.visualstudio.com/t/CRT-crash-when-loading-mixed-dll-from-N/10559475?space=21&sort=newest .