TN033 : version DLL de MFC
Cette note décrit comment utiliser les MFCxx.DLL
MFCxxD.DLL
bibliothèques de liens dynamiques partagées (où xx est le numéro de version MFC) avec les applications MFC et les DLL d’extension MFC. Pour plus d’informations sur les DLL MFC standard, consultez Utilisation de MFC dans le cadre d’une DLL.
Cette note technique couvre trois aspects des DLL. Les deux derniers sont destinés aux utilisateurs les plus avancés :
Comment créer une application MFC qui utilise la version DLL de MFC
Implémentation des bibliothèques de liens dynamiques partagés MFC
Si vous souhaitez créer une DLL à l’aide de MFC qui peut être utilisée avec des applications non MFC (appelée DLL MFC standard), reportez-vous à la note technique 11.
Vue d’ensemble de la prise en charge de MFCxx.DLL : Terminologie et fichiers
DLL MFC standard : vous utilisez une DLL MFC standard pour générer une DLL autonome à l’aide de certaines classes MFC. Les interfaces entre les limites app/DLL sont des interfaces « C » et l’application cliente n’a pas besoin d’être une application MFC.
Les DLL MFC standard sont la version des DLL prises en charge dans MFC 1.0. Ils sont décrits dans la note technique 11 et l’exemple DLLScreenCap
MFC Advanced Concepts .
Remarque
À partir de Visual C++ version 4.0, le terme USRDLL est obsolète et a été remplacé par une DLL MFC standard qui lie statiquement à MFC. Vous pouvez également créer une DLL MFC standard qui lie dynamiquement MFC à MFC.
MFC 3.0 (et versions ultérieures) prend en charge les DLL MFC standard avec toutes les nouvelles fonctionnalités, notamment les classes OLE et Database.
AFXDLL : également appelé version partagée des bibliothèques MFC. Il s’agit de la nouvelle prise en charge de la DLL ajoutée dans MFC 2.0. La bibliothèque MFC elle-même se trouve dans plusieurs DLL (décrites ci-dessous). Une application cliente ou une DLL lie dynamiquement les DLL dont elle a besoin. Les interfaces entre les limites d’application/DLL sont des interfaces de classe C++/MFC. L’application cliente DOIT être une application MFC. Cette DLL prend en charge toutes les fonctionnalités MFC 3.0 (exception : UNICODE n’est pas pris en charge pour les classes de base de données).
Remarque
À partir de Visual C++ version 4.0, ce type de DLL est appelé « DLL d’extension ».
Cette note permet MFCxx.DLL
de faire référence à l’ensemble de dll MFC, qui inclut :
Déboguer :
MFCxxD.DLL
(combiné) etMFCSxxD.LIB
(statique).Version :
MFCxx.DLL
(combinée) etMFCSxx.LIB
(statique).Débogage Unicode :
MFCxxUD.DLL
(combiné) etMFCSxxD.LIB
(statique).Version Unicode :
MFCxxU.DLL
(combinée) etMFCSxxU.LIB
(statique).
Remarque
Les MFCSxx[U][D].LIB
bibliothèques sont utilisées conjointement avec les DLL partagées MFC. Ces bibliothèques contiennent du code qui doit être lié statiquement à l’application ou à la DLL.
Une application est liée aux bibliothèques d’importation correspondantes :
Debug:
MFCxxD.LIB
Mise en production :
MFCxx.LIB
Débogage Unicode :
MFCxxUD.LIB
Version Unicode :
MFCxxU.LIB
Une DLL d’extension MFC est une DLL qui s’étend MFCxx.DLL
(ou les autres DLL partagées MFC). Ici, l’architecture des composants MFC se lance. Si vous dérivez une classe utile d’une classe MFC ou créez un autre kit de ressources de type MFC, vous pouvez le placer dans une DLL. Votre DLL utilise MFCxx.DLL
, comme le fait l’application cliente ultime. Une DLL d’extension MFC autorise les classes feuilles réutilisables, les classes de base réutilisables et les classes de vue et de document réutilisables.
Avantages et inconvénients
Pourquoi utiliser la version partagée de MFC
L’utilisation de la bibliothèque partagée peut entraîner des applications plus petites. (Une application minimale qui utilise la plupart de la bibliothèque MFC est inférieure à 10 Ko).
La version partagée de MFC prend en charge les DLL d’extension MFC et les DLL MFC standard.
Il est plus rapide de créer une application qui utilise les bibliothèques MFC partagées qu’une application MFC liée statiquement. C’est parce qu’il n’est pas nécessaire de lier MFC lui-même. Il est particulièrement vrai dans les
DEBUG
builds où l’éditeur de liens doit compacter les informations de débogage. Lorsque votre application est liée à une DLL qui contient déjà les informations de débogage, il existe moins d’informations de débogage à compacter.
Pourquoi ne pas utiliser la version partagée de MFC :
- L’expédition d’une application qui utilise la bibliothèque partagée nécessite que vous expédiiez
MFCxx.DLL
et d’autres bibliothèques avec votre programme.MFCxx.DLL
est librement redistribuable comme de nombreuses DLL, mais vous devez toujours installer la DLL dans votre programme d’installation. En outre, vous devrez expédier les autres bibliothèques redistribuables utilisées par votre programme et les DLL MFC elles-mêmes.
Comment écrire une DLL d’extension MFC
Une DLL d’extension MFC est une DLL qui contient des classes et des fonctions pour développer les fonctionnalités des classes MFC. Une DLL d’extension MFC utilise les DLL MFC partagées de la même façon qu’une application les utilise, avec quelques considérations supplémentaires :
Le processus de génération est similaire à la création d’une application qui utilise les bibliothèques MFC partagées avec quelques options supplémentaires du compilateur et de l’éditeur de liens.
Une DLL d’extension MFC n’a pas de
CWinApp
classe dérivée.Une DLL d’extension MFC doit fournir un fichier spécial
DllMain
. AppWizard fournit uneDllMain
fonction que vous pouvez modifier.Une DLL d’extension MFC fournit normalement une routine d’initialisation pour créer un
CDynLinkLibrary
fichier , si la DLL d’extension MFC exporteCRuntimeClass
des types ou des ressources vers l’application. Une classe dérivée deCDynLinkLibrary
peut être utilisée si les données par application doivent être conservées par la DLL d’extension MFC.
Ces considérations sont décrites plus en détail ci-dessous. Reportez-vous également à l’exemple DLLHUSK
MFC Advanced Concepts . Il montre comment :
Générez une application à l’aide des bibliothèques partagées. (
DLLHUSK.EXE
est une application MFC qui lie dynamiquement les bibliothèques MFC et d’autres DLL.)Générez une DLL d’extension MFC. (Il montre comment des indicateurs spéciaux tels que
_AFXEXT
l’utilisation sont utilisées lors de la génération d’une DLL d’extension MFC.)Créez deux exemples de DLL d’extension MFC. L’une montre la structure de base d’une DLL d’extension MFC avec des exportations limitées (TESTDLL1) et l’autre montre l’exportation d’une interface de classe entière (TESTDLL2).
L’application cliente et toutes les DLL d’extension MFC doivent utiliser la même version de MFCxx.DLL
. Suivez les conventions des DLL MFC et fournissez à la fois une version de débogage et de mise en production (/release
) de votre DLL d’extension MFC. Cette pratique permet aux programmes clients de générer à la fois des versions de débogage et de mise en production de leurs applications et de les lier à la version de débogage ou de mise en production appropriée de toutes les DLL.
Remarque
Étant donné que le nom C++ mangling et les problèmes d’exportation, la liste d’exportation à partir d’une DLL d’extension MFC peut être différente entre les versions de débogage et de mise en production des mêmes DLL et DLL pour différentes plateformes. La version MFCxx.DLL
comporte environ 2000 points d’entrée exportés ; le débogage MFCxxD.DLL
comporte environ 3 000 points d’entrée exportés.
Remarque rapide sur la gestion de la mémoire
La section intitulée « Gestion de la mémoire », à la fin de cette note technique, décrit l’implémentation de la MFCxx.DLL
version partagée de MFC. Les informations que vous devez savoir pour implémenter uniquement une DLL d’extension MFC sont décrites ici.
MFCxx.DLL
et toutes les DLL d’extension MFC chargées dans l’espace d’adressage d’une application cliente utilisent le même allocateur de mémoire, le chargement des ressources et d’autres états « globaux » MFC comme s’ils se trouvaient dans la même application. Il est important, car les bibliothèques DLL non-MFC et les DLL MFC régulières qui relient statiquement MFC à MFC font exactement l’inverse : chaque DLL alloue hors de son propre pool de mémoire.
Si une DLL d’extension MFC alloue de la mémoire, cette mémoire peut librement se mélanger avec n’importe quel autre objet alloué à l’application. En outre, si une application qui utilise les bibliothèques MFC partagées se bloque, le système d’exploitation conserve l’intégrité de toute autre application MFC qui partage la DLL.
De même, d’autres états MFC « globaux » tels que le fichier exécutable actuel à partir duquel charger des ressources, sont également partagés entre l’application cliente, toutes les DLL d’extension MFC et MFCxx.DLL
lui-même.
Génération d’une DLL d’extension MFC
Vous pouvez utiliser AppWizard pour créer un projet DLL d’extension MFC et générer automatiquement les paramètres appropriés du compilateur et de l’éditeur de liens. Elle génère également une DllMain
fonction que vous pouvez modifier.
Si vous convertissez un projet existant en DLL d’extension MFC, commencez par les paramètres standard générés à l’aide de la version partagée de MFC. Ensuite, effectuez les modifications suivantes :
Ajouter
/D_AFXEXT
aux indicateurs du compilateur. Dans la boîte de dialogue Propriétés du projet, sélectionnez la catégorie de préprocesseur C/C++>. Ajoutez_AFXEXT
au champ Définir des macros , en séparant chacun des éléments avec des points-virgules.Supprimez le commutateur du
/Gy
compilateur. Dans la boîte de dialogue Propriétés du projet, sélectionnez la catégorie Génération de code C/C++>. Vérifiez que la propriété Activer la liaison au niveau de la fonction n’est pas activée. Ce paramètre facilite l’exportation de classes, car l’éditeur de liens ne supprime pas les fonctions non référencées. Si le projet d’origine a généré une DLL MFC standard liée statiquement à MFC, remplacez l’option/MT
/MD
du compilateur (ou/MTd
) par (ou/MDd
).Générez une bibliothèque d’exportation avec l’option
/DLL
LINK. Cette option est définie lorsque vous créez une cible et spécifiez la bibliothèque de liens dynamiques Win32 comme type cible.
Modification de vos fichiers d’en-tête
L’objectif habituel d’une DLL d’extension MFC est d’exporter certaines fonctionnalités courantes vers une ou plusieurs applications qui peuvent utiliser cette fonctionnalité. Essentiellement, la DLL exporte les classes et les fonctions globales à utiliser par vos applications clientes.
Pour vous assurer que chaque fonction membre est marquée comme appropriée pour l’importation ou l’exportation, utilisez les déclarations __declspec(dllexport)
spéciales et __declspec(dllimport)
. Lorsque les applications clientes utilisent vos classes, vous souhaitez qu’elles soient déclarées en tant que __declspec(dllimport)
. Lorsque la DLL d’extension MFC elle-même est générée, les fonctions doivent être déclarées en tant que __declspec(dllexport)
. La DLL générée doit également exporter les fonctions, afin que les programmes clients puissent les lier au moment du chargement.
Pour exporter l’intégralité de votre classe, utilisez AFX_EXT_CLASS
la définition de classe. L’infrastructure définit cette macro comme __declspec(dllexport)
quand _AFXDLL
et _AFXEXT
est définie, mais elle la définit comme __declspec(dllimport)
si _AFXEXT
elle n’est pas définie. _AFXEXT
est défini uniquement lors de la génération de votre DLL d’extension MFC. Par exemple :
class AFX_EXT_CLASS CExampleExport : public CObject
{ /* ... class definition ... */ };
Ne pas exporter la classe entière
Parfois, vous souhaiterez peut-être exporter uniquement les membres nécessaires individuels de votre classe. Par exemple, si vous exportez une CDialog
classe dérivée -, vous devrez peut-être exporter le constructeur et l’appel DoModal
. Vous pouvez exporter ces membres à l’aide du fichier DEF de la DLL, mais vous pouvez également l’utiliser AFX_EXT_CLASS
de la même façon sur les membres individuels que vous devez exporter.
Par exemple :
class CExampleDialog : public CDialog
{
public:
AFX_EXT_CLASS CExampleDialog();
AFX_EXT_CLASS int DoModal();
// rest of class definition
// ...
};
Lorsque vous le faites, vous risquez d’rencontrer un problème supplémentaire, car vous n’exportez pas tous les membres de la classe. Le problème est de la façon dont les macros MFC fonctionnent. Plusieurs macros d’assistance de MFC déclarent ou définissent des membres de données. Votre DLL doit également exporter ces membres de données.
Par exemple, la macro DECLARE_DYNAMIC est définie comme suit lors de la génération d’une DLL d’extension MFC :
#define DECLARE_DYNAMIC(class_name) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
static AFX_DATA CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
La ligne qui commence static AFX_DATA
déclare un objet statique à l’intérieur de votre classe. Pour exporter cette classe correctement et accéder aux informations d’exécution à partir d’un EXE client, vous devez exporter cet objet statique. Étant donné que l’objet statique est déclaré avec le modificateur AFX_DATA
, vous devez uniquement définir AFX_DATA
comme __declspec(dllexport)
lorsque vous générez votre DLL. Définissez-le comme __declspec(dllimport)
lorsque vous générez votre exécutable client.
Comme indiqué ci-dessus, AFX_EXT_CLASS
il est déjà défini de cette façon. Il vous suffit de redéfinir AFX_DATA
pour qu’il soit identique à la définition de AFX_EXT_CLASS
votre classe.
Par exemple :
#undef AFX_DATA
#define AFX_DATA AFX_EXT_CLASS
class CExampleView : public CView
{
DECLARE_DYNAMIC()
// ... class definition ...
};
#undef AFX_DATA
#define AFX_DATA
MFC utilise toujours le AFX_DATA
symbole sur les éléments de données qu’il définit dans ses macros. Cette technique fonctionne donc pour tous ces scénarios. Par exemple, elle fonctionnera pour DECLARE_MESSAGE_MAP.
Remarque
Si vous exportez la classe entière plutôt que les membres sélectionnés de la classe, les membres de données statiques sont automatiquement exportés.
Vous pouvez utiliser la même technique pour exporter automatiquement l’opérateur CArchive
d’extraction pour les classes qui utilisent les macros DECLARE_SERIAL et IMPLEMENT_SERIAL. Exportez l’opérateur archive en crochetant les déclarations de classe (situées dans le fichier d’en-tête) avec le code suivant :
#undef AFX_API
#define AFX_API AFX_EXT_CLASS
/* your class declarations here */
#undef AFX_API
#define AFX_API
Limitations de _AFXEXT
Vous pouvez utiliser le _AFXEXT
symbole de préprocesseur pour vos DLL d’extension MFC tant que vous n’avez pas plusieurs couches de DLL d’extension MFC. Si vous avez des DLL d’extension MFC qui appellent ou dérivent de classes dans vos propres DLL d’extension MFC, qui dérivent ensuite des classes MFC, vous devez utiliser votre propre symbole de préprocesseur pour éviter toute ambiguïté.
Le problème est que dans Win32, vous devez déclarer explicitement toutes les données pour l’exporter __declspec(dllexport)
à partir d’une DLL et __declspec(dllimport)
pour l’importer à partir d’une DLL. Lorsque vous définissez _AFXEXT
, les en-têtes MFC vérifient qu’ils AFX_EXT_CLASS
sont définis correctement.
Lorsque vous avez plusieurs couches, un symbole tel qu’il AFX_EXT_CLASS
n’est pas suffisant : une DLL d’extension MFC peut exporter ses propres classes et importer également d’autres classes à partir d’une autre DLL d’extension MFC. Pour résoudre ce problème, utilisez un symbole de préprocesseur spécial qui indique que vous générez la DLL elle-même, au lieu d’utiliser la DLL. Par exemple, imaginez deux DLL d’extension MFC, A.DLL
et B.DLL
. Ils exportent chacune des classes dans A.H
et B.H
, respectivement. B.DLL
utilise les classes de A.DLL
. Les fichiers d’en-tête ressemblent à ceci :
/* A.H */
#ifdef A_IMPL
#define CLASS_DECL_A __declspec(dllexport)
#else
#define CLASS_DECL_A __declspec(dllimport)
#endif
class CLASS_DECL_A CExampleA : public CObject
{ /* ... class definition ... */ };
/* B.H */
#ifdef B_IMPL
#define CLASS_DECL_B __declspec(dllexport)
#else
#define CLASS_DECL_B __declspec(dllimport)
#endif
class CLASS_DECL_B CExampleB : public CExampleA
{ /* ... class definition ... */ };
Quand A.DLL
elle est générée, elle est générée avec /DA_IMPL
et quand B.DLL
elle est générée, elle est générée avec /DB_IMPL
. En utilisant des symboles distincts pour chaque DLL, CExampleB
est exporté et CExampleA
est importé lors de la génération B.DLL
. CExampleA
est exporté lors de la génération A.DLL
et de l’importation lorsqu’il est utilisé par B.DLL
ou par un autre client.
Ce type de superposition ne peut pas être effectué lors de l’utilisation des symboles intégrés AFX_EXT_CLASS
et _AFXEXT
de préprocesseur. La technique décrite ci-dessus résout ce problème de la même façon que MFC. MFC utilise cette technique lors de la création de ses DLL d’extension OLE, Database et Network MFC.
Toujours pas exporter la classe entière
Là encore, vous devrez prendre des précautions particulières lorsque vous n’exportez pas une classe entière. Vérifiez que les éléments de données nécessaires créés par les macros MFC sont exportés correctement. Vous pouvez le faire en redéfinissant AFX_DATA
la macro de votre classe spécifique. Redéfinissez-le chaque fois que vous n’exportez pas la classe entière.
Par exemple :
// A.H
#ifdef A_IMPL
#define CLASS_DECL_A _declspec(dllexport)
#else
#define CLASS_DECL_A _declspec(dllimport)
#endif
#undef AFX_DATA
#define AFX_DATA CLASS_DECL_A
class CExampleA : public CObject
{
DECLARE_DYNAMIC()
CLASS_DECL_A int SomeFunction();
// class definition
// ...
};
#undef AFX_DATA
#define AFX_DATA
DllMain
Voici le code que vous devez placer dans votre fichier source principal pour votre DLL d’extension MFC. Il doit venir après la norme inclut. Lorsque vous utilisez AppWizard pour créer des fichiers de démarrage pour une DLL d’extension MFC, il fournit un DllMain
pour vous.
#include "afxdllx.h"
static AFX_EXTENSION_MODULE extensionDLL;
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
// MFC extension DLL one-time initialization
if (!AfxInitExtensionModule(
extensionDLL, hInstance))
return 0;
// TODO: perform other initialization tasks here
}
else if (dwReason == DLL_PROCESS_DETACH)
{
// MFC extension DLL per-process termination
AfxTermExtensionModule(extensionDLL);
// TODO: perform other cleanup tasks here
}
return 1; // ok
}
L’appel pour AfxInitExtensionModule
capturer les classes runtime (CRuntimeClass
structures) du module et ses fabriques d’objets (COleObjectFactory
objets) à utiliser ultérieurement lors de la création de l’objet CDynLinkLibrary
. L’appel (facultatif) permet à AfxTermExtensionModule
MFC de propre la DLL d’extension MFC lorsque chaque processus se détache (ce qui se produit lorsque le processus se termine, ou lorsque la DLL est déchargée par un FreeLibrary
appel) à partir de la DLL d’extension MFC. Étant donné que la plupart des DLL d’extension MFC ne sont pas chargées dynamiquement (normalement, elles sont liées via leurs bibliothèques d’importation), l’appel à AfxTermExtensionModule
généralement n’est pas nécessaire.
Si votre application charge et libère dynamiquement des DLL d’extension MFC, veillez à appeler AfxTermExtensionModule
comme indiqué ci-dessus. Veillez également à utiliser AfxLoadLibrary
et AfxFreeLibrary
(au lieu de fonctions LoadLibrary
Win32 et FreeLibrary
) si votre application utilise plusieurs threads ou si elle charge dynamiquement une DLL d’extension MFC. L’utilisation AfxLoadLibrary
et AfxFreeLibrary
l’insures que le code de démarrage et d’arrêt qui s’exécute lorsque la DLL d’extension MFC est chargée et déchargée n’endommage pas l’état MFC global.
Le fichier d’en-tête AFXDLLX.H
contient des définitions spéciales pour les structures utilisées dans les DLL d’extension MFC, telles que la définition pour AFX_EXTENSION_MODULE
et CDynLinkLibrary
.
L’extension GLOBALEDLL doit être déclarée comme indiqué. Contrairement à la version 16 bits de MFC, vous pouvez allouer de la mémoire et appeler des fonctions MFC pendant ce temps, car l’initialisation MFCxx.DLL
complète est effectuée au moment de votre DllMain
appel.
Partage de ressources et de classes
Les DLL d’extension MFC simples n’ont besoin que d’exporter quelques fonctions à faible bande passante vers l’application cliente et rien de plus. Des DLL plus gourmandes en interface utilisateur peuvent souhaiter exporter des ressources et des classes C++ vers l’application cliente.
L’exportation de ressources s’effectue via une liste de ressources. Dans chaque application, il s’agit d’une liste d’objets CDynLinkLibrary
liée de manièreing. Lorsque vous recherchez une ressource, la plupart des implémentations MFC standard qui chargent les ressources examinent d’abord le module de ressource actuel (AfxGetResourceHandle
) et, s’il n’est pas trouvé, parcourez la liste des CDynLinkLibrary
objets qui tentent de charger la ressource demandée.
La création dynamique d’objets C++ avec un nom de classe C++ est similaire. Le mécanisme de désérialisation d’objet CRuntimeClass
MFC doit avoir tous les objets inscrits afin qu’il puisse reconstruire en créant dynamiquement l’objet C++ du type requis en fonction de ce qui a été stocké précédemment.
Si vous souhaitez que l’application cliente utilise des classes dans votre DLL d’extension MFC, DECLARE_SERIAL
vous devez exporter vos classes pour les rendre visibles dans l’application cliente. C’est également fait en parcourant la CDynLinkLibrary
liste.
Dans l’exemple DLLHUSK
Concepts avancés MFC, la liste ressemble à ceci :
head -> DLLHUSK.EXE - or - DLLHUSK.EXE
| |
TESTDLL2.DLL TESTDLL2.DLL
| |
TESTDLL1.DLL TESTDLL1.DLL
| |
| |
MFC90D.DLL MFC90.DLL
L’entrée MFCxx.DLL
est généralement disponible en dernier dans la liste des ressources et des classes. MFCxx.DLL
inclut toutes les ressources MFC standard, y compris les chaînes d’invite pour tous les ID de commande standard. Le fait de le placer à la fin de la liste permet à la fois aux DLL et à l’application cliente elle-même de s’appuyer sur les ressources partagées dans le MFCxx.DLL
, au lieu d’avoir leurs propres copies.
La fusion des ressources et des noms de classes de toutes les DLL dans l’espace de noms de l’application cliente présente l’inconvénient que vous devez faire attention aux ID ou noms que vous choisissez. Vous pouvez désactiver cette fonctionnalité en n’exportant pas vos ressources ou un CDynLinkLibrary
objet vers l’application cliente. L’exemple DLLHUSK
gère l’espace de nom de ressource partagé à l’aide de plusieurs fichiers d’en-tête. Pour plus d’informations sur l’utilisation de fichiers de ressources partagés, consultez la Note technique 35 .
Initialisation de la DLL
Comme mentionné ci-dessus, vous souhaiterez généralement créer un CDynLinkLibrary
objet pour exporter vos ressources et classes vers l’application cliente. Vous devez fournir un point d’entrée exporté pour initialiser la DLL. Minimalement, c’est une void
routine qui ne prend pas d’arguments et ne retourne rien, mais il peut être tout ce que vous aimez.
Chaque application cliente qui souhaite utiliser votre DLL doit appeler cette routine d’initialisation si vous utilisez cette approche. Vous pouvez également allouer cet CDynLinkLibrary
objet dans votre DllMain
juste après l’appel AfxInitExtensionModule
.
La routine d’initialisation doit créer un CDynLinkLibrary
objet dans le tas de l’application actuelle, câblée à vos informations DLL d’extension MFC. Vous pouvez le faire en définissant une fonction comme celle-ci :
extern "C" extern void WINAPI InitXxxDLL()
{
new CDynLinkLibrary(extensionDLL);
}
Le nom de routine, InitXxxDLL dans cet exemple, peut être tout ce que vous voulez. Il n’a pas besoin d’être extern "C"
, mais il facilite la maintenance de la liste d’exportation.
Remarque
Si vous utilisez votre DLL d’extension MFC à partir d’une DLL MFC standard, vous devez exporter cette fonction d’initialisation. Cette fonction doit être appelée à partir de la DLL MFC standard avant d’utiliser des classes ou ressources DLL d’extension MFC.
Exportation d’entrées
La façon simple d’exporter vos classes consiste à utiliser __declspec(dllimport)
et __declspec(dllexport)
sur chaque fonction globale et de classe que vous souhaitez exporter. Il est beaucoup plus facile, mais il est moins efficace que de nommer chaque point d’entrée dans un fichier DEF, comme décrit ci-dessous. C’est parce que vous avez moins de contrôle sur les fonctions exportées. Et vous ne pouvez pas exporter les fonctions par ordinal. TESTDLL1 et TESTDLL2 utilisez cette méthode pour exporter leurs entrées.
Une méthode plus efficace consiste à exporter chaque entrée en l’nommant dans le fichier DEF. Cette méthode est utilisée par MFCxx.DLL
. Étant donné que nous exportons de manière sélective à partir de notre DLL, nous devons décider quelles interfaces particulières nous souhaitons exporter. Il est difficile, car vous devez spécifier les noms bascules vers l’éditeur de liens sous la forme d’entrées dans le fichier DEF. N’exportez aucune classe C++, sauf si vous avez vraiment besoin d’avoir un lien symbolique pour celui-ci.
Si vous avez essayé d’exporter des classes C++ avec un fichier DEF précédemment, vous pouvez développer un outil pour générer cette liste automatiquement. Elle peut être effectuée à l’aide d’un processus de liaison en deux étapes. Liez votre DLL une fois sans exportation et autorisez l’éditeur de liens à générer un fichier MAP. Le fichier MAP contient une liste de fonctions qui doivent être exportées. Avec une réorganisation, vous pouvez l’utiliser pour générer les entrées EXPORT pour votre fichier DEF. La liste d’exportation pour MFCxx.DLL
et les DLL d’extension OLE et Database MFC, plusieurs milliers en nombre, a été générée avec un tel processus (bien qu’il ne soit pas entièrement automatique et nécessite un réglage de main toutes les fois en un certain temps).
CWinApp vs CDynLinkLibrary
Une DLL d’extension MFC n’a pas d’objet CWinApp
dérivé de lui-même. Au lieu de cela, elle doit fonctionner avec l’objet CWinApp
dérivé de l’application cliente. Cela signifie que l’application cliente possède la pompe de message principale, la boucle inactive, et ainsi de suite.
Si votre DLL d’extension MFC doit conserver des données supplémentaires pour chaque application, vous pouvez dériver une nouvelle classe et CDynLinkLibrary
la créer dans la InitXxxDLL
routine décrite ci-dessus. Lors de l’exécution, la DLL peut case activée la liste d’objets de CDynLinkLibrary
l’application actuelle pour trouver celle de cette DLL d’extension MFC particulière.
Utilisation de ressources dans votre implémentation DLL
Comme mentionné ci-dessus, la charge de ressource par défaut guide la liste des CDynLinkLibrary
objets qui recherchent le premier EXE ou DLL qui a la ressource demandée. Toutes les API MFC et tout le code interne utilise AfxFindResourceHandle
pour parcourir la liste des ressources pour rechercher n’importe quelle ressource, quel que soit l’emplacement où elle se trouve.
Si vous souhaitez charger uniquement des ressources à partir d’un emplacement spécifique, utilisez les API AfxGetResourceHandle
et AfxSetResourceHandle
enregistrez l’ancien handle et définissez le nouveau handle. Veillez à restaurer l’ancien handle de ressource avant de revenir à l’application cliente. L’exemple TESTDLL2 utilise cette approche pour charger explicitement un menu.
La marche à pied de la liste présente quelques inconvénients : il est légèrement plus lent et nécessite la gestion des plages d’ID de ressource. Il présente l’avantage qu’une application cliente qui lie plusieurs DLL d’extension MFC peut utiliser n’importe quelle ressource fournie par DLL sans avoir à spécifier le handle d’instance DLL. AfxFindResourceHandle
est une API utilisée pour parcourir la liste des ressources afin de rechercher une correspondance donnée. Il prend le nom et le type d’une ressource, et retourne le handle de ressource où il trouve d’abord la ressource, ou NULL.
Écriture d’une application qui utilise la version dll
Conditions requises pour l’application
Une application qui utilise la version partagée de MFC doit suivre quelques règles de base :
Il doit avoir un
CWinApp
objet et respecter les règles standard d’une pompe de messages.Elle doit être compilée avec un ensemble d’indicateurs de compilateur requis (voir ci-dessous).
Il doit être lié aux bibliothèques d’importation MFCxx. En définissant les indicateurs de compilateur requis, les en-têtes MFC déterminent au moment du lien la bibliothèque avec laquelle l’application doit établir un lien.
Pour exécuter l’exécutable,
MFCxx.DLL
doit se trouver sur le chemin d’accès ou dans le répertoire système Windows.
Création avec l’environnement de développement
Si vous utilisez le makefile interne avec la plupart des valeurs par défaut standard, vous pouvez facilement modifier le projet pour générer la version dll.
L’étape suivante suppose que vous disposez d’une application MFC qui fonctionne correctement avec NAFXCWD.LIB
(pour le débogage) et (pour la version) et NAFXCW.LIB
que vous souhaitez la convertir pour utiliser la version partagée de la bibliothèque MFC. Vous exécutez l’environnement Visual Studio et disposez d’un fichier projet interne.
- Dans le menu Projets , sélectionnez Propriétés. Dans la page Général sous Paramètres par défaut du projet, définissez les classes Microsoft Foundation pour utiliser MFC dans une DLL partagée (MFCxx(d).dll).
Création avec NMAKE
Si vous utilisez la fonctionnalité makefile externe du compilateur ou que vous utilisez NMAKE directement, vous devez modifier votre makefile pour prendre en charge les options requises du compilateur et de l’éditeur de liens.
Indicateurs de compilateur requis :
/D_AFXDLL /MD
/D_AFXDLL
Les en-têtes MFC standard doivent _AFXDLL
être définis.
/MD
L’application doit utiliser la version DLL de la bibliothèque d’exécution C.
Tous les autres indicateurs du compilateur suivent les valeurs par défaut MFC (par exemple, _DEBUG
pour le débogage).
Modifiez la liste des bibliothèques de l’éditeur de liens. Remplacez NAFXCWD.LIB
par MFCxxD.LIB
et NAFXCW.LIB
par MFCxx.LIB
. Remplacez LIBC.LIB
par MSVCRT.LIB
. Comme pour toute autre bibliothèque MFC, il est important qu’elle MFCxxD.LIB
soit placée avant toutes les bibliothèques C-runtime.
Vous pouvez éventuellement ajouter /D_AFXDLL
à vos options de compilateur de ressources de mise en production et de débogage (celle qui compile réellement les ressources avec /R
). Cette option réduit la taille de votre exécutable final en partageant les ressources présentes dans les DLL MFC.
Une reconstruction complète est requise une fois ces modifications effectuées.
Génération des exemples
La plupart des exemples de programmes MFC peuvent être générés à partir de Visual C++ ou à partir d’un MAKEFILE compatible NMAKE partagé à partir de la ligne de commande.
Pour convertir l’un de ces exemples à utiliser MFCxx.DLL
, vous pouvez charger le fichier MAK dans Visual C++ et définir les options project comme décrit ci-dessus. Si vous utilisez la build NMAKE, vous pouvez spécifier AFXDLL=1
sur la ligne de commande NMAKE et générer l’exemple à l’aide des bibliothèques MFC partagées.
L’exemple De concepts avancés MFC DLLHUSK est généré avec la version DLL de MFC. Cet exemple montre non seulement comment créer une application liée MFCxx.DLL
, mais également d’autres fonctionnalités de l’option d’empaquetage dll MFC, comme les DLL d’extension MFC décrites plus loin dans cette note technique.
Notes d’empaquetage
Les versions de version des DLL (MFCxx.DLL
et MFCxxU.DLL
) sont redistribuables librement. Les versions de débogage des DLL ne sont pas redistribuables librement et doivent être utilisées uniquement pendant le développement de votre application.
Les DLL de débogage sont fournies avec des informations de débogage. À l’aide du débogueur Visual C++, vous pouvez suivre l’exécution de votre application et de la DLL. Les DLL release (MFCxx.DLL
et MFCxxU.DLL
) ne contiennent pas d’informations de débogage.
Si vous personnalisez ou régénérez les DLL, vous devez les appeler autre chose que « MFCxx ». Le fichier MFCDLL.MAK
SRC MFC décrit les options de génération et contient la logique permettant de renommer la DLL. Le renommage des fichiers est nécessaire, car ces DLL sont potentiellement partagées par de nombreuses applications MFC. Une version personnalisée des DLL MFC remplace celles installées sur le système peut interrompre une autre application MFC à l’aide des DLL MFC partagées.
La reconstruction des DLL MFC n’est pas recommandée.
Implémentation du fichier MFCxx.DLL
La section suivante décrit comment la DLL MFC (MFCxx.DLL
et MFCxxD.DLL
) est implémentée. Comprendre les détails ici n’est pas important si tout ce que vous souhaitez faire est d’utiliser la DLL MFC avec votre application. Les détails ici ne sont pas essentiels pour comprendre comment écrire une DLL d’extension MFC, mais la compréhension de cette implémentation peut vous aider à écrire votre propre DLL.
Vue d’ensemble de l’implémentation
La DLL MFC est vraiment un cas particulier d’une DLL d’extension MFC, comme décrit ci-dessus. Il a un grand nombre d’exportations pour un grand nombre de classes. Il existe quelques choses supplémentaires que nous faisons dans la DLL MFC qui le rendent encore plus spécial qu’une DLL d’extension MFC standard.
Win32 fait la plupart du travail
La version 16 bits de MFC nécessite un certain nombre de techniques spéciales, notamment les données par application sur le segment de pile, les segments spéciaux créés par le code d’assembly 80x86, les contextes d’exception par processus et d’autres techniques. Win32 prend directement en charge les données par processus dans une DLL, ce qui est ce que vous souhaitez la plupart du temps. Pour la plupart MFCxx.DLL
, il suffit d’empaquetage NAFXCW.LIB
dans une DLL. Si vous examinez le code source MFC, vous trouverez quelques #ifdef _AFXDLL
cas, car il n’y a pas beaucoup de cas spéciaux qui doivent être effectués. Les cas spéciaux qui existent spécifiquement pour traiter Win32 sur Windows 3.1 (autrement appelé Win32s). Win32s ne prend pas directement en charge les données DLL par processus. La DLL MFC doit utiliser les API Win32 (Thread-Local Storage) pour obtenir les données locales du processus.
Impact sur les sources de bibliothèque, fichiers supplémentaires
L’impact de la version sur les _AFXDLL
sources et en-têtes de bibliothèque de classes MFC normaux est relativement mineur. Il existe un fichier de version spécial (AFXV_DLL.H
) et un fichier d’en-tête supplémentaire (AFXDLL_.H
) inclus par l’en-tête principal AFXWIN.H
. L’en-tête AFXDLL_.H
inclut la CDynLinkLibrary
classe et d’autres détails d’implémentation des DLL d’extension _AFXDLL
MFC et des applications. L’en-tête AFXDLLX.H
est fourni pour créer des DLL d’extension MFC (voir ci-dessus pour plus d’informations).
Les sources régulières de la bibliothèque MFC dans MFC SRC ont un code conditionnel supplémentaire sous la _AFXDLL
#ifdef. Un fichier source supplémentaire (DLLINIT.CPP
) contient le code d’initialisation de DLL supplémentaire et d’autres colles pour la version partagée de MFC.
Pour générer la version partagée de MFC, des fichiers supplémentaires sont fournis. (Consultez ci-dessous pour plus d’informations sur la création de la DLL.)
Deux fichiers DEF sont utilisés pour exporter les points d’entrée DLL MFC pour les versions de débogage (
MFCxxD.DEF
) et de mise en production (MFCxx.DEF
) de la DLL.Un fichier RC (
MFCDLL.RC
) contient toutes les ressources MFC standard et uneVERSIONINFO
ressource pour la DLL.Un fichier CLW (
MFCDLL.CLW
) est fourni pour permettre la navigation dans les classes MFC à l’aide de ClassWizard. Cette fonctionnalité n’est pas particulière à la version DLL de MFC.
Gestion de la mémoire
Une application utilisant MFCxx.DLL
un allocateur de mémoire commun fourni par MSVCRTxx.DLL
, la DLL du runtime C partagé. L’application, les DLL d’extension MFC et les DLL MFC utilisent cet allocateur de mémoire partagée. À l’aide d’une DLL partagée pour l’allocation de mémoire, les DLL MFC peuvent allouer de la mémoire libérée ultérieurement par l’application ou inversement. Étant donné que l’application et la DLL doivent utiliser le même allocateur, vous ne devez pas remplacer le global operator new
C++ ou operator delete
. Les mêmes règles s’appliquent au reste des routines d’allocation de mémoire au moment de l’exécution C (par malloc
exemple, , realloc
free
et d’autres).
Noms Ordinals et classes __declspec(dllexport) et DLL
Nous n’utilisons pas les class
__declspec(dllexport)
fonctionnalités du compilateur C++. Au lieu de cela, une liste d’exportations est incluse avec les sources de bibliothèque de classes (MFCxx.DEF
et MFCxxD.DEF
). Seuls un ensemble de points d’entrée sélectionnés (fonctions et données) sont exportés. D’autres symboles, tels que les fonctions d’implémentation privée MFC ou les classes, ne sont pas exportés. Toutes les exportations sont effectuées par ordinal sans nom de chaîne dans la table de noms résident ou non résidente.
L’utilisation class
__declspec(dllexport)
peut être une alternative viable pour la création de DLL plus petites, mais dans une DLL volumineuse comme MFC, le mécanisme d’exportation par défaut a des limites d’efficacité et de capacité.
Cela signifie que nous pouvons empaqueter une grande quantité de fonctionnalités dans la version MFCxx.DLL
qui n’est qu’environ 800 Ko sans compromettre une grande vitesse d’exécution ou de chargement. MFCxx.DLL
aurait été 100 Ko plus grande si cette technique n’avait pas été utilisée. La technique permet d’ajouter des points d’entrée supplémentaires à la fin du fichier DEF. Il permet un contrôle de version simple sans compromettre la vitesse et l’efficacité de l’exportation par ordinal. Les révisions de version majeure dans la bibliothèque de classes MFC modifient le nom de la bibliothèque. Autrement dit, MFC30.DLL
la DLL redistribuable contenant la version 3.0 de la bibliothèque de classes MFC. Une mise à niveau de cette DLL, par exemple, dans une MFC hypothétique 3.1, la DLL serait nommée MFC31.DLL
à la place. Là encore, si vous modifiez le code source MFC pour produire une version personnalisée de la DLL MFC, utilisez un nom différent (et de préférence sans « MFC » dans le nom).