Ostrzeżenia konsolidatora podczas kompilowania projektów DLL dodatku Managed Extensions dla języka C++

Tłumaczenia artykułów Tłumaczenia artykułów
Numer ID artykułu: 814472 - Zobacz jakich produktów dotyczą zawarte w tym artykule porady.
Rozwiń wszystko | Zwiń wszystko

Na tej stronie

Symptomy

Podczas kompilacji lub konsolidacji wyświetlany jest jeden z następujących komunikatów o błędach:
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'.
Te ostrzeżenia mogą być wyświetlone w następujących okolicznościach:
  • Podczas kompilowania obiektów połączonych z przełącznikiem /clr.
  • Podczas kompilowania jednego z następujących projektów: szablonu ASP.NET Web Service, szablonu Class Library lub szablonu Windows Control Library.
  • Po dodaniu kodu korzystającego ze zmiennych globalnych lub klas macierzystych (innych niż __gc lub __value) ze statycznymi członkami danych. Przykładem może być biblioteka ActiveX Template Library (ATL), klasy Microsoft Foundation Class (MFC) oraz klasy C Run-Time (CRT).
Uwaga: Błędy LNK2001 i LNK4210 mogą się odnosić do projektów, których nie dotyczy problem opisany w tym artykule. W projekcie na pewno występuje problem opisany w tym artykule, jeśli rozwiązanie problemu z ostrzeżeniem LNK2001 lub LNK4210 powoduje wyświetlenie ostrzeżenia LNK4243 lub jeśli konsolidacja projektu powoduje pojawienie się błędu LNK4243.

Przyczyna

Następujące projekty są domyślnie tworzone jako biblioteka dołączana dynamicznie (DLL, Dynamic Link Library) bez dołączania jakichkolwiek bibliotek macierzystych (takich jak CRT, ATL lub MFC) oraz bez zmiennych globalnych lub macierzystych klas ze statycznymi członkami danych:
  • Szablon ASP.NET Web Service
  • Szablon Class Library
  • Szablon Windows Control Library
W przypadku dodania kodu wykorzystującego zmienne globalne lub macierzyste klasy ze statycznymi członkami danych (na przykład biblioteki ATL, MFC i CRT stosują zmienne globalne) w czasie kompilacji pojawi się komunikat o błędzie konsolidatora. W takim wypadku należy ręcznie dodać kod inicjujący zmienne statyczne. Aby uzyskać więcej informacji, jak to zrobić, zobacz sekcję „Rozwiązanie” w tym artykule.

Dla ułatwienia w tym artykule zmienne globalne oraz statycznych członków danych klas macierzystych określa się dalej jako „zmienne statyczne”.

Takie zachowanie jest spowodowane problemem z ładowaniem mieszanych bibliotek DLL. W mieszanych bibliotekach DLL (tzn. w bibliotekach zawierających zarówno kod zarządzany, jak i kod macierzysty) w pewnych warunkach mogą wystąpić sytuacje prowadzące do zakleszczenia, jeśli zostaną one załadowane do przestrzeni adresowej procesu, zwłaszcza gdy system jest mocno obciążony. Wspomniane wcześniej komunikaty o błędach konsolidatora są włączone w celu zyskania pewności, że klient jest świadomy możliwości zakleszczenia i zastosowania obejść problemów opisanych w tym dokumencie. Szczegółowy opis problemu z ładowaniem mieszanych bibliotek DLL jest dostępny w następującej dokumentacji:
Problemy z ładowaniem mieszanych bibliotek DLL
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

Rozwiązanie

Projekty dodatku Managed Extensions dla języka C++ tworzone jako biblioteki DLL domyślnie nie dołączają macierzystych bibliotek języka C/C++, takich jak CRT, ATL lub MFC, oraz nie używają zmiennych statycznych. Ponadto ustawienia projektu określają, że biblioteki DLL powinny być konsolidowane z włączoną opcją /NOENTRY.

Jest to spowodowane tym, że konsolidacja z punktem wejścia powoduje uruchomienie kodu zarządzanego w czasie działania procedury DllMain, co nie jest bezpieczne (zobacz ograniczony zestaw operacji, jakie można wykonywać w czasie działania procedury DllMain).

Biblioteka DLL bez punktu wejścia nie ma możliwości inicjowania zmiennych statycznych z wyjątkiem zmiennych bardzo prostych typów, takich jak zmienne całkowite. W bibliotekach DLL z włączoną opcją /NOENTRY na ogół nie występują zmienne statyczne.

Wszystkie biblioteki ATL, MFC i CRT opierają się na zmiennych statycznych, dlatego przed skorzystaniem z takich bibliotek DLL należy wprowadzić pewne modyfikacje.

Jeśli mieszane biblioteki DLL są potrzebne, bo użytkownik musi korzystać ze zmiennych statycznych lub bibliotek opartych na zmiennych statycznych (takich jak ATL, MFC lub CRT), należy zmodyfikować bibliotekę DLL, tak aby zmienne statyczne były inicjowane ręcznie.

Pierwszy krok przy ręcznej inicjacji to upewnienie się, że został wyłączony kod inicjacji automatycznej, który nie jest bezpieczny w mieszanych bibliotekach DLL i może powodować zakleszczenie. Aby wyłączyć kod inicjacji, wykonaj następujące kroki:

Usuwanie punktu wejścia zarządzanej biblioteki DLL

  1. Wykonaj konsolidację z opcją /NOENTRY. W oknie Solution Explorer kliknij prawym przyciskiem myszy węzeł projektu i kliknij polecenie Properties. W oknie dialogowym Property Pages kliknij przycisk Linker, kliknij przycisk Command Line, a następnie dodaj ten przełącznik do pola Additional Options.
  2. Skonsoliduj bibliotekę msvcrt.lib. W oknie dialogowym Property Pages kliknij przycisk Linker, kliknij przycisk Input, a następnie dodaj wpis msvcrt.lib do właściwości Additional Dependencies.
  3. Usuń wpis nochkclr.obj. Na stronie Input (tej samej, co w poprzednim kroku) usuń wpis nochkclr.obj z właściwości Additional Dependencies.
  4. Dołącz bibliotekę CRT. Na stronie Input (tej samej, co w poprzednim kroku) dodaj wpis __DllMainCRTStartup@12 do właściwości Force Symbol References.

    Jeżeli korzystasz z wiersza polecenia, określ powyższe ustawienia projektu w następujący sposób:
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

Modyfikowanie składników, które przyjmują bibliotekę DLL, pod kątem ręcznej inicjacji

Po jawnym usunięciu punktu wejścia trzeba zmodyfikować składniki przyjmujące bibliotekę DLL w celu ręcznej inicjacji, zależnie od sposobu zaimplementowania biblioteki DLL:
  • Wejście do biblioteki DLL odbywa się przez eksportowanie (__declspec(dllexport)), a odbiorcy nie mogą używać zarządzanego kodu, jeśli są dołączeni statycznie lub dynamicznie do biblioteki DLL.
  • Biblioteka DLL jest oparta na modelu COM.
  • Odbiorcy biblioteki DLL mogą używać zarządzanego kodu, a biblioteka DLL zawiera eksportowania lub zarządzane punkty wejścia.

Modyfikowanie bibliotek DLL, do których wchodzi się przez eksportowanie i których odbiorcy nie mogą używać kodu zarządzanego

Aby zmodyfikować biblioteki DLL, do których wchodzi się przez eksportowanie (__declspec(dllexport)) i których odbiorcy nie mogą używać zarządzanego kodu, wykonaj następujące kroki:
  1. Dodaj do biblioteki DLL dwa nowe eksportowania w sposób przedstawiony w następującym kodzie:
    // init.cpp
    
    #include <windows.h>
    #include <_vcclrit.h>
    
    // Wywołanie tej funkcji powinno poprzedzać inne wywołania w tej bibliotece DLL.
    // Wywołania z wielu wątków są bezpieczne; nie jest liczona liczba odwołań;
    // ponowne wejścia są bezpieczne.
    
    __declspec(dllexport) void __cdecl DllEnsureInit(void)
    {
    	// Nie należy wykonywać innych operacji w tym miejscu. Jeżeli są potrzebne dodatkowe kroki inicjacyjne,
    	// należy utworzyć statyczne obiekty z konstruktorami wykonującymi inicjację.
    	__crt_dll_initialize();
    	// Nie należy wykonywać innych operacji w tym miejscu.
    }
    
    // Tę funkcję należy wywołać wówczas, gdy cały proces całkowicie zakończy
    // wywoływanie innych obiektów w tej bibliotece DLL. Wywołania z wielu wątków są bezpieczne;
    // nie jest liczona liczba odwołań; ponowne wejścia są bezpieczne.
    // Pierwsze wywołanie zakończy działanie.
    
    __declspec(dllexport) void __cdecl DllForceTerm(void)
    {
    	// Nie należy wykonywać innych operacji w tym miejscu. Jeżeli są potrzebne dodatkowe kroki końcowe, 
    	// należy użyć atexit.
    	__crt_dll_terminate();
    	// Nie należy wykonywać innych operacji w tym miejscu.
    }
    
    Uwaga: W programie Visual C++ 2005 należy dodać opcję kompilatora dla obsługi aparatu plików wykonywalnych języka wspólnego (/clr:oldSyntax), aby pomyślnie skompilować powyższy kod przykładowy. Aby dodać opcję kompilatora dla obsługi aparatu plików wykonywalnych języka wspólnego, wykonaj następujące kroki:
    1. Kliknij menu Project (Projekt), a następnie kliknij polecenie NazwaProjektu Properties (Właściwości).

      Uwaga: NazwaProjektu jest symbolem zastępczym dla nazwy projektu.
    2. Rozwiń węzeł Configuration Properties (Właściwości konfiguracyjne), a następnie kliknij pozycję General (Ogólne).
    3. W prawym okienku kliknij, aby zaznaczyć opcję Common Language Runtime Support, Old Syntax (/clr:oldSyntax) (Obsługa aparatu plików wykonywalnych języka wspólnego) w ustawieniach projektu Common Language Runtime support (Obsługa aparatu plików wykonywalnych języka wspólnego).
    4. Kliknij przycisk Apply (Zastosuj), a następnie kliknij przycisk OK.
    Aby uzyskać więcej informacji dotyczących opcji kompilatora dla obsługi aparatu plików wykonywalnych języka wspólnego, odwiedź następującą witrynę MSDN (Microsoft Developer Network) w sieci Web:
    http://msdn2.microsoft.com/en-us/library/k8d11d4s.aspx
    Te kroki dotyczą całego artykułu.
  2. Biblioteka DLL może mieć kilku odbiorców. Jeżeli istnieje kilku odbiorców, dodaj następujący kod do pliku def biblioteki DLL w sekcji eksportowań:
    DllEnsureInit	PRIVATE
    DllForceTerm	PRIVATE
    
    Jeżeli te wiersze nie zostaną dodane, a istnieją dwie biblioteki DLL eksportujące funkcje, wystąpią błędy konsolidacji w aplikacji dołączającej bibliotekę DLL. Eksportowane funkcje zwykle mają identyczne nazwy. W wypadku wielu odbiorców każdy z nich musi być dołączany statycznie lub dynamicznie do biblioteki DLL.
  3. Jeżeli odbiorca jest dołączany statycznie do biblioteki DLL, przez użyciem biblioteki po raz pierwszy lub wykorzystaniem w aplikacji jakiegokolwiek obiektu zależnego od tej biblioteki, dodaj następujące wywołanie:
    // Snippet 1
    
    typedef void (__stdcall *pfnEnsureInit)(void);
    typedef void (__stdcall *pfnForceTerm)(void);
    
    {
    	// ... kod inicjacyjny
    	HANDLE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// Exit, return; brak dodatkowych operacji do wykonania.
    	}
    	pfnEnsureInit pfnDll=::( pfnEnsureInit) GetProcAddress(hDll, 
       "DllEnsureInit");
    	if(!pfnDll)
    	{
    		// Exit, return; brak dodatkowych operacji do wykonania.
    	}
    	
    	pfnDll();
    	
    	// ... dodatkowy kod inicjacyjny
    }
    
  4. Po ostatnim użyciu biblioteki DLL w danej aplikacji dodaj następujący kod:
    // Snippet 2
    
    {
    	// ... kod zakończenia 
    	HANDLE hDll=::GetModuleHandle("mydll.dll");
    	If(!hDll)
    	{
    		// exit, return; brak dodatkowych operacji do wykonania
    	}
    	pfnForceTerm pfnDll=::( pfnForceTerm) GetProcAddress(hDll, 
       "DllForceTerm");
    	if(!pfnDll)
    	{
    		// exit, return; brak dodatkowych operacji do wykonania
    	}
    	
    	pfnDll();
    	
    	// ... dodatkowy kod zakończenia 
    }
    
  5. Jeśli odbiorca jest dołączany dynamicznie do biblioteki DLL, wstaw kod w następujący sposób:
    • Wstaw kod snippet 1 (zobacz krok 3) zaraz za pierwszą funkcją LoadLibrary biblioteki DLL .
    • Wstaw kod snippet 2 (zobacz krok 4) tuż przed ostatnią funkcją FreeLibrary biblioteki DLL.

Modyfikowanie biblioteki DLL opartej na modelu COM

  • Zmodyfikuj funkcje eksportujące biblioteki DLL DllCanUnloadNow, DllGetClassObject, DllRegisterServer i DllUnregisterServer w sposób przedstawiony w następującym kodzie:
    //  Implementacja eksportowań biblioteki DLL.
    
    #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;
    	}
    	// W tym miejscu należy wywołać kod rejestrujący
        HRESULT hr = _Module.RegisterServer(TRUE)
     
        return hr;
    }
    
    
    STDAPI DllUnregisterServer(void)
    { 
        HRESULT hr = S_OK;
    	__crt_dll_terminate();
    
        // W tym miejscu należy wywołać kod wyrejestrowujący
        hr = _Module.UnregisterServer(TRUE);
        return hr;
    }
    

Modyfikowanie biblioteki DLL zawierającej odbiorców wykorzystujących kod zarządzany oraz eksportowania lub zarządzane punkty wejścia

Aby zmodyfikować bibliotekę DLL zawierającą odbiorców wykorzystujących kod zarządzany oraz eksportowania lub zarządzane punkty wejścia, wykonaj następujące kroki:
  1. Zaimplementuj klasę zarządzaną ze statycznymi funkcjami członkowskimi inicjującymi i kończącymi działanie. Dodaj do projektu plik .cpp implementujący klasę zarządzaną ze statycznymi członkami inicjującymi i kończącymi działanie:
    // ManagedWrapper.cpp
    
    // Ten kod sprawdza, czy procedura DllMain nie jest wywoływana automatycznie 
    // przez program ładujący, jeżeli włączona jest opcja /noentry. Sprawdza także niektóre
    // funkcje inicjowane przez bibliotekę CRT.
    
    #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. Wywołaj te funkcje przed odwołaniem się do biblioteki DLL i po zakończeniu jej używania. Wywołaj funkcje składowe inicjujące i kończące działanie w funkcji main:
    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main() {
    	int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }
    

Użytkownicy programu Visual C++ .NET 2002

Uwaga: Chociaż komunikat o błędzie konsolidatora LNK4243 nie istnieje w programie Visual C++ .NET 2002, zaleca się, aby użytkownicy programu Visual C++ .NET 2002 przestrzegali podanych wcześniej zaleceń dotyczących tworzenia mieszanych bibliotek DLL.

Program Visual C++ .NET 2003 zawiera dodatkowy nagłówek ułatwiający ręczną inicjację. Aby rozwiązania wymienione w tym artykule były skuteczne w przypadku programu Visual C++ .NET 2002, należy dodać do projektu plik nagłówka o nazwie _vcclrit.h i następującej zawartości:
/***
* _vcclrit.h
*
*       Copyright (c) Microsoft Corporation. Wszelkie prawa zastrzeżone.
*
* Przeznaczenie:
*       Ten plik definiuje funkcje i zmienne wykorzystywane przez użytkownika
*       do inicjowania CRT i biblioteki dll w scenariuszu IJW.
*
****/

#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

// Kod używany do blokowania 
__declspec( selectany ) LONG  volatile __lock_handle = 0;

// Wywołanie Init
__declspec(selectany) BOOL volatile __initialized = FALSE;

// Wywołanie Term
__declspec( selectany ) BOOL volatile __terminated = FALSE;

__inline BOOL WINAPI __crt_dll_initialize()
{
    // Należy próbować zapewnić unikatowe nazwy zmiennych, aby zmienne 
    // nie powodowały nawet konfliktów z makrami.
    static BOOL volatile (__retval) = FALSE;
    static DWORD volatile (__lockThreadId) = 0xffffffff;
    DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
    int (__int_var)=0;
    
    // Użycie blokady; niezbędne w scenariuszu z wieloma wątkami. 
    // Ponadto wątki muszą oczekiwać w tym miejscu na upewnienie się, że biblioteka dll 
    // jest zainicjowana, podczas wykonywania tej funkcji.
    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 );

        // Zawieszenie w tej pętli oznacza, że 
        // dllMainCRTStartup uległa zawieszeniu w innym wątku. 
        // Najbardziej prawdopodobną przyczyną zawieszenia jest jeden z używanych 
        // konstruktorów lub destruktorów statycznych.
	}
    // Uwaga: W rzeczywistości kod związany z blokowaniem w tym miejscu nie jest potrzebny, ponieważ 
    // operacje zapisu są zawsze wykonywane po uaktywnieniu blokady. Tylko odczyt jest wykonywany poza blokadą.
    (__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;
        // Zwolnienie blokady
       __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;
    
    // Użycie blokady; ta blokada jest niezbędna do synchronizacji Terminate. 
    // z 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 );

        // Zawieszenie w tej pętli oznacza, że 
        // dllMainCRTStartup uległa zawieszeniu w innym wątku. Najbardziej prawdopodobną 
        // przyczyną zawieszenia jest jeden z używanych konstruktorów 
        // lub destruktorów statycznych.
    }
    // Uwaga: W rzeczywistości kod związany z blokowaniem w tym miejscu nie jest potrzebny, ponieważ 
    // operacje zapisu są zawsze wykonywane po uaktywnieniu blokady. Tylko odczyt jest wykonywany poza blokadą.
    (__lockThreadId) = (__currentThreadId);
    __try {
        if ( (__initialized) == FALSE )
        {
            (__retval) = FALSE;
        }
        else if ( (__terminated) == FALSE )
        {
            (__retval) = _DllMainCRTStartup( ( HINSTANCE )( &(__ImageBase) ), DLL_PROCESS_DETACH, 0 );
            (__terminated) = TRUE;
        }
    } __finally {
        // przywrócenie __lockThreadId
        (__lockThreadId) = 0xffffffff;
        // Zwolnienie blokady
       __USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
    }
    return (__retval);
}

Materiały referencyjne

Aby uzyskać więcej informacji, kliknij następujący numer artykułu w celu wyświetlenia tego artykułu z bazy wiedzy Microsoft Knowledge Base:
309694 BUG: AppDomainUnloaded exception when you use managed extensions for Visual C++ components

Właściwości

Numer ID artykułu: 814472 - Ostatnia weryfikacja: 11 maja 2007 - Weryfikacja: 7.2
Informacje zawarte w tym artykule dotyczą:
  • Microsoft Visual C++ 2005 Express Edition
  • Microsoft Visual C++ .NET 2003 Standard Edition
  • Microsoft Visual C++ .NET 2002 Standard Edition
  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 1.0
Słowa kluczowe: 
kbdll kbprb kbijw kbcominterop kbmanaged KB814472

Przekaż opinię

 

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