Sie erhalten Linkerwarnungen, wenn Sie verwaltete Erweiterungen für C++-DLL-Projekte erstellen.

Dieser Artikel enthält Informationen zum Auflösen von Linkerwarnungen beim Erstellen verwalteter Erweiterungen für C++-DLL-Projekte.

Originalversion des Produkts:   Visual C++
Ursprüngliche KB-Nummer:   814472

Problembeschreibung

Sie erhalten eine der folgenden Fehlermeldungen zur Kompilierungszeit oder zur Linkzeit:

Linker Tools Error LNK2001
'Nicht aufgelöstes externes Symbol "Symbol" '
Linker Tools Warnung LNK4210
'. Der CRT-Abschnitt ist vorhanden; Möglicherweise gibt es unbehandelte statische Initialisierungen oder Teminatoren'Sie erhalten Linker-Warnungen, wenn Sie Managed Extensions for C++ DLL-Projekte erstellen
Linker Tools Warnung LNK4243
"DLL mit Objekten, die mit /clr kompiliert wurden, ist nicht mit /NOENTRY verknüpft; image may not run correctly'.

Diese Warnungen können unter folgenden Umständen auftreten:

  • Beim Kompilieren von Verknüpfungsobjekten mit dem Schalter "/clr ".
  • Wenn Sie eines der folgenden Projekte erstellen:
    • ASP.NET Webdienstvorlage
    • Klassenbibliotheksvorlage
    • Windows Steuerelementbibliotheksvorlage
  • Wenn Sie Code hinzugefügt haben, der globale Variablen oder systemeigene Klassen (d. h. nicht __gcoder __value) mit statischen Datenmembern verwendet. Beispielsweise die ActiveX Template Library (ATL), Microsoft Foundation Classes (MFC) und die C Run-Time (CRT)-Klassen.

Hinweis

Möglicherweise erhalten Sie die LNK2001- und LNK4210-Fehler bei Projekten, die von dem in diesem Artikel beschriebenen Problem nicht betroffen sind. Das Projekt ist jedoch definitiv von dem in diesem Artikel beschriebenen Problem betroffen, wenn das Auflösen einer LNK2001- oder LNK4210-Warnung zu einer LNK4243-Warnung führt oder wenn durch das Verknüpfen des Projekts eine LNK4243-Warnung generiert wird.

Ursache

Die folgenden Projekte werden standardmäßig als DLL (Dynamic Link Library) ohne Verknüpfung mit systemeigenen Bibliotheken (z. B. CRT, ATL oder MFC) und ohne globale Variablen oder systemeigene Klassen mit statischen Datenmembern erstellt:

  • ASP.NET Webdienstvorlage
  • Klassenbibliotheksvorlage
  • Windows Steuerelementbibliotheksvorlage

Wenn Sie Code hinzufügen, der globale Variablen oder systemeigene Klassen mit statischen Datenmembern verwendet (z. B. verwenden die ATL-, MFC- und CRT-Bibliotheken globale Variablen), erhalten Sie zur Kompilierungszeit Linker-Fehlermeldungen. In diesem Fall müssen Sie Code hinzufügen, um die statischen Variablen manuell zu initialisieren. Weitere Informationen hierzu finden Sie im Abschnitt "Entschließung " in diesem Artikel.

Der Einfachheit halber bezieht sich dieser Artikel ab diesem Zeitpunkt auf globale Variablen und statische Datenmember nativer Klassen als statische oder statische Variablen .

Dieses Problem wird durch das gemischte DLL-Ladeproblem verursacht. Gemischte DLLs (d. h. DLLs, die sowohl verwalteten als auch nativen Code enthalten) können unter bestimmten Umständen auf Deadlock-Szenarien stoßen, wenn sie in den Adressraum des Prozesses geladen werden, insbesondere wenn das System unter Stress steht. Die zuvor erwähnten Linker-Fehlermeldungen wurden im Linker aktiviert, um sicherzustellen, dass Kunden das Potenzial für Deadlock und die in diesem Dokument beschriebenen Problemumgehungen kennen.

Lösung

Verwaltete Erweiterungen für C++-Projekte, die standardmäßig als DLLs erstellt werden, verknüpfen sich nicht mit systemeigenen C/C++-Bibliotheken wie der C-Laufzeitbibliothek ,ATL oder MFC und verwenden keine statischen Variablen. Darüber hinaus geben die Projekteinstellungen an, dass die DLLs mit der aktivierten Option "/NOENTRY " verknüpft werden sollen.

Dies geschieht, da die Verknüpfung mit einem Einstiegspunkt dazu führt, dass verwalteter Code ausgeführt DllMainwird, was nicht sicher ist (siehe DllMain die begrenzten Aktionen, die Sie während des Gültigkeitsbereichs ausführen können).

Eine DLL ohne Einstiegspunkt hat keine Möglichkeit, statische Variablen zu initialisieren, außer für einfache Typen wie ganze Zahlen. Normalerweise haben Sie keine statischen Variablen in einer /NOENTRY-DLL .

Die ATL-, MFC- und CRT-Bibliotheken basieren alle auf statischen Variablen, sodass Sie diese Bibliotheken auch nicht aus diesen DLLs heraus verwenden können, ohne zuvor Änderungen vorzunehmen.

Wenn Ihre DLL im gemischten Modus statische Oder Bibliotheken verwenden muss, die von statischen Daten abhängig sind (z. B. ATL, MFC oder CRT), müssen Sie die DLL so ändern, dass die Statik manuell initialisiert wird.

Der erste Schritt zur manuellen Initialisierung besteht darin, sicherzustellen, dass Sie den automatischen Initialisierungscode deaktivieren, der bei gemischten DLLs unsicher ist und deadlock verursachen kann. Führen Sie die Schritte aus, um den Initialisierungscode zu deaktivieren.

Entfernen des Einstiegspunkts der verwalteten DLL

  1. Mit /NOENTRY verknüpfen. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf den Projektknoten, und klicken Sie auf "Eigenschaften". Klicken Sie im Dialogfeld "Eigenschaftenseiten " auf "Linker", klicken Sie auf "Befehlszeile", und fügen Sie diese Option dem Feld "Zusätzliche Optionen" hinzu.

  2. Link msvcrt.lib. Klicken Sie im Dialogfeld "Eigenschaftenseiten " auf "Linker", klicken Sie auf "Eingabe", und fügen Sie dann "msvcrt.lib " zur Eigenschaft "Zusätzliche Abhängigkeiten " hinzu.

  3. Entfernen Sie nochkclr.obj. Entfernen Sie auf der Eingabeseite (dieselbe Seite wie im vorherigen Schritt) nochkclr.obj aus der Eigenschaft "Additional Dependencies ".

  4. Link im CRT. Fügen Sie __DllMainCRTStartup@12 auf der Eingabeseite (dieselbe Seite wie im vorherigen Schritt) die Eigenschaft "Force Symbol References" hinzu.

    Wenn Sie die Eingabeaufforderung verwenden, geben Sie die oben genannten Projekteinstellungen wie folgt an:

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

Ändern von Komponenten, die die DLL für die manuelle Initialisierung verwenden

Nachdem Sie den expliziten Einstiegspunkt entfernt haben, müssen Sie Komponenten ändern, die die DLL für die manuelle Initialisierung verwenden, je nachdem, wie Ihre DLL implementiert wird:

  • Ihre DLL wird mithilfe von DLL-Exporten (__declspec(dllexport)) eingegeben, und Ihre Verbraucher können verwalteten Code nicht verwenden, wenn sie statisch oder dynamisch mit Ihrer DLL verknüpft sind.
  • Ihre DLL ist eine COM-basierte DLL.
  • Verbraucher Ihrer DLL können verwalteten Code verwenden, und Ihre DLL enthält entweder DLL-Exporte oder verwaltete Einstiegspunkte.

Ändern von DLLs, die Sie eingeben, mithilfe von DLL-Exporten und Consumern, die keinen verwalteten Code verwenden können

Führen Sie die folgenden Schritte aus, um dlLs zu ändern, die Sie mithilfe von DLL-Exporten (__declspec(dllexport)) und Consumern eingeben, die keinen verwalteten Code verwenden können:

  1. Fügen Sie ihrer DLL zwei neue Exporte hinzu, wie im folgenden Code gezeigt:

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

    Führen Sie die folgenden Schritte aus, um die Common Language Runtime-Unterstützungscompileroption hinzuzufügen:

    1. Klicken Sie auf Project und dann auf ProjectName-Eigenschaften.

      Hinweis

      ProjectName ist ein Platzhalter für den Namen des Projekts.

    2. Erweitern Sie die Konfigurationseigenschaften, und klicken Sie dann auf "Allgemein".

    3. Klicken Sie im rechten Bereich auf die Option " Common Language Runtime Support, Old Syntax (/clr:oldSyntax)" in den Projekteinstellungen für die Common Language Runtime-Unterstützung .

    4. Klicken Sie im Feld Wähleinstellungen (Telefonkontext) auf Durchsuchen, um die Wähleinstellungen für den Benutzer zu suchen.

    Weitere Informationen zu Compileroptionen für allgemeine Sprachlaufzeiten finden Sie unter /clr (Common Language Runtime Compilation).

    Diese Schritte gelten für den gesamten Artikel.

  2. Ihre DLL kann mehrere Verbraucher haben. Wenn mehrere Consumer vorhanden sind, fügen Sie den folgenden Code zur DLL-DEF-Datei im Exportabschnitt hinzu:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Wenn Sie diese Zeilen nicht hinzufügen und wenn Sie zwei DLLs haben, die Funktionen exportieren, weist die Anwendung, die mit der DLL verknüpft ist, Verknüpfungsfehler auf. In der Regel haben die exportierten Funktionen die gleichen Namen. In einem Multiconsumer-Fall kann jeder Consumer statisch oder dynamisch mit Ihrer DLL verknüpft werden.

  3. Wenn der Consumer statisch mit der DLL verknüpft ist, fügen Sie den folgenden Aufruf hinzu, bevor Sie die DLL zum ersten Mal verwenden oder bevor Sie etwas verwenden, das davon in Ihrer Anwendung abhängt:

    // 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. Fügen Sie nach der letzten Verwendung der DLL in Ihrer Anwendung den folgenden Code hinzu:

    // 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. Wenn der Consumer dynamisch mit der DLL verknüpft ist, fügen Sie folgenden Code ein:

    • Fügen Sie Codeausschnitt 1 (siehe Schritt 3) unmittelbar nach der ersten LoadLibrary für die DLL ein.
    • Fügen Sie Codeausschnitt 2 (siehe Schritt 4) unmittelbar vor der letzten FreeLibrary für die DLL ein.

Com-basierte DLLs ändern

Ändern Sie die DLL-Exportfunktionen DllCanUnloadNow, DllGetClassObject, DllRegisterServerund DllUnregisterServer wie im folgenden Code gezeigt:

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

Ändern einer DLL mit Consumern, die verwalteten Code und DLL-Exporte oder verwaltete Einstiegspunkte verwenden

Führen Sie die folgenden Schritte aus, um DLL-Dateien zu ändern, die Consumer enthalten, die verwalteten Code und DLL-Exporte oder verwaltete Einstiegspunkte verwenden:

  1. Implementieren Sie eine verwaltete Klasse mit statischen Memberfunktionen zum Initialisieren und Beenden. Fügen Sie Ihrem Projekt eine CPP-Datei hinzu, und implementieren Sie eine verwaltete Klasse mit statischen Membern zur Initialisierung und Beendigung:

    // 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. Rufen Sie diese Funktionen auf, bevor Sie auf die DLL verweisen und nachdem Sie die Verwendung abgeschlossen haben. Aufrufen der Initialisierungs- und Beendigungsmemberfunktionen 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();
    }