PRB:建置 Managed Extensions for C++ DLL 專案時,連結器發出警告

文章翻譯 文章翻譯
文章編號: 814472 - 檢視此文章適用的產品。
全部展開 | 全部摺疊

在此頁中

徵狀

編譯或連結時,您會收到下列其中一個錯誤訊息:
Linker Tools Error LNK2001
無法在外部之程式庫中找到或連結所需之資料型別或函式的符號 "symbol"。

Linker Tools Warning LNK4210
.CRT 區段存在; 可能有無法處理的靜態初始設定式或終端子

Linker Tools Warning LNK4243
使用 /clr 編譯含有物件的 DLL 無法以 /NOENTRY 連結; 映像可能無法正確執行。
在下列情況中可能會出現這些警告:
  • 使用 /clr 參數編譯連結物件。
  • 建置下列其中一個專案:ASP.NET Web Service Template、Class Library Template 或 Windows Control Library Template。
  • 您已經新增程式碼,而程式碼是使用具有靜態資料成員的全域變數或原生類別 (即 not __gc or __value)。 例如,ActiveX Template Library (ATL)、Microsoft Foundation Classes (MFC) 和 C Run-Time (CRT) 類別。
注意:您可能會收到 LNK2001 和 LNK4210 錯誤,但是專案不會受到本文所述問題的影響。然而,如果解決 LNK2001 或 LNK4210 警告時卻導致發生 LNK4243 警告,或者連結專案時產生了 LNK4243 警告,本文所述的問題一定對專案造成影響。

發生的原因

依照預設會建立下列專案做為動態連結程式庫 (DLL),但不會建立連至原生程式庫的連結 (例如 CRT、ATL 或 MFC),也不會建立任何具有靜態資料成員的全域變數或原生類別:
  • ASP.NET Web Service Template
  • Class Library Template
  • Windows Control Library Template
如果您新增的程式碼是使用具有靜態資料成員的全域變數或原生類別 (例如,ATL、MFC 和 CRT 程式庫使用全域變數),就會在編譯時間收到連結器錯誤訊息。 出現錯誤時,您必須新增程式碼,才能手動初始化靜態變數。如需有關如何執行這項操作的詳細資訊,請參閱本文<解決方案>一節。

為了您的方便,本文後面會從「靜態」或「靜態變數」的觀點地進一步說明原生類別的全域變數和靜態資料成員。

這個問題是由混合 DLL 載入問題所造成。當載入混合 DLL (即包含 Managed 和機器碼的 DLL) 至處理序位址空間時,特別是在系統負載等某些情況下,混合 DLL 可能會發生死結 (Deadlock) 的情形。連結器會啟用前面所提到的連結器錯誤訊息,以確保客戶知道發生死結的可能性,以及本文所述的解決方法。如需有關混合 DLL 載入問題的詳細說明,請參閱下列白皮書:
混合 DLL 載入問題 (英文)
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

解決方案

依照預設所建立的 Managed Extensions for C++ 專案不會連結至原生 C/C++ 程式庫 (例如 C run-time (CRT) 程式庫、ATL 或 MFC),也不會使用任何靜態變數。此外,專案設定會指定 DLL 與已啟用的 /NOENTRY 選項加以連結。

這麼做的原因是,因為與任何項目連結會造成 Managed 程式碼在 DllMain 期間執行,如此並不安全 (查看 DllMain,以便在它的範圍內執行您所能執行的有限操作)。

沒有進入點的 DLL 無法初始化靜態變數,除非是非常簡單的型別,例如整數。通常,/NOENTRY DLL 中不會含有靜態變數。

ATL、MFC 和 CRT 程式均依賴靜態變數,所以在未進行修改之前,您還是無法在這些 DLL 內使用這些程式庫。

如果您的混合模式 DLL 必須使用依賴靜態 (例如 ATL、MFC 或 CRT) 的靜態或程式庫,您必須修改 DLL,才能手動初始化靜態。

手動初始化的第一個步驟是,確認您停用自動初始化程式碼,因為自動初始化程式與混合 DLL 一起運作不是安全的做法,並且可能造成死結。如果要停用初始化程式碼,請依照下列步驟執行。

移除 Managed 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,就會無法使用 Managed 程式碼。
  • 您的 DLL 是以 COM 為基礎的 DLL。
  • 您的 DLL 消費者可以使用 Managed 程式碼,並且您的 DLL 包含了 DLL 匯出或 Managed 進入點。

修改透過 DLL 匯出而進入的 DLL,以及消費者無法使用 Managed 程式碼的 DLL

如果要修改透過 DLL 匯出 (__declspec(dllexport)) 而進入的 DLL,以及消費者無法使用 Managed 程式碼的 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;
    }
    

修改包含使用 Managed 程式碼、DLL 匯出或 Managed 進入點之消費者的 DLL

如果要修改包含使用 Managed 程式碼、DLL 匯出或 Managed 進入點之消費者的 DLL,請依照下列步驟執行:
  1. 使用靜態成員函式實作 Managed 類別,以便執行初始化和終止。在專案中新增 .cpp 檔案,並使用靜態成員實作 Managed 類別,以便執行初始化和終止:
    // 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.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 使用者

注意:Visual C++ .NET 2002 產品版本沒有連結器錯誤訊息 LNK4243,但還是建議 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);
}

?考

如需詳細資訊,請按一下下面的文件編號,檢視「Microsoft 知識庫」中的文件:
309694 BUG:AppDomainUnloaded Exception When You Use Managed Extensions for C++ Components

屬性

文章編號: 814472 - 上次校閱: 2007年5月11日 - 版次: 7.2
這篇文章中的資訊適用於:
  • Microsoft Visual C++ .NET 2003 Standard Edition
  • Microsoft Visual C++ .NET 2002 Standard Edition
  • Microsoft .NET Framework 1.0
  • Microsoft .NET Framework 1.1
關鍵字:?
kbtshoot 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