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.

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

Symptome

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

Linkertoolfehler LNK2001
'nicht aufgelöstes externes Symbol 'Symbol'
Linkertools: Warnung LNK4210
'. CRT-Abschnitt vorhanden; Möglicherweise gibt es nicht behandelte statische Initialisierungen oder Teminatoren.Sie erhalten Linkerwarnungen, wenn Sie Managed Extensions for C++ DLL-Projekte erstellen.
Linkertoolwarnung LNK4243
'DLL mit Objekten, die mit /clr kompiliert wurden, ist nicht mit /NOENTRY verknüpft; Image wird möglicherweise nicht ordnungsgemäß ausgeführt.".

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

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

Hinweis

Möglicherweise erhalten Sie die LNK2001 und LNK4210 Fehler bei Projekten, die von dem in diesem Artikel beschriebenen Problem nicht betroffen sind. Das in diesem Artikel beschriebene Problem wirkt sich jedoch definitiv auf das Projekt aus, wenn das Auflösen 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 nativen Bibliotheken (z. B. CRT, ATL oder MFC) und ohne globale Variablen oder native Klassen mit statischen Datenmembern erstellt:

  • ASP.NET-Webdienstvorlage
  • Klassenbibliotheksvorlage
  • Vorlage für die Windows-Steuerelementbibliothek

Wenn Sie Code hinzufügen, der globale Variablen oder native Klassen mit statischen Datenmembern verwendet (z. B. verwenden die ATL-, MFC- und CRT-Bibliotheken globale Variablen), erhalten Sie zur Kompilierzeit Linker-Fehlermeldungen. In diesem Fall müssen Sie Code hinzufügen, um die statischen Variablen manuell zu initialisieren. Weitere Informationen dazu finden Sie im Abschnitt Lösung dieses Artikels.

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

Dieses Problem wird durch das Problem beim Laden gemischter DLL verursacht. Gemischte DLLs (d. h. DLLs, die sowohl verwalteten als auch nativen Code enthalten) können unter bestimmten Umständen zu Deadlockszenarien kommen, 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 Deadlockpotenzial und die in diesem Dokument beschriebenen Problemumgehungen kennen.

Lösung

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

Dies erfolgt, weil das Verknüpfen mit einem Einstiegspunkt bewirkt, dass verwalteter Code während DllMainausgeführt wird, was nicht sicher ist (informationen DllMain zu den eingeschränkten Aktionen, die Sie während des Bereichs ausführen können).

Eine DLL ohne Einstiegspunkt hat keine Möglichkeit, statische Variablen mit Ausnahme einfacher Typen wie ganze Zahlen zu initialisieren. 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 innerhalb dieser DLLs verwenden können, ohne zuvor Änderungen vorzunehmen.

Wenn Ihre DLL im gemischten Modus Statische oder Bibliotheken verwenden muss, die von Statischen abhängig sind (z. B. ATL, MFC oder CRT), müssen Sie Die DLL so ändern, dass die Statischen manuell initialisiert werden.

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

Entfernen des Einstiegspunkts der verwalteten DLL

  1. Link mit /NOENTRY. Klicken Sie 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 dem Feld Zusätzliche Optionen hinzu.

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

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

  4. Link in der CRT. Fügen Sie auf der Seite Eingabe (auf der gleichen Seite wie im vorherigen Schritt) der Eigenschaft Symbolverweise erzwingen__DllMainCRTStartup@12 hinzu.

    Wenn Sie die Eingabeaufforderung verwenden, geben Sie die obigen 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 nutzen

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

  • Ihre DLL wird mithilfe von DLL-Exporten (__declspec(dllexport)) eingegeben, und Ihre Consumer können keinen verwalteten Code 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, 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 Projekt und dann auf ProjektName-Eigenschaften.

      Hinweis

      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 Common Language Runtime-Unterstützung, alte Syntax (/clr:oldSyntax) in den Common Language Runtime-Supportprojekteinstellungen auszuwählen.

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

    Weitere Informationen zur Common Language Runtime-Unterstützung von Compileroptionen finden Sie unter /clr (Common Language Runtime-Kompilierung).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 über mehrere Consumer verfügen. Wenn mehrere Consumer vorhanden sind, fügen Sie der DEF-DLL-Datei im Abschnitt export den folgenden Code hinzu:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Wenn Sie diese Zeilen nicht hinzufügen und über zwei DLLs verfügen, 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 code wie folgt 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 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 einer DLL, die Consumer enthält, die verwalteten Code und DLL-Exporte oder verwaltete Einstiegspunkte verwenden

Führen Sie die folgenden Schritte aus, um eine DLL zu ändern, die Consumer enthält, 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 sie verwendet haben. Rufen Sie die Initialisierungs- und Beendigungsmemberfunktionen in auf main:

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