PROBLÈME : Avertissements de l'Éditeur de liens lors de la création d'extensions managées pour des projets DLL C++

Traductions disponibles Traductions disponibles
Numéro d'article: 814472 - Voir les produits auxquels s'applique cet article
Informations bêta
Cet article traite d'une version bêta d'un produit Microsoft. Les informations contenues dans cet article sont fournies "en l'état" et peuvent faire l'objet de modifications sans préavis.

Aucun support produit officiel n'est proposé par Microsoft pour ce produit bêta. Pour plus d'informations sur la procédure à suivre pour obtenir de l'assistance sur une version bêta, consultez la documentation fournie avec les fichiers du produit bêta ou effectuez une recherche sur le site Web à partir duquel vous avez téléchargé la version.
Agrandir tout | Réduire tout

Sommaire

Symptômes

L'un des messages d'erreur suivants s'affiche lors de la compilation ou de la liaison :
Erreur des outils de l'Éditeur de liens LNK2001
'symbole externe non résolu "sysmbol" '

Avertissement des outils de l'Éditeur de liens LNK4210
'la section .CRT existe ; présence probable de terminateurs ou d'initialiseurs statiques non gérés'

Avertissement des outils de l'Éditeur de liens LNK4243
'Le fichier DLL contenant des objets compilés avec /clr n'est pas lié à /NOENTRY ; l'image peut fonctionner de façon incorrecte'.
Ces avertissements peuvent s'afficher dans les conditions suivantes :
  • Lorsque vous compilez des liens d'objets à l'aide du commutateur /clr.
  • Lorsque vous créez l'un des projets suivants : modèle de service Web ASP.NET, modèle de bibliothèque de classes ou 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 autres que __gc ou __value) avec des données membres statiques. Par exemple, la bibliothèque de modèles ActiveX (ATL), les classes Microsoft Foundation Classes (MFC) et les classes C Run-Time (CRT).
Remarque : les erreurs LNK2001 et LNK4210 peuvent s'afficher avec des projets qui ne sont pas affectés par le problème décrit dans cet article. Toutefois, le projet est sans aucun doute affecté par le problème décrit dans cet article si la résolution de l'avertissement LNK2001 ou LNK4210 provoque un avertissement 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èques de liens dynamiques (DLL) sans aucun lien aux bibliothèques natives (telles que les bibliothèques CRT, ATL ou MFC), et sans variable globale ni classe native ayant des données membres 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 ayant des données membres statiques (par exemple, les bibliothèques ATL, MFC et CRT utilisent des variables globales), des messages d'erreur de l'Éditeur de liens s'afficheront lors de la compilation. Lorsque ceci se produit, vous devez ajouter du code pour initialiser manuellement les variables statiques. Pour plus d'informations sur la procédure à suivre, reportez-vous à la section "Résolution" de cet article.

Pour plus de commodité, la suite de cet article se réfère aux variables globales et aux données membres statiques des classes natives comme "variables statiques".

Ce problème est dû au problème de chargement des fichiers DLL mixtes. Les fichiers DLL mixtes (c'est-à-dire contenant du code managé et du code natif) peuvent rencontrer des scénarios de blocage dans certaines conditions lors de leur chargement dans l'espace d'adressage de processus, particulièrement en cas de charge élevée du système. Les messages d'erreur de l'Éditeur de liens mentionnés ci-dessus ont été activés dans l'Éditeur de liens pour veiller à ce que les clients soient conscients des risques de blocage et des solutions de contournement décrites dans ce document. Pour obtenir une description détaillée du problème de chargement de fichiers DLL mixtes, reportez-vous au livre blanc suivant (en anglais) :
Problème de chargement de fichiers DLL mixtes (Mixed DLL Loading Problem)
http://msdn2.microsoft.com/en-us/library/aa290048(vs.71).aspx

Résolution

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

Ceci est dû au fait que la liaison avec un point d'entrée provoque l'exécution du code managé au cours de DllMain, ce qui n'est pas fiable (reportez-vous à DllMain pour obtenir une liste des actions possibles au cours de la portée de cette fonction).

Un fichier DLL sans point d'entrée n'a aucun moyen d'initialiser des variables statiques, si ce n'est pour des types de données simples tels que les nombres entiers. Aucune variable statique n'est généralement présente dans un fichier DLL /NOENTRY.

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

Si votre fichier DLL mixte utilise des variables statiques ou des bibliothèques dépendant de variables statiques (telles que les bibliothèques ATL, MFC ou CRT), vous devez modifier votre fichier DLL de sorte que les variables statiques soient initialisées manuellement.

La première étape vers l'initialisation manuelle est de veiller à désactiver le code d'initialisation automatique, qui n'est pas fiable avec les fichiers DLL mixtes et peut provoquer un blocage. Pour désactiver le code d'initialisation, procédez comme suit.

Suppression du point d'entrée du fichier DLL managé

  1. Liez avec /NOENTRY. Dans l'Explorateur de solutions, cliquez avec le bouton droit sur le noeud du projet, puis cliquez sur Propriétés. Dans la boîte de dialogue Pages de propriétés, cliquez sur Éditeur de liens, puis sur Ligne de commande, puis ajoutez ce commutateur au champ Options supplémentaires.
  2. Liez msvcrt.lib. Dans la boîte de dialogue Pages de propriétés, cliquez sur Éditeur de liens, puis sur Entrée, puis ajoutez msvcrt.lib à la propriété Dépendances supplémentaires.
  3. Supprimez nochkclr.obj. Sur la page Entrée (la même qu'à l'étape précédente), supprimez nochkclr.obj de la propriété Dépendances supplémentaires.
  4. Liez le fichier certificat .CRT. Sur la page Entrée (la même qu'à l'étape précédente), ajoutez DllMainCRTStartup@12 à la propriété Références des symboles forcées.

    Si vous utilisez une invite de commandes, spécifiez les paramètres de projet ci-dessus à l'aide de la commande suivante :
    LINK /NOENTRY msvcrt.lib /NODEFAULTLIB:nochkclr.obj /INCLUDE:__DllMainCRTStartup@12

Modification des composants qui consomment le fichier DLL pour l'initialisation manuelle

Après avoir supprimé le point d'entrée explicite, vous devez modifier les composants qui consomment le fichier DLL pour l'initialisation manuelle, selon la manière dont est implémenté votre fichier DLL :
  • Votre fichier DLL est entré à l'aide d'exportations de fichiers DLL (__declspec(dllexport)) et vos consommateurs ne peuvent pas utiliser de code managé s'ils sont liés de façon statique ou dynamique à votre fichier DLL.
  • Votre fichier DLL est un fichier DLL COM.
  • Les consommateurs de votre fichier DLL peuvent utiliser du code managé et votre fichier DLL contient soit des exportations de fichiers DLL soit des points d'entrée managés.

Modification des fichiers DLL entrés à l'aide d'exportations de fichiers DLL et des consommateurs qui ne peuvent pas utiliser de code managé

Pour modifier les fichiers DLL entrés à l'aide d'exportations de fichiers DLL (__declspec(dllexport)) et les consommateurs qui ne peuvent pas utiliser du code managé, procédez comme suit :
  1. Ajoutez deux nouvelles exportations à votre fichier 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.
    }
    
  2. Votre fichier DLL peut avoir plusieurs consommateurs. Dans ce cas, ajoutez le code suivant au fichier DLL .def dans la section des exportations :
    DllEnsureInit	PRIVATE
    DllForceTerm	PRIVATE
    
    Si vous n'ajoutez pas ces lignes et si deux de vos fichiers DLL exportent des fonctions, l'application qui se lie au fichier DLL aura des erreurs de liens. En général, les fonctions exportées ont le même nom. Dans un scénario de plusieurs consommateurs, chaque consommateur peut être lié de manière statique ou dynamique à votre fichier DLL.
  3. Si le consommateur est lié de manière statique au fichier DLL, avant d'utiliser le fichier DLL pour la première fois ou d'utiliser quoi que ce soit qui dépende de ce fichier DLL pour 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 du fichier 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é au fichier DLL de manière dynamique, insérez le code suivant :
    • Insérez l'extrait de code 1 (voir l'étape 3) immédiatement après la première entrée LoadLibrary du fichier DLL.
    • Insérez l'extrait de code 2 (voir l'étape 4) immédiatement avant la dernière entrée FreeLibrary du fichier DLL.

Pour modifier les fichiers DLL COM

  • Modifiez les fonctions d'exportation de fichiers DLL DllCanUnloadNow, DllGetClassObject, DllRegisterServer et 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;
    }
    

Modification des fichiers DLL qui contiennent des consommateurs utilisant du code managé et des exportations de fichiers DLL ou des points d'entrée managés

Pour modifier un fichier DLL contenant des consommateurs qui utilisent du code managé et des exportations de fichiers DLL ou des points d'entrée managés, procédez comme suit :
  1. Implémentez une classe managée avec des fonctions de membre statique pour l'initialisation et l'arrêt. Ajoutez un fichier .cpp à votre projet, 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 au fichier DLL et après avoir fini de l'utiliser. Appelez les fonctions membres d'initialisation et d'arrêt dans la variable main :
    // Main.cpp
    
    #using <mscorlib.dll>
    using namespace System;
    using namespace System::Reflection;
    #using "ijwdll.dll";
    
    int main() {
    	int retval = ManagedWrapper::minitialize();
        ManagedWrapper::mterminate();
    }
    

Utilisateurs de Visual C++ .NET 2002

Remarque : Quoique le message d'erreur de l'Éditeur de liens LNK4243 n'existe pas dans la version Visual C++ .NET 2002, Microsoft recommande aux utilisateurs de Visual C++ .NET 2002 de suivre les instructions présentées plus haut lors du développement de fichiers DLL mixtes.

La version Visual C++ .NET 2003 contient un en-tête supplémentaire permettant de faciliter l'initialisation manuelle. Pour appliquer les solutions présentées dans cet article à Visual C++ .NET 2002, vous devez ajouter un fichier d'en-tête à votre projet nommé _vcclrit.h comprenant le texte suivant :
/***
* _vcclrit.h
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
* Purpose:
*       This file defines the functions and variables used by user
*       to initialize CRT and the dll in IJW scenario.
*
****/

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

extern IMAGE_DOS_HEADER __ImageBase;

BOOL WINAPI _DllMainCRTStartup(
        HANDLE  hDllHandle,
        DWORD   dwReason,
        LPVOID  lpreserved
        );
#ifdef __cplusplus
}
#endif

#ifdef _cplusplus
#define __USE_GLOBAL_NAMESPACE  ::
#else
#define __USE_GLOBAL_NAMESPACE
#endif

// Used to lock 
__declspec( selectany ) LONG  volatile __lock_handle = 0;

// Init called
__declspec(selectany) BOOL volatile __initialized = FALSE;

// Term called
__declspec( selectany ) BOOL volatile __terminated = FALSE;

__inline BOOL WINAPI __crt_dll_initialize()
{
    // Try to make the variable names unique, so that the variables 
    // do not even clash with macros.
    static BOOL volatile (__retval) = FALSE;
    static DWORD volatile (__lockThreadId) = 0xffffffff;
    DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
    int (__int_var)=0;
    
    // Take Lock; this is needed for multithreaded scenario. 
    // Additionally, the threads need to wait here to make sure that the dll 
    // is initialized when they get past this function.
    while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
	{
        ++(__int_var);
        if ((__lockThreadId) == (__currentThreadId)) 
        {
            return TRUE;
        }
		__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

        // If you hang in this loop, this implies that your 
        // dllMainCRTStartup is hung on another thread. 
        // The most likely cause of this is a hang in one of your 
        // static constructors or destructors.
	}
    // Note: you do not really need any interlocked stuff here because the 
    // writes are always in the lock. Only reads are outside the lock.
    (__lockThreadId) = (__currentThreadId);
    __try {
        if ( (__terminated) == TRUE )
        {
            (__retval) = FALSE;
        }
        else if ( (__initialized) == FALSE )
        {
            (__retval) = (_DllMainCRTStartup)( ( HINSTANCE )( &__ImageBase ), DLL_PROCESS_ATTACH, 0 );
            (__initialized) = TRUE;
        }

    } __finally {
        // revert the __lockThreadId
        (__lockThreadId) = 0xffffffff;
        // Release Lock
       __USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
    }
    return (__retval);
}

__inline BOOL WINAPI __crt_dll_terminate()
{
    static BOOL volatile (__retval) = TRUE;
    static DWORD volatile (__lockThreadId) = 0xffffffff;
    DWORD volatile (__currentThreadId) = __USE_GLOBAL_NAMESPACE(GetCurrentThreadId)();
    int (__int_var)=0;
    
    // Take Lock; this lock is needed to keep Terminate 
    // in sync with Initialize.
    while ( __USE_GLOBAL_NAMESPACE(InterlockedExchange)( &(__lock_handle), 1) == 1 )
	{
        ++(__int_var);
        if ((__lockThreadId) == (__currentThreadId)) 
        {
            return TRUE;
        }
		__USE_GLOBAL_NAMESPACE(Sleep)( (__int_var)>1000?100:0 );

        // If you hang in this loop, this implies that your 
        // dllMainCRTStartup is hung on another thread. The most likely 
        // cause of this is a hang in one of your static constructors 
        // or destructors.
    }
    // Note: you do not really need any interlocked stuff here because the 
    // writes are always in the lock. Only reads are outside the lock.
    (__lockThreadId) = (__currentThreadId);
    __try {
        if ( (__initialized) == FALSE )
        {
            (__retval) = FALSE;
        }
        else if ( (__terminated) == FALSE )
        {
            (__retval) = _DllMainCRTStartup( ( HINSTANCE )( &(__ImageBase) ), DLL_PROCESS_DETACH, 0 );
            (__terminated) = TRUE;
        }
    } __finally {
        // revert the __lockThreadId
        (__lockThreadId) = 0xffffffff;
        // Release Lock
       __USE_GLOBAL_NAMESPACE(InterlockedExchange)(&(__lock_handle),0);
    }
    return (__retval);
}

Références

BOGUE : Exception AppDomainUnloaded lors de l'utilisation des extensions managées pour C++. Pour plus d'informations, cliquez sur le numéro ci-dessous pour afficher l'article correspondant dans la Base de connaissances Microsoft :
309694

Propriétés

Numéro d'article: 814472 - Dernière mise à jour: vendredi 11 mai 2007 - Version: 2.3
Les informations contenues dans cet article s'appliquent au(x) produit(s) suivant(s):
  • Microsoft Visual C++ .NET 2003 Initiation
  • Microsoft Visual C++ .NET 2002 Initiation
  • Microsoft .NET Framework 1.0
  • Microsoft .NET Framework 1.1
Mots-clés : 
kbdll kbprb kbijw kbcominterop kbmanaged KB814472
L'INFORMATION CONTENUE DANS CE DOCUMENT EST FOURNIE PAR MICROSOFT SANS GARANTIE D'AUCUNE SORTE, EXPLICITE OU IMPLICITE. L'UTILISATEUR ASSUME LE RISQUE DE L'UTILISATION DU CONTENU DE CE DOCUMENT. CE DOCUMENT NE PEUT ETRE REVENDU OU CEDE EN ECHANGE D'UN QUELCONQUE PROFIT.

Envoyer des commentaires

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com