Vous recevez des avertissements de l’éditeur de liens lorsque vous générez des extensions managées pour des projets DLL C++

Cet article fournit des informations sur la résolution des avertissements de l’éditeur de liens lorsque vous générez des extensions managées pour des projets DLL C++.

Version d’origine du produit : Visual C++
Numéro de la base de connaissances d’origine : 814472

Symptômes

Vous recevez l’un des messages d’erreur suivants au moment de la compilation ou du lien :

LNK2001 d’erreur des outils Éditeur de liens
'non résolu symbole externe « symbol » '
LNK4210 d’avertissement des outils Éditeur de liens
'. La section CRT existe ; il peut y avoir des initialisations statiques ou des téminateurs non pris en charge.Vous recevez des avertissements de l’éditeur de liens lorsque vous générez Extensions managées pour C++ projets DLL
LNK4243 d’avertissement des outils Éditeur de liens
« La DLL contenant des objets compilés avec /clr n’est pas liée avec /NOENTRY ; l’image peut ne pas s’exécuter correctement'.

Ces avertissements peuvent se produire dans les circonstances suivantes :

  • Lorsque vous compilez des objets de liaison avec le commutateur /clr .
  • Lorsque vous générez l’un des projets suivants :
    • modèle de service web ASP.NET
    • Modèle de bibliothèque de classes
    • Modèle de bibliothèque de contrôles Windows
  • Lorsque vous avez ajouté du code qui utilise des variables globales ou des classes natives (c’est-à-dire, pas __gcou __value) avec des membres de données statiques. Par exemple, la bibliothèque de modèles ActiveX (ATL), les classes MFC (Microsoft Foundation Classes) et les classes C Run-Time (CRT).

Remarque

Vous pouvez recevoir les erreurs LNK2001 et LNK4210 avec des projets qui ne sont pas affectés par le problème décrit dans cet article. Toutefois, le projet est certainement affecté par le problème décrit dans cet article si la résolution d’un avertissement de LNK2001 ou de LNK4210 entraîne un avertissement de LNK4243, ou si la liaison du projet génère un avertissement LNK4243.

Cause

Les projets suivants sont créés par défaut en tant que bibliothèque de liens dynamiques (DLL) sans aucune liaison aux bibliothèques natives (telles que CRT, ATL ou MFC) et sans variables globales ou classes natives avec des membres de données statiques :

  • modèle de service web ASP.NET
  • Modèle de bibliothèque de classes
  • Modèle de bibliothèque de contrôles Windows

Si vous ajoutez du code qui utilise des variables globales ou des classes natives avec des membres de données statiques (par exemple, les bibliothèques ATL, MFC et CRT utilisent des variables globales), vous recevrez des messages d’erreur de l’éditeur de liens au moment de la compilation. Dans ce cas, vous devez ajouter du code pour initialiser manuellement les variables statiques. Pour plus d’informations sur la procédure à suivre, consultez la section Résolution de cet article.

Pour plus de commodité, cet article fait référence aux variables globales et aux membres de données statiques des classes natives en tant que variables statiques ou statiques à partir de ce point.

Ce problème est dû au problème de chargement de DLL mixte. Les DLL mixtes (c’est-à-dire les DLL qui contiennent du code managé et natif) peuvent rencontrer des scénarios d’interblocage dans certaines circonstances lorsqu’elles sont chargées dans l’espace d’adressage du processus, en particulier lorsque le système est soumis à des contraintes. Les messages d’erreur de l’éditeur de liens mentionnés précédemment ont été activés dans l’éditeur de liens pour s’assurer que les clients sont conscients du risque d’interblocage et des solutions de contournement décrites dans ce document.

Résolution

Les extensions managées pour les projets C++ créés en tant que DLL par défaut ne sont pas liées à des bibliothèques C/C++ natives telles que la bibliothèque runtime C (CRT), ATL ou MFC et n’utilisent pas de variables statiques. En outre, les paramètres du projet spécifient que les DLL doivent être liées avec l’option /NOENTRY activée.

Cela est fait parce que la liaison avec un point d’entrée entraîne l’exécution du code managé pendant DllMain, ce qui n’est pas sûr (voir DllMain pour l’ensemble limité de choses que vous pouvez faire pendant son étendue).

Une DLL sans point d’entrée n’a aucun moyen d’initialiser des variables statiques, à l’exception des types simples tels que les entiers. Vous n’avez généralement pas de variables statiques dans une DLL /NOENTRY .

Les bibliothèques ATL, MFC et CRT s’appuient toutes sur des variables statiques. Vous ne pouvez donc pas non plus utiliser ces bibliothèques à partir de ces DLL sans apporter au préalable des modifications.

Si votre DLL en mode mixte doit utiliser des statiques ou des bibliothèques qui dépendent de statiques (telles qu’ATL, MFC ou CRT), vous devez modifier votre DLL afin que les statiques soient initialisées manuellement.

La première étape de l’initialisation manuelle consiste à vous assurer que vous désactivez le code d’initialisation automatique, ce qui est dangereux avec les DLL mixtes et peut entraîner un blocage. Pour désactiver le code d’initialisation, suivez les étapes.

Supprimer le point d’entrée de la DLL managée

  1. Lien avec /NOENTRY. Dans Explorateur de solutions, cliquez avec le bouton droit sur le nœud du projet, puis cliquez sur Propriétés. Dans la boîte de dialogue Pages de propriétés, cliquez sur Éditeur de liens, sur Ligne de commande, puis ajoutez ce commutateur au champ Options supplémentaires .

  2. Lier msvcrt.lib. Dans la boîte de dialogue Pages de propriétés, cliquez sur Éditeur de liens, sur Entrée, puis ajoutez msvcrt.lib à la propriété Dépendances supplémentaires .

  3. Supprimez nochkclr.obj. Dans la page Entrée (la même page que celle de l’étape précédente), supprimez nochkclr.obj de la propriété Dépendances supplémentaires .

  4. Lien dans le CRT. Dans la page Entrée (la même page qu’à l’étape précédente), ajoutez __DllMainCRTStartup@12 à la propriété Forcer les références de symboles .

    Si vous utilisez l’invite de commandes, spécifiez les paramètres de projet ci-dessus avec les éléments suivants :

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

Modifier les composants qui consomment la DLL pour l’initialisation manuelle

Après avoir supprimé le point d’entrée explicite, vous devez modifier les composants qui consomment la DLL pour l’initialisation manuelle, en fonction de la façon dont votre DLL est implémentée :

  • Votre DLL est entrée à l’aide d’exportations DLL (__declspec(dllexport)), et vos consommateurs ne peuvent pas utiliser de code managé s’ils sont liés de manière statique ou dynamique à votre DLL.
  • Votre DLL est une DLL basée sur COM.
  • Les consommateurs de votre DLL peuvent utiliser du code managé, et votre DLL contient des exportations de DLL ou des points d’entrée managés.

Modifier les DLL que vous entrez à l’aide d’exportations DLL et de consommateurs qui ne peuvent pas utiliser de code managé

Pour modifier les DLL que vous entrez à l’aide d’exportations dll (__declspec(dllexport)) et de consommateurs qui ne peuvent pas utiliser de code managé, procédez comme suit :

  1. Ajoutez deux nouvelles exportations à votre DLL, comme indiqué dans le code suivant :

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

    Pour ajouter l’option de compilateur de prise en charge du Common Language Runtime, procédez comme suit :

    1. Cliquez sur Projet, puis sur Propriétés du nom du projet.

      Remarque

      ProjectName est un espace réservé pour le nom du projet.

    2. Développez Propriétés de configuration, puis cliquez sur Général.

    3. Dans le volet droit, cliquez pour sélectionner Prise en charge du Common Language Runtime, Ancienne syntaxe (/clr :oldSyntax) dans les paramètres de projet de prise en charge du Common Language Runtime .

    4. Dans la zone Plan de numérotation (contexte téléphonique), cliquez sur Parcourir pour localiser le plan de numérotation de l’utilisateur.

    Pour plus d’informations sur les options de compilateur de prise en charge du Common Language Runtime, consultez /clr (Compilation du Common Language Runtime).

    Ces étapes s’appliquent à l’ensemble de l’article.

  2. Votre DLL peut avoir plusieurs consommateurs. S’il a plusieurs consommateurs, ajoutez le code suivant au fichier .def DLL dans la section exportations :

    DllEnsureInitPRIVATE
    DllForceTermPRIVATE
    

    Si vous n’ajoutez pas ces lignes et si vous avez deux DLL qui exportent des fonctions, l’application qui établit un lien vers la DLL présente des erreurs de liaison. En règle générale, les fonctions exportées portent les mêmes noms. Dans un cas multiconsommateur, chaque consommateur peut être lié statiquement ou dynamiquement à votre DLL.

  3. Si le consommateur est lié statiquement à la DLL, avant d’utiliser la DLL pour la première fois, ou avant d’utiliser quoi que ce soit qui en dépend dans votre application, ajoutez l’appel suivant :

    // 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. Après la dernière utilisation de la DLL dans votre application, ajoutez le code suivant :

    // 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. Si le consommateur est lié dynamiquement à la DLL, insérez le code comme suit :

    • Insérez l’extrait de code 1 (voir l’étape 3) immédiatement après le premier LoadLibrary pour la DLL.
    • Insérez l’extrait de code 2 (voir l’étape 4) juste avant la dernière FreeLibrary pour la DLL.

Modifier les DLL basées sur COM

Modifiez les fonctions DllCanUnloadNowd’exportation de DLL , DllGetClassObject, DllRegisterServeret DllUnregisterServer comme illustré dans le code suivant :

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

Modifier la DLL qui contient des consommateurs qui utilisent du code managé et des exportations DE DLL ou des points d’entrée managés

Pour modifier la DLL qui contient des consommateurs qui utilisent du code managé et des exportations de DLL ou des points d’entrée managés, procédez comme suit :

  1. Implémentez une classe managée avec des fonctions membres statiques pour l’initialisation et l’arrêt. Ajoutez un fichier .cpp à votre projet, en implémentant une classe managée avec des membres statiques pour l’initialisation et l’arrêt :

    // 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. Appelez ces fonctions avant de faire référence à la DLL et une fois que vous avez fini de l’utiliser. Appelez les fonctions membres d’initialisation et d’arrêt dans main:

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