Quando si compilano estensioni gestite per progetti DLL C++ vengono visualizzati avvisi del linker

Questo articolo fornisce informazioni sulla risoluzione degli avvisi del linker quando si compilano estensioni gestite per progetti DLL C++.

Versione originale del prodotto: Visual C++
Numero KB originale: 814472

Sintomi

In fase di compilazione o in fase di collegamento viene visualizzato uno dei messaggi di errore seguenti:

Errore degli strumenti del linker LNK2001
'simbolo esterno non risolto "simbolo" '
Avviso degli strumenti del linker LNK4210
'. La sezione CRT esiste; potrebbero non essere gestiti inizializzati statici o teminatori'Si ricevono avvisi del linker quando si compilano progetti DLL Estensioni gestite per C++
LNK4243 di avviso degli strumenti del linker
'LA DLL contenente oggetti compilati con /clr non è collegata a /NOENTRY; l'immagine potrebbe non essere eseguita correttamente".

Questi avvisi possono verificarsi nelle circostanze seguenti:

  • Quando si compilano oggetti di collegamento con l'opzione /clr .
  • Quando si compila uno dei progetti seguenti:
    • Modello di servizio Web ASP.NET
    • Modello di libreria di classi
    • Modello della libreria di controlli di Windows
  • Dopo aver aggiunto codice che usa variabili globali o classi native (ovvero, non __gco __value) con membri dati statici. Ad esempio, le classi ActiveX Template Library (ATL), Microsoft Foundation Classes (MFC) e C Run-Time (CRT).

Nota

È possibile che vengano visualizzati gli errori di LNK2001 e LNK4210 con i progetti che non sono interessati dal problema descritto in questo articolo. Tuttavia, il progetto è sicuramente interessato dal problema descritto in questo articolo se la risoluzione di un avviso di LNK2001 o LNK4210 causa un avviso LNK4243 o se il collegamento del progetto genera un avviso LNK4243.

Causa

I progetti seguenti vengono creati per impostazione predefinita come libreria a collegamento dinamico (DLL) senza alcun collegamento a librerie native (ad esempio CRT, ATL o MFC) e senza variabili globali o classi native con membri dati statici:

  • Modello di servizio Web ASP.NET
  • Modello di libreria di classi
  • Modello della libreria di controlli di Windows

Se si aggiunge codice che usa variabili globali o classi native con membri dati statici (ad esempio, le librerie ATL, MFC e CRT usano variabili globali), si riceveranno messaggi di errore del linker in fase di compilazione. In questo caso, è necessario aggiungere codice per inizializzare manualmente le variabili statiche. Per altre informazioni su come eseguire questa operazione, vedere la sezione Risoluzione di questo articolo.

Per praticità, questo articolo fa riferimento alle variabili globali e ai membri dati statici delle classi native come variabili statiche o statiche da questo punto in avanti.

Questo problema è causato dal problema di caricamento di DLL miste. Le DLL miste,ovvero le DLL che contengono codice sia gestito che nativo, possono riscontrare scenari di deadlock in alcune circostanze quando vengono caricate nello spazio indirizzi del processo, soprattutto quando il sistema è sotto stress. I messaggi di errore del linker menzionati in precedenza sono stati abilitati nel linker per assicurarsi che i clienti siano consapevoli del potenziale deadlock e delle soluzioni alternative descritte in questo documento.

Risoluzione

Le estensioni gestite per i progetti C++ creati come DLL per impostazione predefinita non si collegano alle librerie C/C++ native, ad esempio la libreria C run-time (CRT), ATL o MFC e non usano variabili statiche. Inoltre, le impostazioni del progetto specificano che le DLL devono essere collegate all'opzione /NOENTRY abilitata.

Questa operazione viene eseguita perché il collegamento a un punto di ingresso causa l'esecuzione del codice gestito durante DllMain, che non è sicuro (vedere DllMain per il set limitato di operazioni che è possibile eseguire durante l'ambito).

Una DLL senza un punto di ingresso non ha modo di inizializzare variabili statiche, ad eccezione dei tipi semplici, ad esempio i numeri interi. In genere non sono presenti variabili statiche in una DLL /NOENTRY .

Le librerie ATL, MFC e CRT si basano tutte su variabili statiche, pertanto non è possibile usare queste librerie dall'interno di queste DLL senza prima apportare modifiche.

Se la DLL in modalità mista deve usare librerie o statici che dipendono da elementi statici, ad esempio ATL, MFC o CRT, è necessario modificare la DLL in modo che gli elementi statici vengano inizializzati manualmente.

Il primo passaggio per l'inizializzazione manuale consiste nel assicurarsi di disabilitare il codice di inizializzazione automatica, che non è sicuro con DLL miste e può causare deadlock. Per disabilitare il codice di inizializzazione, seguire questa procedura.

Rimuovere il punto di ingresso della DLL gestita

  1. Collegamento con /NOENTRY. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo del progetto e scegliere Proprietà. Nella finestra di dialogo Pagine delle proprietà fare clic su Linker, fare clic su Riga di comando e quindi aggiungere questa opzione al campo Opzioni aggiuntive .

  2. Collegare msvcrt.lib. Nella finestra di dialogo Pagine delle proprietà fare clic su Linker, fare clic su Input e quindi aggiungere msvcrt.lib alla proprietà Dipendenze aggiuntive .

  3. Rimuovere nochkclr.obj. Nella pagina Input (stessa pagina del passaggio precedente) rimuovere nochkclr.obj dalla proprietà Dipendenze aggiuntive .

  4. Collegamento in CRT. Nella pagina Input (stessa pagina del passaggio precedente) aggiungere __DllMainCRTStartup@12 alla proprietà Force Symbol References.On the Input page (Same page as in the previous step), add __DllMainCRTStartup@12 to the Force Symbol References property.

    Se si usa il prompt dei comandi, specificare le impostazioni del progetto precedenti con quanto segue:

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

Modificare i componenti che utilizzano la DLL per l'inizializzazione manuale

Dopo aver rimosso il punto di ingresso esplicito, è necessario modificare i componenti che utilizzano la DLL per l'inizializzazione manuale, a seconda della modalità di implementazione della DLL:

  • La DLL viene immessa usando le esportazioni DLL (__declspec(dllexport) ) e i consumer non possono usare codice gestito se sono collegati in modo statico o dinamico alla DLL.
  • La DLL è una DLL basata su COM.
  • I consumer della DLL possono usare codice gestito e la DLL contiene esportazioni DLL o punti di ingresso gestiti.

Modificare le DLL immesse usando le esportazioni dll e i consumer che non possono usare codice gestito

Per modificare le DLL immesse usando le esportazioni dll (__declspec(dllexport)) e i consumer che non possono usare codice gestito, seguire questa procedura:

  1. Aggiungere due nuove esportazioni alla DLL, come illustrato nel codice seguente:

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

    Per aggiungere l'opzione del compilatore di supporto di Common Language Runtime, seguire questa procedura:

    1. Fare clic su Progetto e quindi su Proprietà NomeProgetto.

      Nota

      ProjectName è un segnaposto per il nome del progetto.

    2. Espandere Proprietà di configurazione e quindi fare clic su Generale.

    3. Nel riquadro destro fare clic per selezionare Supporto di Common Language Runtime, Sintassi precedente (/clr:oldSyntax) nelle impostazioni del progetto di supporto di Common Language Runtime .

    4. Fare clic su Applica e quindi su OK.

    Per altre informazioni sulle opzioni del compilatore di supporto di Common Language Runtime, vedere /clr (Compilazione di Common Language Runtime).

    Questi passaggi si applicano all'intero articolo.

  2. La DLL può avere diversi consumer. Se dispone di più consumer, aggiungere il codice seguente al file DLL def nella sezione exports:

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Se non si aggiungono queste righe e si dispone di due DLL che esportano le funzioni, l'applicazione collegata alla DLL avrà errori di collegamento. In genere, le funzioni esportate hanno gli stessi nomi. In un caso multiconsumer, ogni consumer può essere collegato in modo statico o dinamico alla DLL.

  3. Se il consumer è collegato in modo statico alla DLL, prima di usare la DLL la prima volta o prima di usare qualsiasi elemento che dipende da esso nell'applicazione, aggiungere la chiamata seguente:

    // 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. Dopo l'ultimo utilizzo della DLL nell'applicazione, aggiungere il codice seguente:

    // 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. Se il consumer è collegato dinamicamente alla DLL, inserire il codice come indicato di seguito:

    • Inserire il frammento 1 (vedere il passaggio 3) subito dopo la prima LoadLibrary per la DLL.
    • Inserire il frammento 2 (vedere il passaggio 4) immediatamente prima dell'ultima FreeLibrary per la DLL.

Modificare le DLL basate su COM

Modificare le funzioni DllCanUnloadNowdi esportazione DLL , DllGetClassObject, DllRegisterServere DllUnregisterServer come illustrato nel codice seguente:

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

Modificare la DLL che contiene consumer che usano le esportazioni di codice gestito e DLL o i punti di ingresso gestiti

Per modificare la DLL che contiene consumer che usano le esportazioni di codice gestito e DLL o i punti di ingresso gestiti, seguire questa procedura:

  1. Implementare una classe gestita con funzioni membro statiche per l'inizializzazione e la terminazione. Aggiungere un file .cpp al progetto, implementando una classe gestita con membri statici per l'inizializzazione e la terminazione:

    // 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. Chiamare queste funzioni prima di fare riferimento alla DLL e al termine dell'uso. Chiamare le funzioni membro di inizializzazione e terminazione 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();
    }