Initialization of Mixed Assemblies

Windows developers must always be wary of loader lock when running code during DllMain. However, there are some additional issues to consider when dealing with C++/clr mixed-mode assemblies.

Code within DllMain must not access the .NET Common Language Runtime (CLR). That means that DllMain should make no calls to managed functions, directly or indirectly; no managed code should be declared or implemented in DllMain; and no garbage collection or automatic library loading should take place within DllMain.

Causes of Loader Lock

With the introduction of the .NET platform, there are two distinct mechanisms for loading an execution module (EXE or DLL): one for Windows, which is used for unmanaged modules, and one for the CLR, which loads .NET assemblies. The mixed DLL loading problem centers around the Microsoft Windows OS loader.


The Windows loader guarantees that no code can access code or data in that DLL before it has been initialized, and that no code can redundantly load the DLL while it's partially initialized. To do it, the Windows loader uses a process-global critical section (often called the "loader lock") that prevents unsafe access during module initialization. As a result, the loading process is vulnerable to many classic deadlock scenarios. For mixed assemblies, the following two scenarios increase the risk of deadlock:

  • First, if users attempt to execute functions compiled to Microsoft intermediate language (MSIL) when the loader lock is held (from DllMain or in static initializers, for example), it can cause deadlock. Consider the case in which the MSIL function references a type in an assembly that hasn't been loaded. The CLR will attempt to automatically load that assembly, which may require the Windows loader to block on the loader lock. A deadlock occurs, since the loader lock is already held by code earlier in the call sequence. However, executing MSIL under loader lock doesn't guarantee that a deadlock will occur, which makes this scenario difficult to diagnose and fix. In some circumstances, such as when the DLL of the referenced type contains no native constructs and all of its dependencies contain no native constructs, the Windows loader isn't required to load the .NET assembly of the referenced type. Additionally, the required assembly or its mixed native/.NET dependencies may have already been loaded by other code. Consequently, the deadlocking can be difficult to predict, and can vary depending on the configuration of the target machine.

  • Second, when loading DLLs in versions 1.0 and 1.1 of the .NET Framework, the CLR assumed that the loader lock was not held, and performed several actions that are invalid under loader lock. Assuming that the loader lock isn't held is a valid assumption for purely .NET DLLs, but, because mixed DLLs execute native initialization routines, they require the native Windows loader, and therefore the loader lock. Consequently, even if the developer was not attempting to execute any MSIL functions during DLL initialization, there was still a small possibility of nondeterministic deadlock with versions 1.0 and 1.1 of the .NET Framework.

All non-determinism has been removed from the mixed DLL loading process. It was accomplished with these changes:


  • Unmanaged and managed initialization is performed in two separate and distinct stages. Unmanaged initialization takes place first (via DllMain), and managed initialization takes place afterwards, through a .NET-supported .cctor construct. The latter is completely transparent to the user unless /Zl or /NODEFAULTLIB are used. See/NODEFAULTLIB (Ignore Libraries) and /Zl (Omit Default Library Name) for more information.

Loader lock can still occur, but now it occurs reproducibly, and is detected. If DllMain contains MSIL instructions, the compiler generates warning Compiler Warning (level 1) C4747. Furthermore, either the CRT or the CLR will try to detect and report attempts to execute MSIL under loader lock. CRT detection results in runtime diagnostic C Run-Time Error R6033.


Scenarios and Workarounds







For more information on this scenario, see Impediments to Diagnosis.

Initializing Static Objects


The code below shows examples of static initializers that require dynamic initialization: a function call, object construction, and a pointer initialization. (These examples aren't static, but are assumed as defined in the global scope, which has the same effect.)

// dynamic initializer function generated
int a = init();
CObject o(arg1, arg2);
CObject* op = new CObject(arg1, arg2);






User-Supplied Functions Affecting Startup

There are several user-supplied functions on which libraries depend for initialization during startup. For example, when globally overloading operators in C++ such as the new and delete operators, the user-provided versions are used everywhere, including in C++ Standard Library initialization and destruction. As a result, C++ Standard Library and user-provided static initializers will invoke any user-provided versions of these operators.


For more information on this scenario, see Impediments to Diagnosis.

Custom Locales

If the user provides a custom global locale, this locale will be used for initializing all future I/O streams, including streams that are statically initialized. If this global locale object is compiled to MSIL, then locale-object member functions compiled to MSIL may be invoked while the loader lock is held.

There are three options for solving this problem:

The source files containing all global I/O stream definitions can be compiled using the /clr option. It prevents their static initializers from being executed under loader lock.



Impediments to Diagnosis

In some cases, it's difficult to detect the source of deadlocks. The following subsections discuss these scenarios and ways to work around these issues.

Implementation in Headers


Prior to Visual Studio 2005, the linker simply chooses the largest of these semantically equivalent definitions, to accommodate forward declarations, and scenarios when different optimization options are used for different source files. It creates a problem for mixed native/.NET DLLs.


As a convenience for users dealing with loader lock, the linker will choose the native implementation over the managed when presented with both. This default avoids the above issues. However, there are two exceptions to this rule in this release due to two unresolved issues with the compiler:

  • The call to an inline function is through a global static function pointer. This scenario is notable because virtual functions are called through global function pointers. For example,
#include "definesmyObject.h"
#include "definesclassC.h"

typedef void (*function_pointer_t)();

function_pointer_t myObject_p = &myObject;

#pragma unmanaged
void DuringLoaderlock(C & c)

Diagnosing in Debug Mode


How to debug loader lock issues

The diagnostic that the CLR generates when an MSIL function is invoked causes the CLR to suspend execution. In turn, that causes the Visual C++ mixed-mode debugger to be suspended as well when running the debuggee in-process. However, when attaching to the process, it isn't possible to obtain a managed callstack for the debuggee using the mixed debugger.


  1. Ensure that symbols for mscoree.dll and mscorwks.dll are available.

    You can make the symbols available in two ways. First, the PDBs for mscoree.dll and mscorwks.dll can be added to the symbol search path. To add them, open the symbol search path options dialog. (From the Tools menu, choose Options. In the left pane of the Options dialog box, open the Debugging node and choose Symbols.) Add the path to the mscoree.dll and mscorwks.dll PDB files to the search list. These PDBs are installed to the %VSINSTALLDIR%\SDK\v2.0\symbols. Choose OK.

    Second, the PDBs for mscoree.dll and mscorwks.dll can be downloaded from the Microsoft Symbol Server. To configure Symbol Server, open the symbol search path options dialog. (From the Tools menu, choose Options. In the left pane of the Options dialog box, open the Debugging node and choose Symbols.) Add this search path to the search list: Add a symbol cache directory to the symbol server cache text box. Choose OK.

  2. Set debugger mode to native-only mode.

    Open the Properties grid for the startup project in the solution. Select Configuration Properties > Debugging. Set the Debugger Type to Native-Only.

  3. Start the debugger (F5).

  4. When the /clr diagnostic is generated, choose Retry and then choose Break.

  5. Open the call stack window. (On the menu bar, choose Debug > Windows > Call Stack.) The offending DllMain or static initializer is identified with a green arrow. If the offending function is not identified, the following steps must be taken to find it.

  6. Open the Immediate window (On the menu bar, choose Debug > Windows > Immediate.)

  7. Enter .load sos.dll into the Immediate window to load the SOS debugging service.

  8. Enter !dumpstack into the Immediate window to obtain a complete listing of the internal /clr stack.

  9. Look for the first instance (closest to the bottom of the stack) of either _CorDllMain (if DllMain causes the issue) or _VTableBootstrapThunkInitHelperStub or GetTargetForVTableEntry (if a static initializer causes the issue). The stack entry just below this call is the invocation of the MSIL implemented function that attempted to execute under loader lock.

  10. Go to the source file and line number identified in the previous step and correct the problem using the scenarios and solutions described in the Scenarios section.






// initializing_mixed_assemblies.cpp
// compile with: /clr /LD
#pragma once
#include <stdio.h>
#include <windows.h>
struct __declspec(dllexport) A {
   A() {

   void Test() {

#pragma unmanaged
// Global instance of object
A obj;

extern "C"
   return true;

This example demonstrates issues in initialization of mixed assemblies:

// initializing_mixed_assemblies_2.cpp
// compile with: /clr initializing_mixed_assemblies.lib
#include <windows.h>
using namespace System;
#include <stdio.h>
#using "initializing_mixed_assemblies.dll"
struct __declspec(dllimport) A {
   void Test();

int main() {
   A obj;

This code produces the following output:

Module ctor initializing based on global instance of class.

Test called so linker does not throw away unused object.

See also

Mixed (Native and Managed) Assemblies