Freigeben über


Beim Erstellen verwalteter Erweiterungen für C++-DLL-Projekte erhalten Sie Linkerwarnungen.

Dieser Artikel enthält Informationen zum Auflösen von Linkerwarnungen, wenn Sie verwaltete Erweiterungen für C++-DLL-Projekte erstellen.

Originalproduktversion: Visual C++
Ursprüngliche KB-Nummer: 814472

Problembeschreibung

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

Linkertoolfehler LNK2001
'Nicht aufgelöstes externes Symbol 'Symbol'
Linkertoolwarnung LNK4210
'. DER CRT-Abschnitt ist vorhanden; Es kann nicht behandelte statische Initialisierungen oder Teminatoren geben, die Linkerwarnungen erhalten, wenn Sie verwaltete Erweiterungen für C++-DLL-Projekte erstellen.
Linkertools–Warnung LNK4243
'DLL mit Objekten, die mit /clr kompiliert wurden, ist nicht mit /NOENTRY verknüpft; Das Image wird möglicherweise nicht ordnungsgemäß ausgeführt'.

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

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

Notiz

Möglicherweise erhalten Sie die LNK2001 und LNK4210 Fehler mit 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 Beheben einer LNK2001 oder LNK4210 Warnung zu einer LNK4243 Warnung führt, oder wenn das Verknüpfen des Projekts eine LNK4243 Warnung generiert.

Ursache

Die folgenden Projekte werden standardmäßig als Dynamic Link Library (DLL) 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 Datenmelementen verwendet (z. B. die ATL-, MFC- und CRT-Bibliotheken verwenden globale Variablen), erhalten Sie linker Fehlermeldungen zur Kompilierungszeit. In diesem Fall müssen Sie Code hinzufügen, um die statischen Variablen manuell zu initialisieren. Weitere Informationen dazu finden Sie im Abschnitt "Entschließung " dieses Artikels.

Aus Gründen der Einfachheit bezieht sich dieser Artikel auf globale Variablen und statische Datenelemente systemeigener Klassen als statische oder statische Variablen von diesem Punkt aus.

Dieses Problem wird durch das Problem beim Laden gemischter DLL verursacht. Gemischte DLLs (d. h. DLLs, die verwalteten und systemeigenen Code enthalten) können unter bestimmten Umständen deadlock-Szenarien auftreten, wenn sie in den Prozessadressraum 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 Problemumgehungen kennen, die in diesem Dokument beschrieben werden.

Lösung

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

Dies geschieht, da die Verknüpfung mit einem Einstiegspunkt dazu führt, dass verwalteter Code während DllMaindes Vorgangs ausgeführt wird, der nicht sicher ist (siehe DllMain die eingeschränkten 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. In der Regel verfügen Sie nicht über statische 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 verwenden können, ohne zuerst Änderungen vorzunehmen.

Wenn Ihre DLL im gemischten Modus Statische oder Bibliotheken verwenden muss, die von Statiken (z. B. ATL, MFC oder CRT) abhängen, 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 zu Deadlocks führen kann. Führen Sie die Schritte aus, um den Initialisierungscode zu deaktivieren.

Entfernen des Einstiegspunkts der verwalteten DLL

  1. Link mit /NOENTRY. 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 diesen Schalter zum Feld "Zusätzliche Optionen " hinzu.

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

  3. Entfernen Sie nochkclr.obj. Entfernen Sie auf der Eingabeseite (dieselbe Seite wie im vorherigen Schritt) nochkclr.obj aus der Eigenschaft "Zusätzliche Abhängigkeiten".

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

    Wenn Sie die Eingabeaufforderung verwenden, geben Sie die oben genannten Projekteinstellungen mit den folgenden Optionen 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 mit 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.
  • Consumer Ihrer DLL können verwalteten Code verwenden, und Ihre DLL enthält entweder DLL-Exporte oder verwaltete Einstiegspunkte.

Ändern sie DLLs, die Sie eingeben, indem Sie DLL-Exporte und Consumer verwenden, 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 Compileroption für die Common Language Runtime-Unterstützung hinzuzufügen:

    1. Klicken Sie auf "Projekt" und dann auf "ProjectName-Eigenschaften".

      Notiz

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

    2. Erweitern Sie konfigurationseigenschaften, und klicken Sie dann auf " Allgemein".

    3. Klicken Sie im rechten Bereich, um die Unterstützung von Common Language Runtime, Old Syntax (/clr:oldSyntax) in den Common Language Runtime-Supportprojekteinstellungen auszuwählen.

    4. Klicken Sie auf Übernehmen und anschließend auf OK.

    Weitere Informationen zur Unterstützung von Compileroptionen für common language runtime finden Sie unter /clr (Common Language Runtime Compilation).For more information about common language runtime support compiler options, see /clr (Common Language Runtime Compilation)

    Diese Schritte gelten für den gesamten Artikel.

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

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Wenn Sie diese Zeilen nicht hinzufügen und zwei DLLs haben, die Funktionen exportieren, weist die Anwendung, die mit der DLL verknüpft ist, Verknüpfungsfehler auf. In der Regel weisen die exportierten Funktionen dieselben Namen auf. 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, bevor Sie die DLL zum ersten Mal verwenden oder bevor Sie etwas verwenden, das davon in Ihrer Anwendung abhängt, fügen Sie den folgenden Aufruf hinzu:

    // 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 code wie folgt ein:

    • Fügen Sie codeausschnitt 1 (siehe Schritt 3) unmittelbar nach dem 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.

ÄNDERN VON COM-basierten DLLs

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

// 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 von DLL-Dateien, die Consumer enthalten, die verwalteten Code und DLL-Exporte oder verwaltete Einstiegspunkte verwenden

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

  1. Implementieren Sie eine verwaltete Klasse mit statischen Memberfunktionen für die Initialisierung und Beendigung. Fügen Sie Ihrem Projekt eine .cpp Datei hinzu, und implementieren Sie eine verwaltete Klasse mit statischen Membern für die 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 Beendigungsmitgliedsfunktionen 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();
    }