PRB:为 C++ DLL 项目建立托管扩展时出现链接器警告

文章翻译 文章翻译
文章编号: 814472 - 查看本文应用于的产品
展开全部 | 关闭全部

本文内容

症状

您在编译或链接时收到下列错误消息之一:
Linker Tools Error LNK2001
'unresolved external symbol "sysmbol" '

Linker Tools Warning LNK4210
'.CRT section exists; there may be unhandled static initializes or teminators'

Linker Tools Warning LNK4243
'DLL containing objects compiled with /clr is not linked with /NOENTRY; image may not run correctly'.
在下列情况下可能会出现这些警告:
  • 当您使用 /clr 开关编译链接对象时。
  • 当您建立下列项目之一时:ASP.NET Web 服务模板、类程序库模板或 Windows 控件库模板。
  • 当您已添加了代码并且这些代码使用全局变量或含有静态数据成员的本机类(即,不是 __gc 或 __value)时。例如,ActiveX 模板库 (ATL)、Microsoft 基础类 (MFC) 和 C 运行库 (CRT) 类
注意:对于不会 受到本文所述问题的影响的项目,您可能会收到 LNK2001 和 LNK4210 错误。但是,如果解决 LNK2001 或 LNK4210 警告导致生成 LNK4243 警告,或者如果链接该项目生成 LNK4243 警告,则该项目肯定 受到本文所述问题的影响。

原因

默认情况下,下列项目作为动态链接库 (DLL) 创建,与本机程序库(例如 CRT、ATL 或 MFC)之间没有任何链接,并且没有任何全局变量或具有静态数据成员的本机类:
  • ASP.NET Web 服务模板
  • 类程序库模板
  • Windows 控件库模板
如果您添加使用全局变量或具有静态数据成员的本机类的代码(例如,ATL、MFC 和 CRT 程序库使用全局变量),您将在编译时收到链接器错误消息。如果是这样,您必须添加代码以手动初始化静态变量。有关如何执行此操作的更多信息,请参见本文的“解决办法”一节。

为方便起见,本文此后会将全局变量和本机类的静态数据成员称为“静态变量”。

此问题是由混合 DLL 加载问题引起的。混合 DLL(即同时包含托管代码和本机代码的 DLL)在加载到进程地址空间时,在某些情况下会遇到死锁的情形,尤其是在系统负荷较重时。前面提到的链接器错误消息已在链接器中启用,以确保客户能够意识到遇到死锁情形的可能性并了解本文档中描述的变通办法。有关混合 DLL 加载问题的详细说明,请参阅以下白皮书:
Mixed DLL Loading Problem(混合 DLL 加载问题)
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

解决方案

默认情况下,作为 DLL 创建的 C++ 项目托管扩展不与 C 运行库 (CRT) 程序库、ATL 或 MFC 等本机 C/C++ 程序库链接,并且不使用任何静态变量。另外,项目设置指定应在启用 /NOENTRY 选项的情况下链接该 DLL。

这样做的原因是:使用入口点链接会导致托管代码在 DllMain 执行期间运行,而这是不安全的(请参见 DllMain,以了解您可以在它的执行过程中完成的一组有限的操作)。

没有入口点的 DLL 无法初始化静态变量,整型等非常简单的类型除外。通常情况下,在 /NOENTRY DLL 中没有静态变量。

ATL、MFC 和 CRT 程序库都依赖静态变量,因此,如果不首先进行修改,您也不能从这些 DLL 内部使用这些程序库。

如果您的混合模式 DLL 需要使用静态变量或依赖静态变量的程序库(例如 ATL、MFC 或 CRT),则您必须修改您的 DLL 以便可以手动初始化这些静态变量。

手动初始化的第一步是确保您禁用了自动初始化代码(对于混合 DLL 而言自动初始化代码是不安全的,并可能导致死锁)。要禁用初始化代码,请按照下列步骤操作。

删除托管 DLL 的入口点

  1. 使用 /NOENTRY 链接。在“解决方案资源管理器”中,右键单击项目节点,单击“属性”。在“属性页”对话框中,依次单击“链接器”、“命令行”,然后将此开关添加到“附加选项”字段。
  2. 链接 msvcrt.lib。在“属性页”对话框中,依次单击“链接器”、“输入”,然后将 msvcrt.lib 添加到“其他依存项”属性中。
  3. 删除 nochkclr.obj。在“输入”页(与上一步中使用的页面相同)中,从其他依存项属性中删除 nochkclr.obj
  4. 链接 CRT。在“输入”页(与上一步中使用的页面相同)中,将 __DllMainCRTStartup@12 添加到“强制符号引用”属性中。

    如果您要使用命令提示,请通过以下操作指定上述项目设置:
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

修改使用该 DLL 的组件以便进行手动初始化

删除显式入口点后,您必须修改使用该 DLL 的组件以便进行手动初始化,具体取决于 DLL 的实现方式:
  • 您的 DLL 是使用 DLL 导出 (__declspec(dllexport)) 进入的,而且如果您的消费程序与您的 DLL 静态链接或动态链接,则它们不能使用托管代码。
  • 您的 DLL 是基于 COM 的 DLL。
  • 您的 DLL 的消费程序可以使用托管代码,并且您的 DLL 包含 DLL 导出或托管入口点。

修改您使用 DLL 导出和不能使用托管代码的消费程序进入的 DLL

要修改您使用 DLL 导出 (__declspec(dllexport)) 和不能使用托管代码的消费程序进入的 DLL,请按照下列步骤操作:
  1. 向您的 DLL 中添加两个新导出,如以下代码所示:
    // 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.
    }
    
  2. 您的 DLL 可以有若干个消费程序。如果您的 DLL 确实有多个消费程序,请将以下代码添加到 DLL .def 文件的导出节:
    DllEnsureInit	PRIVATE
    DllForceTerm	PRIVATE
    
    如果您未添加这些代码行并且您有两个能够导出函数的 DLL,则与该 DLL 链接的应用程序将出现链接错误。通常,导出的函数具有相同的名称。在存在多个消费程序的情况下,每个消费程序都可以与您的 DLL 静态或动态链接。
  3. 如果消费程序与该 DLL 静态链接,则在首次使用该 DLL 之前,或者在使用应用程序中任何依赖于该 DLL 的项目之前,请添加以下调用:
    // 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. 在应用程序中最后一次使用该 DLL 之后,添加以下代码:
    // 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. 如果消费程序与该 DLL 动态链接,请如下所示插入代码:
    • 在首次对该 DLL 使用 LoadLibrary 后,紧接着插入代码片段 1(参见步骤 3)。
    • 在最后一次对该 DLL 使用 FreeLibrary 之前的紧邻位置,插入代码片段 2(参见步骤 4)。

修改基于 COM 的 DLL

  • 如以下代码中所示,修改 DLL 导出函数 DllCanUnloadNowDllGetClassObjectDllRegisterServerDllUnregisterServer
    //  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;
    }
    

修改包含消费程序并且这些消费程序使用托管代码和 DLL 导出或托管入口点的 DLL

要修改包含消费程序并且这些消费程序使用托管代码和 DLL 导出或托管入口点的 DLL,请按照下列步骤操作:
  1. 实现一个使用静态成员函数进行初始化和终止的托管类。将一个 .cpp 文件添加到您的项目中,以实现一个使用静态成员进行初始化和终止的托管类:
    // 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. 在您引用 DLL 之前和使用完 DLL 之后调用这些函数。在 main 中调用初始化成员函数和终止成员函数:
    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main() {
    	int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }
    

Visual C++ .NET 2002 用户

注意:虽然链接器错误消息 LNK4243 在 Visual C++ .NET 2002 产品版本中不存在,但是仍然建议 Visual C++ .NET 2002 用户在开发混合 DLL 时遵循前面提到的原则。

Visual C++ .NET 2003 产品版本包含一个附加标题以使手动初始化更加方便。要使本文列出的解决方案适用于 Visual C++ .NET 2002,您必须向名为 _vcclrit.h 的项目中添加一个包含以下文本的头文件:
/***
* _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);
}

参考

BUG:AppDomainUnloaded Exception When You Use Managed Extensions for C++ Components有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
309694

属性

文章编号: 814472 - 最后修改: 2007年5月11日 - 修订: 5.2
这篇文章中的信息适用于:
  • Microsoft Visual C++ .NET 2003 Standard Edition
  • Microsoft Visual C++ .NET 2002 标准版
  • Microsoft .NET Framework 1.0
  • Microsoft .NET Framework 1.1
关键字:?
kbdll kbprb kbijw kbcominterop kbmanaged KB814472
Microsoft和/或其各供应商对于为任何目的而在本服务器上发布的文件及有关图形所含信息的适用性,不作任何声明。 所有该等文件及有关图形均"依样"提供,而不带任何性质的保证。Microsoft和/或其各供应商特此声明,对所有与该等信息有关的保证和条件不负任何责任,该等保证和条件包括关于适销性、符合特定用途、所有权和非侵权的所有默示保证和条件。在任何情况下,在由于使用或运行本服务器上的信息所引起的或与该等使用或运行有关的诉讼中,Microsoft和/或其各供应商就因丧失使用、数据或利润所导致的任何特别的、间接的、衍生性的损害或任何因使用而丧失所导致的之损害、数据或利润不负任何责任。

提供反馈

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com