You receive linker warnings when you build Managed Extensions for C++ DLL projects (814472)



The information in this article applies to:

  • Microsoft Visual C++ 2005 Express Edition
  • Microsoft Visual C++ .NET (2003)
  • Microsoft Visual C++ .NET (2002)
  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 1.0

SYMPTOMS

You receive one of the following error messages at compile time or at link time:
Linker Tools Error LNK2001
'unresolved external symbol "symbol" '

Linker Tools Warning LNK4210
'.CRT section exists; there may be unhandled static initializes or teminators'You receive Linker Warnings when you build Managed Extensions for C++ DLL projects

Linker Tools Warning LNK4243
'DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly'.
These warnings may occur during the following circumstances:
  • When you compile linking objects with the /clr switch.
  • When you are building one of the following projects: ASP.NET Web Service Template; Class Library Template; or Windows Control Library Template.
  • When you have added code that uses global variables or native classes (that is, not __gc or __value) with static data members. For example, the ActiveX Template Library (ATL), Microsoft Foundation Classes (MFC), and the C Run-Time (CRT) classes
Note: you may receive the LNK2001 and LNK4210 errors with projects that are not affected by the issue described in this article. However, the project definitely is affected by the issue described in this article if resolving a LNK2001 or LNK4210 warning leads to a LNK4243 warning, or if linking the project generates a LNK4243 warning.

CAUSE

The following projects are created by default as a dynamic link library (DLL) without any linkage to native libraries (such as the CRT, ATL, or MFC), and without any global variables or native classes with static data members:
  • ASP.NET Web Service Template
  • Class Library Template
  • Windows Control Library Template
If you add code that uses global variables or native classes with static data members (for example, the ATL, MFC, and CRT libraries use global variables), you will receive linker error messages at compile time. When this occurs, you must add code to manually initialize the static variables. For more information about how to do this, see the "Resolution" section of this article.

For convenience, this article refers to global variables and static data members of native classes as "statics" or "static variables" from this point forward.

This problem is caused by the mixed DLL loading problem. Mixed DLLs (that is, DLLs that contain both managed and native code) can encounter deadlock scenarios under some circumstances when they are loaded into the process address space, especially when the system is under stress. The linker error messages mentioned earlier were enabled in the linker to make sure that customers are aware of the potential for deadlock and the workarounds that are described in this document. For a detailed description of the mixed DLL loading problem, see the following whitepaper:

RESOLUTION

Managed Extensions for C++ projects that are created as DLLs by default do not link to native C/C++ libraries such as the C run-time (CRT) library, ATL, or MFC and do not use any static variables. Additionally, the project settings specify that the DLLs should be linked with the /NOENTRY option enabled.

This is done because linking with an entry point causes managed code to run during DllMain, which is not safe (see DllMain for the limited set of things you can do during its scope).

A DLL without an entry point has no way to initialize static variables except for very simple types such as integers. You do not typically have static variables in a /NOENTRY DLL.

The ATL, MFC and CRT libraries all rely on static variables, so you also cannot use these libraries from within these DLLs without first making modifications.

If your mixed-mode DLL needs to use statics or libraries that depend on statics (such as ATL, MFC, or CRT), then you must modify your DLL so that the statics are manually initialized.

The first step to manual initialization is to make sure that you disable the automatic initialization code, which is unsafe with mixed DLLs and can cause deadlock. To disable the initialization code, follow the steps.

Remove the Entry Point of the Managed DLL

  1. Link with /NOENTRY. In Solution Explorer, right-click the project node, click Properties. In the Property Pages dialog box, click Linker, click Command Line, and then add this switch to the Additional Options field.
  2. Link msvcrt.lib. In the Property Pages dialog box, click Linker, click Input., and then add msvcrt.lib to the Additional Dependencies property.
  3. Remove nochkclr.obj. On the Input page (same page as in the previous step), remove nochkclr.obj from the Additional Dependencies property.
  4. Link in the CRT. On the Input page (same page as in the previous step), add __DllMainCRTStartup@12 to the Force Symbol References property.

    If you are using the command prompt, specify the above project settings with the following:
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

Modify the Components that Consume the DLL for Manual Initializiation

After you remove the explicit entry point, you must modify components that consume the DLL for manual initialization, depending on the way that your DLL is implemented:
  • Your DLL is entered using DLL exports (__declspec(dllexport)), and your consumers cannot use managed code if they are linked statically or dynamically to your DLL.
  • Your DLL is a COM-based DLL.
  • Consumers of your DLL can use managed code, and your DLL contains either DLL exports or managed entry points.

Modify DLLs That You Enter By Using DLL Exports and Consumers That Cannot Use Managed Code

To modify DLLs that you enter by using dll exports (__declspec(dllexport)) and consumers that cannot use managed code, follow these steps:
  1. Add two new exports to your DLL, as shown in the following code:
    // init.cpp
    
    #include <windows.h>
    #include <_vcclrit.h>
    
    // Call this function before you call anything in this DLL.
    // It is safe to call from multiple threads; it is not reference
    // counted; and is reentrancy safe.
    
    __declspec(dllexport) void __cdecl DllEnsureInit(void)
    {
    	// Do nothing else here. If you need extra initialization steps,
    	// create static objects with constructors that perform initialization.
    	__crt_dll_initialize();
    	// Do nothing else here.
    }
    
    // Call this function after this whole process is totally done
    // calling anything in this DLL. It is safe to call from multiple
    // threads; is not reference counted; and is reentrancy safe.
    // First call will terminate.
    
    __declspec(dllexport) void __cdecl DllForceTerm(void)
    {
    	// Do nothing else here. If you need extra terminate steps, 
    	// use atexit.
    	__crt_dll_terminate();
    	// Do nothing else here.
    }
    
    Note In Visual C++ 2005, you must add the common language runtime support compiler option (/clr:oldSyntax) to successfully compile the previous code sample. To add the common language runtime support compiler option, follow these steps:
    1. Click Project, and then click ProjectName Properties.

      Note ProjectName is a placeholder for the name of the project.
    2. Expand Configuration Properties, and then click General.
    3. In the right pane, click to select Common Language Runtime Support, Old Syntax (/clr:oldSyntax) in the Common Language Runtime support project settings.
    4. Click Apply, and then click OK.
    For more information about common language runtime support compiler options, visit the following Microsoft Developer Network (MSDN) Web site: These steps apply to the whole article.
  2. Your DLL can have several consumers. If it does have multiple consumers, add the following code to the DLL .def file in the exports section:
    DllEnsureInit	PRIVATE
    DllForceTerm	PRIVATE
    
    If you do not add these lines, and if you have two DLLs that export functions, the application that links to the DLL will have link errors. Typically, the exported functions have the same names. In a multiconsumer case, each consumer can be linked statically or dynamically to your DLL.
  3. If the consumer is statically linked to the DLL, before you use the DLL the first time, or before you use anything that depends on it in your application, add the following call:
    // Snippet 1
    
    typedef void (__stdcall *pfnEnsureInit)(void);
    typedef void (__stdcall *pfnForceTerm)(void);
    
    {
    	// ... initialization code
    	HANDLE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// Exit, return; there is nothing else to do.
    	}
    	pfnEnsureInit pfnDll=::( pfnEnsureInit) GetProcAddress(hDll, 
       "DllEnsureInit");
    	if(!pfnDll)
    	{
    		// Exit, return; there is nothing else to do.
    	}
    	
    	pfnDll();
    	
    	// ... more initialization code
    }
    
  4. After the last use of the DLL in your application, add the following code:
    // Snippet 2
    
    {
    	// ... termination code
    	HANDLE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// exit, return; there is nothing else to do
    	}
    	pfnForceTerm pfnDll=::( pfnForceTerm) GetProcAddress(hDll, 
       "DllForceTerm");
    	if(!pfnDll)
    	{
    		// exit, return; there is nothing else to do
    	}
    	
    	pfnDll();
    	
    	// ... more termination code
    }
    
  5. If the consumer is dynamically linked to the DLL, insert code as follows:
    • Insert snippet 1 (see step 3) immediately after the first LoadLibrary for the DLL .
    • Insert snippet 2 (see step 4) immediately before the last FreeLibrary for the DLL.

To Modify COM-based DLL

  • Modify the DLL export functions DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer as demonstrated in the following code:
    //  Implementation of DLL Exports.
    
    #include <_vcclrit.h>
    
    STDAPI DllCanUnloadNow(void)
    {
        
        if ( _Module.GetLockCount() == 0 )
    	{
    		__crt_dll_terminate();
            return S_OK;
    	}
        else
        {
            return S_FALSE;
    
        }
    	
    }
    
    STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
    {
        if ( !( __crt_dll_initialize() ) )
    	{
    		return E_FAIL;
    	}
        else
        {
            return _Module.GetClassObject(rclsid, riid, ppv);
        }
    }
    
    STDAPI DllRegisterServer(void)
    {
    	if ( !( __crt_dll_initialize() ) )
    	{
    		return E_FAIL;
    	}
    	// Call your registration code here
        HRESULT hr = _Module.RegisterServer(TRUE)
     
        return hr;
    }
    
    
    STDAPI DllUnregisterServer(void)
    { 
        HRESULT hr = S_OK;
    	__crt_dll_terminate();
    
        // Call your unregistration code here
        hr = _Module.UnregisterServer(TRUE);
        return hr;
    }
    

Modify DLL That Contains Consumers That Use Managed Code and DLL Exports or Managed Entry Points

To modify DLL that contains consumers that use managed code and dll exports or managed entry points, follow these steps:
  1. Implement a managed class with static member functions for initialization and termination. Add a .cpp file to your project, implementing a managed class with static members for initialization and termination:
    // ManagedWrapper.cpp
    
    // This code verifies that DllMain is not automatically called 
    // by the Loader when linked with /noentry. It also checks some
    // functions that the CRT initializes.
    
    #include <windows.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    #include "_vcclrit.h"
    
    #using <mscorlib.dll>
    using namespace System;
    
    public __gc class ManagedWrapper {
    public:
    	static BOOL minitialize() {
    		BOOL retval = TRUE;
    		try {
               retval =  __crt_dll_initialize();
    		} catch(System::Exception* e) {
    			Console::WriteLine(e->Message);
    			retval = FALSE;
    		}
    		return retval;
    	}
    	static BOOL mterminate() {
    		BOOL retval = TRUE;
    		try {
                retval = __crt_dll_terminate();
    		} catch(System::Exception* e) {
    						Console::WriteLine(e->Message);
    			retval = FALSE;
    		}
    		return retval;
    	}
    };
    
    BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID 
    lpvReserved) {
    	Console::WriteLine(S"DllMain is called...");
    	return TRUE;
    } /* DllMain */
    
  2. Call these functions before you refer to the DLL and after you have finished using it. Call the initialization and termination member functions in main:
    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main() {
    	int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }
    

Users of Visual C++ .NET 2002

Note: Although linker error message LNK4243 does not exist in the Visual C++ .NET 2002 product release, users of Visual C++ .NET 2002 are advised to follow the guidelines mentioned earlier when developing mixed DLLs.

The Visual C++ .NET 2003 product release contains an additional header to make manual initialization more convenient. To make the solutions listed in this article work with Visual C++ .NET 2002, you must add a header file to your project called _vcclrit.h with the following text:
/***
* _vcclrit.h
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
* Purpose:
*       This file defines the functions and variables used by user
*       to initialize CRT and the dll in IJW scenario.
*
****/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

extern IMAGE_DOS_HEADER __ImageBase;

BOOL WINAPI _DllMainCRTStartup(
        HANDLE  hDllHandle,
        DWORD   dwReason,
        LPVOID  lpreserved
        );
#ifdef __cplusplus
}
#endif

#ifdef _cplusplus
#define __USE_GLOBAL_NAMESPACE  ::
#else
#define __USE_GLOBAL_NAMESPACE
#endif

// Used to lock 
__declspec( selectany ) LONG  volatile __lock_handle = 0;

// Init called
__declspec(selectany) BOOL volatile __initialized = FALSE;

// Term called
__declspec( selectany ) BOOL volatile __terminated = FALSE;

__inline BOOL WINAPI __crt_dll_initialize()
{
    // Try to make the variable names unique, so that the variables 
    // do not even clash with macros.
    static BOOL volatile (__retval) = FALSE;
    static DWORD volatile (__lockThreadId) = 0xffffffff;
    DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
    int (__int_var)=0;
    
    // Take Lock; this is needed for multithreaded scenario. 
    // Additionally, the threads need to wait here to make sure that the dll 
    // is initialized when they get past this function.
    while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
	{
        ++(__int_var);
        if ((__lockThreadId) == (__currentThreadId)) 
        {
            return TRUE;
        }
		__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

        // If you hang in this loop, this implies that your 
        // dllMainCRTStartup is hung on another thread. 
        // The most likely cause of this is a hang in one of your 
        // static constructors or destructors.
	}
    // Note: you do not really need any interlocked stuff here because the 
    // writes are always in the lock. Only reads are outside the lock.
    (__lockThreadId) = (__currentThreadId);
    __try {
        if ( (__terminated) == TRUE )
        {
            (__retval) = FALSE;
        }
        else if ( (__initialized) == FALSE )
        {
            (__retval) = (_DllMainCRTStartup)( ( HINSTANCE )( &__ImageBase ), DLL_PROCESS_ATTACH, 0 );
            (__initialized) = TRUE;
        }

    } __finally {
        // revert the __lockThreadId
        (__lockThreadId) = 0xffffffff;
        // Release Lock
       __USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
    }
    return (__retval);
}

__inline BOOL WINAPI __crt_dll_terminate()
{
    static BOOL volatile (__retval) = TRUE;
    static DWORD volatile (__lockThreadId) = 0xffffffff;
    DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
    int (__int_var)=0;
    
    // Take Lock; this lock is needed to keep Terminate 
    // in sync with Initialize.
    while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
	{
        ++(__int_var);
        if ((__lockThreadId) == (__currentThreadId)) 
        {
            return TRUE;
        }
		__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

        // If you hang in this loop, this implies that your 
        // dllMainCRTStartup is hung on another thread. The most likely 
        // cause of this is a hang in one of your static constructors 
        // or destructors.
    }
    // Note: you do not really need any interlocked stuff here because the 
    // writes are always in the lock. Only reads are outside the lock.
    (__lockThreadId) = (__currentThreadId);
    __try {
        if ( (__initialized) == FALSE )
        {
            (__retval) = FALSE;
        }
        else if ( (__terminated) == FALSE )
        {
            (__retval) = _DllMainCRTStartup( ( HINSTANCE )( &(__ImageBase) ), DLL_PROCESS_DETACH, 0 );
            (__terminated) = TRUE;
        }
    } __finally {
        // revert the __lockThreadId
        (__lockThreadId) = 0xffffffff;
        // Release Lock
       __USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
    }
    return (__retval);
}

REFERENCES

For more information, click the following article number to view the article in the Microsoft Knowledge Base:

309694 BUG: AppDomainUnloaded exception when you use managed extensions for Visual C++ components


Modification Type:MajorLast Reviewed:1/9/2006
Keywords:kbCOMInterop kbManaged kbDLL kbIJW kbprb KB814472 kbAudDeveloper kbAudITPRO