Podczas tworzenia rozszerzeń zarządzanych dla projektów biblioteki DLL języka C++ są wysyłane ostrzeżenia konsolidatora

Ten artykuł zawiera informacje o rozwiązywaniu ostrzeżeń konsolidatora podczas tworzenia rozszerzeń zarządzanych dla projektów biblioteki DLL języka C++.

Oryginalna wersja produktu: Visual C++
Oryginalny numer KB: 814472

Symptomy

W czasie kompilacji lub w czasie łączenia jest wyświetlany jeden z następujących komunikatów o błędach:

Błąd narzędzi konsolidatora LNK2001
"Nierozwiązany symbol zewnętrzny "symbol"
Ostrzeżenie narzędzi konsolidatora LNK4210
'. Istnieje sekcja CRT; nieobsługiwane statyczne inicjowanie lub teminatory— podczas kompilowania Managed Extensions for C++ projektów DLL są wysyłane ostrzeżenia konsolidatora
Ostrzeżenie narzędzi konsolidatora LNK4243
'Biblioteka DLL zawierająca obiekty skompilowane za pomocą /clr nie jest połączona z /NOENTRY; obraz może nie działać poprawnie".

Te ostrzeżenia mogą wystąpić w następujących okolicznościach:

  • Podczas kompilowania obiektów łączących za pomocą przełącznika /clr .
  • Podczas tworzenia jednego z następujących projektów:
    • szablon usługi sieci Web ASP.NET
    • Szablon biblioteki klas
    • Szablon biblioteki kontrolek systemu Windows
  • Po dodaniu kodu używającego zmiennych globalnych lub klas natywnych (tj. nie __gclub __value) ze statycznymi elementami członkowskimi danych. Na przykład klasy ActiveX Template Library (ATL), Microsoft Foundation Classes (MFC) i C Run-Time (CRT).

Uwaga

Mogą wystąpić błędy LNK2001 i LNK4210 dotyczące projektów, których nie dotyczy problem opisany w tym artykule. Jednak na projekt na pewno ma wpływ problem opisany w tym artykule, jeśli rozwiązanie ostrzeżenia LNK2001 lub LNK4210 prowadzi do ostrzeżenia LNK4243 lub jeśli połączenie projektu generuje ostrzeżenie LNK4243.

Przyczyna

Następujące projekty są tworzone domyślnie jako dynamiczna biblioteka łączy (DLL) bez żadnego powiązania z bibliotekami natywnymi (takimi jak CRT, ATL lub MFC) i bez żadnych zmiennych globalnych lub klas natywnych ze statycznymi elementami członkowskimi danych:

  • szablon usługi sieci Web ASP.NET
  • Szablon biblioteki klas
  • Szablon biblioteki kontrolek systemu Windows

Jeśli dodasz kod, który używa zmiennych globalnych lub klas natywnych ze statycznymi elementami członkowskimi danych (na przykład biblioteki ATL, MFC i CRT używają zmiennych globalnych), w czasie kompilacji zostaną wyświetlone komunikaty o błędach konsolidatora. W takim przypadku należy dodać kod, aby ręcznie zainicjować zmienne statyczne. Aby uzyskać więcej informacji o tym, jak to zrobić, zobacz sekcję Rozwiązywanie tego artykułu.

Dla wygody ten artykuł odnosi się do zmiennych globalnych i statycznych elementów członkowskich danych klas natywnych jako zmiennych statycznych lub statycznych od tego momentu.

Ten problem jest spowodowany problemem z ładowaniem mieszanej biblioteki DLL. Mieszane biblioteki DLL (czyli biblioteki DLL zawierające zarówno kod zarządzany, jak i natywny) mogą napotkać scenariusze zakleszczenia w pewnych okolicznościach, gdy są ładowane do przestrzeni adresowej procesu, zwłaszcza gdy system jest pod wpływem obciążenia. Wspomniane wcześniej komunikaty o błędach konsolidatora zostały włączone w konsolidatorze, aby upewnić się, że klienci są świadomi możliwości zakleszczenia i obejść opisanych w tym dokumencie.

Rozwiązanie

Rozszerzenia zarządzane dla projektów języka C++, które są domyślnie tworzone jako biblioteki DLL, nie łączą się z natywnymi bibliotekami C/C++, takimi jak biblioteka crt (C run-time), atl lub MFC i nie używają żadnych zmiennych statycznych. Ponadto ustawienia projektu określają, że biblioteki DLL powinny być połączone z włączoną opcją /NOENTRY .

Dzieje się tak, ponieważ łączenie z punktem wejścia powoduje uruchomienie zarządzanego kodu podczas DllMain, co nie jest bezpieczne (zobacz DllMain ograniczony zestaw rzeczy, które można zrobić w jego zakresie).

Bibliotekę DLL bez punktu wejścia nie można zainicjować zmiennych statycznych, z wyjątkiem typów prostych, takich jak liczby całkowite. Zwykle nie masz zmiennych statycznych w /NOENTRY DLL.

Biblioteki ATL, MFC i CRT bazują na zmiennych statycznych, więc nie można również używać tych bibliotek z tych bibliotek DLL bez uprzedniego wprowadzania modyfikacji.

Jeśli biblioteka DLL w trybie mieszanym musi używać statycznych lub bibliotek zależnych od statycznych (takich jak ATL, MFC lub CRT), musisz zmodyfikować bibliotekę DLL tak, aby statyczne dane były inicjowane ręcznie.

Pierwszym krokiem do ręcznego inicjowania jest wyłączenie kodu automatycznej inicjowania, co jest niebezpieczne w przypadku mieszanych bibliotek DLL i może spowodować zakleszczenie. Aby wyłączyć kod inicjowania, wykonaj kroki.

Usuwanie punktu wejścia zarządzanej biblioteki DLL

  1. Połącz z /NOENTRY. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy węzeł projektu, kliknij pozycję Właściwości. W oknie dialogowym Strony właściwości kliknij pozycję Konsolidator, kliknij pozycję Wiersz polecenia, a następnie dodaj ten przełącznik do pola Opcje dodatkowe .

  2. Połącz plik msvcrt.lib. W oknie dialogowym Strony właściwości kliknij pozycję Konsolidator, kliknij pozycję Dane wejściowe, a następnie dodaj plik msvcrt.lib do właściwości Dodatkowe zależności .

  3. Usuń nochkclr.obj. Na stronie Dane wejściowe (na tej samej stronie co w poprzednim kroku) usuń nochkclr.obj z właściwości Dodatkowe zależności .

  4. Łącze w crt. Na stronie Dane wejściowe (na tej samej stronie co w poprzednim kroku) dodaj __DllMainCRTStartup@12 do właściwości Wymuś odwołania do symboli .

    Jeśli używasz wiersza polecenia, określ powyższe ustawienia projektu, wykonując następujące czynności:

    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12
    

Modyfikowanie składników korzystających z biblioteki DLL na potrzeby ręcznego inicjowania

Po usunięciu jawnego punktu wejścia należy zmodyfikować składniki, które używają biblioteki DLL do ręcznego inicjowania, w zależności od sposobu implementacji biblioteki DLL:

  • Bibliotekę DLL wprowadza się przy użyciu eksportów biblioteki DLL (__declspec(dllexport)), a użytkownicy nie mogą używać kodu zarządzanego, jeśli są one połączone statycznie lub dynamicznie z biblioteką DLL.
  • Twoja bibliotekę DLL jest biblioteką DLL opartą na modelu COM.
  • Użytkownicy biblioteki DLL mogą używać kodu zarządzanego, a twoja bibliotekę DLL zawiera eksporty bibliotek DLL lub zarządzane punkty wejścia.

Modyfikowanie wprowadzonych bibliotek DLL przy użyciu eksportów biblioteki DLL i użytkowników, którzy nie mogą używać kodu zarządzanego

Aby zmodyfikować wprowadzone biblioteki DLL przy użyciu eksportów biblioteki dll (__declspec(dllexport)) i użytkowników, którzy nie mogą używać kodu zarządzanego, wykonaj następujące kroki:

  1. Dodaj dwa nowe eksporty do biblioteki DLL, jak pokazano w poniższym kodzie:

    // 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.
    }
    

    Aby dodać opcję kompilatora obsługi środowiska uruchomieniowego języka wspólnego, wykonaj następujące kroki:

    1. Kliknij pozycję Projekt, a następnie kliknij pozycję Właściwości projectname.

      Uwaga

      ProjectName jest symbolem zastępczym nazwy projektu.

    2. Rozwiń węzeł Właściwości konfiguracji, a następnie kliknij pozycję Ogólne.

    3. W okienku po prawej stronie kliknij, aby wybrać pozycję Obsługa środowiska uruchomieniowego języka wspólnego, Stara składnia (/clr:oldSyntax) w ustawieniach projektu obsługi środowiska uruchomieniowego języka wspólnego .

    4. Kliknij przycisk Zastosuj, a następnie przycisk OK.

    Aby uzyskać więcej informacji na temat opcji kompilatora obsługi środowiska uruchomieniowego języka wspólnego, zobacz /clr (Common Language Runtime Compilation).

    Te kroki dotyczą całego artykułu.

  2. Twoja bibliotekę DLL może mieć kilku użytkowników. Jeśli ma wielu użytkowników, dodaj następujący kod do pliku def biblioteki DLL w sekcji eksportów:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Jeśli nie dodasz tych wierszy i jeśli masz dwie biblioteki DLL, które eksportują funkcje, aplikacja, która łączy się z biblioteką DLL, będzie miała błędy linków. Zazwyczaj wyeksportowane funkcje mają te same nazwy. W przypadku wielu konsumerów każdy użytkownik może być połączony statycznie lub dynamicznie z biblioteką DLL.

  3. Jeśli odbiorca jest statycznie połączony z biblioteką DLL, przed pierwszym użyciem biblioteki DLL lub przed użyciem niczego, co zależy od niej w aplikacji, dodaj następujące wywołanie:

    // 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. Po ostatnim użyciu biblioteki DLL w aplikacji dodaj następujący kod:

    // 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. Jeśli odbiorca jest dynamicznie połączony z biblioteką DLL, wstaw kod w następujący sposób:

    • Wstaw fragment kodu 1 (patrz krok 3) bezpośrednio po pierwszej funkcji LoadLibrary dla biblioteki DLL.
    • Wstaw fragment kodu 2 (patrz krok 4) bezpośrednio przed ostatnią biblioteką FreeLibrary dla biblioteki DLL.

Modyfikowanie bibliotek DLL opartych na modelu COM

Zmodyfikuj funkcje DllCanUnloadNoweksportu biblioteki DLL , DllGetClassObject, DllRegisterServeri DllUnregisterServer , jak pokazano w poniższym kodzie:

// 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;
}

Modyfikowanie biblioteki DLL zawierającej konsumentów korzystających z kodu zarządzanego i eksportu biblioteki DLL lub zarządzanych punktów wejścia

Aby zmodyfikować bibliotekę DLL zawierającą konsumentów korzystających z kodu zarządzanego i eksportu bibliotek dll lub zarządzanych punktów wejścia, wykonaj następujące kroki:

  1. Zaimplementuj klasę zarządzaną za pomocą statycznych funkcji składowych na potrzeby inicjowania i kończowania. Dodaj plik .cpp do projektu, implementując klasę zarządzaną ze statycznymi elementami członkowskimi na potrzeby inicjowania i kończania:

    // 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. Wywołaj te funkcje przed odwołaniem się do biblioteki DLL i po zakończeniu korzystania z niej. Wywołaj funkcje składowe inicjowania i zakończenia w programie main:

    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main()
    {
        int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }