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. CAUSEThe 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: RESOLUTIONManaged 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- 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.
- Link msvcrt.lib. In the Property Pages dialog box, click
Linker, click Input., and then add msvcrt.lib to the Additional Dependencies
property.
- Remove nochkclr.obj. On the Input page (same page as in the previous
step), remove nochkclr.obj from the Additional Dependencies property.
- 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 InitializiationAfter 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 CodeTo modify DLLs that you enter by using dll exports
(__declspec(dllexport)) and consumers that cannot use managed code, follow
these steps:
- 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:
- Click Project, and then click
ProjectName Properties.
Note ProjectName is a placeholder for the
name of the project. - Expand Configuration Properties, and then click
General.
- In the right pane, click to select Common Language Runtime Support, Old Syntax
(/clr:oldSyntax) in the
Common Language Runtime support project settings.
- 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. - 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. - 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
}
- 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
}
- 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 PointsTo modify DLL that contains consumers that use managed code and
dll exports or managed entry points, follow these steps:
- 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 */
- 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 2002Note: 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: | Major | Last Reviewed: | 1/9/2006 |
---|
Keywords: | kbCOMInterop kbManaged kbDLL kbIJW kbprb KB814472 kbAudDeveloper kbAudITPRO |
---|
|
|
©2004 Microsoft Corporation. All rights reserved.
|
|