Numéro d'article: 106553 - Dernière mise à jour: jeudi 15 janvier 2004 - Version: 3.0

PROCÉDURE : pour écrire des DLL C et les appeler dans Visual Basic

A noterCet article s'applique à un système d'exploitation différent de celui que vous utilisez. Le contenu de l'article qui ne vous concerne peut-être pas est désactivé.
Ancien nº de publication de cet article : F106553

Sommaire

Agrandir tout | Réduire tout

Résumé

Cet article décrit la procédure à suivre pour utiliser des DLL avec Visual Basic. Elle couvre les points suivants :

Section A

  • 1.0 Qu'est-ce qu'une DLL ?
  • 1.1 Pourquoi utiliser une DLL ?
  • 1.2 Anatomie d'une DLL.
  • 1.3 Gestion de la mémoire des DLL.
  • 1.4 Construction d'une DLL à l'aide de Visual C++.
  • 1.5 Exemple de DLL C.

Section B

  • 2.0 Appel de DLL dans Visual Basic.
  • 2.1 Paramètres des DLL.
  • 2.2 Dépannage.
  • 2.3 Exemple de programme d'appel Visual Basic.

Plus d'informations

SECTION A

1.0 Qu'est-ce qu'une DLL ?

Les DLL (Dynamic Link Libraries, ou " bibliothèques de liens dynamiques " en français) sont des éléments essentiels de Windows. Une DLL contient des fonctions qui sont appelées par votre programme exécutable au cours de son exécution. En d'autres termes, une DLL est une bibliothèque de fonctions avec lesquelles votre programme peut se lier dynamiquement.

Un lien peut être statique ou dynamique. Les liens statiques sont immuables. Toutes les données d'adressage nécessaires à votre programme pour accéder à une fonction de la bibliothèque sont fixées lors de la création du fichier exécutable et demeurent inchangées pendant son exécution.

Les liens dynamiques sont créés en fonction des besoins. Quand votre programme a besoin d'accéder à une fonction qui ne se trouve pas dans le fichier exécutable, Windows charge la bibliothèque de liens dynamiques (la DLL) et rend toutes ses fonctions accessibles à votre application. Windows résout alors l'adresse de chaque fonction et la lie dynamiquement à votre application.

Toutes les commandes personnalisées utilisées dans Visual Basic sont des DLL. La seule différence tient au fait qu'elles nécessitent une gestion spéciale sous la forme de messages reçus de Visual Basic.

1.1 Pourquoi utiliser des DLL ?

Voici quatre raisons pour lesquelles vous pouvez vouloir utiliser une DLL :

  • Accès aux fonctions d'exécution C :

    La bibliothèque d'exécution C contient un grand nombre de fonctions utiles qui ne seraient pas accessibles aux programmeurs Visual Basic en l'absence de DLL. Par exemple, la fonction _dos_getdiskfree vous permet de calculer le volume total d'espace disque et la quantité d'espace libre sur un lecteur.
  • Accès aux fonctions de l'API (Application Programming Interface) Windows qui nécessitent des sous-programmes de rappel :

    Certaines fonctions de l'API Windows requièrent une fonction de rappel. Une fonction de rappel est une fonction qui est appelée par Windows lors de l'exécution de l'appel de l'API. C'est le cas, par exemple, de la fonction EnumTaskWindows, qui fournit le descripteur de toutes les fenêtres qui appartiennent à une tâche particulière.
  • Vitesse :

    Le langage C est un langage entièrement compilé qui fonctionne à un niveau relativement proche du code machine natif. Cela signifie que les programmes qui sont correctement écrits en langage C s'exécuteront rapidement.
  • Chargement en fonction des besoins :

    Le code et les données d'une DLL ne sont chargés qu'en cas de besoin. Il est possible d'organiser une DLL de façon à ce que seuls les éléments requis soient chargés, et non pas la DLL tout entière. Cela permet de réduire l'encombrement de la mémoire et le temps nécessaire au chargement.

1.2 Anatomie d'une DLL

Chaque DLL doit contenir une fonction LibMain. Elle devrait également contenir une procédure de sortie WEP (Windows Exit Procedure) en plus des fonctions exportées pouvant être appelées par un programme exécutable.

  • LibMain :

    Toute DLL doit contenir la fonction LibMain. Cette fonction est appelée par le système pour initialiser la DLL. LibMain est appelée une seule fois (lors du chargement du premier programme qui a besoin de la DLL). Les paramètres ci-dessous sont ceux qui sont transférés à la fonction LibMain :
    - HANDLE : Descripteur associé à l'instance de la DLL.
    - WORD : Segment de données de la bibliothèque.
    - WORD : Taille de tas.
    - LPSTR : Paramètres de ligne de commande.
  • WEP:

    La fonction WEP (Windows Exit Procedure) exécute un nettoyage pour une DLL avant que la bibliothèque soit déchargée. Bien qu'une fonction WEP était requise pour chaque DLL dans les versions précédentes du système d'exploitation Windows, elle est facultative pour la version 3.1. Il est recommandé d'inclure une fonction WEP dans le fichier de définition de module (.DEF) dans Visual C, comme par exemple :
    EXPORTS
    WEP
  • Fonctions exportées :

    Il s'agit des fonctions que vous voulez appeler de votre DLL. Elles sont dénotées " _export ". " _export " est utilisées à des fins de compatibilité en ascendant. Toutes les fonctions que vous voulez appeler doivent également figurer dans le fichier .DEF de votre DLL.

1.3 Gestion de la mémoire des DLL

Utilisez le modèle mémoire de grande capacité.

Le langage C stocke toutes les variables définies comme statiques ou globales (définies en dehors d'une fonction) dans le tas mémoire du programme et toutes les autres variables sur la pile.

Dans le modèle de petite ou moyenne capacité, tous les pointeurs sont par défaut des pointeurs 16 bits. Cela signifie que l'accès aux données se fait par décalages de 16 bits par rapport au registre du segment de données (SD) ou au registre du segment de pile (SP). Malheureusement, le compilateur n'a aucun moyen de savoir si le décalage se fait par rapport au SD ou au SP. Dans la plupart des programmes, cela ne devrait pas poser de problème puisque le SD et le SP désignent le même segment. Les DLL constituent toutefois un cas à part.

Une DLL possède son propre segment de données, mais elle partage sa pile avec le programme d'appel. Cela veut dire que le SD et le SP ne désignent pas le même emplacement. La solution la plus simple pour contourner ce problème consiste à construire une DLL sur la base du modèle mémoire de grande capacité, où toutes les variables sont référencées par une valeur de 32 bits.

Pourquoi allouer de la mémoire dynamiquement ?

L'allocation dynamique de la mémoire est une technique parfaitement adaptée à Windows. La déclaration de tableaux de données de grande taille prend de la place, que ce soit dans la pile de votre programme, qui est limitée à 64 Ko, ou dans le segment de données de votre programme, ce qui gaspille de l'espace disque et encombre la mémoire Windows. Il est préférable de demander de l'espace mémoire à Windows lorsque vous en avez besoin, puis de le libérer lorsque vous avez terminé.

Allocation de mémoire

Dans Windows, vous pouvez allouer dynamiquement deux types de mémoire : la mémoire locale et la mémoire globale. La mémoire locale est limitée à 64 Ko et, dans le cas d'une DLL, elle est partagée avec le programme qui a appelé la DLL. La mémoire globale est toute la mémoire dont Windows dispose après avoir été chargé.

La mémoire locale est allouée et gérée à l'aide des fonctions LocalAlloc, LocalLock, LocalUnlock et LocalFree -- comme dans cet exemple :
   char* pszBuffer;
   ....
   pszBuffer = (char *) LocalAlloc (LPTR, 20);
   ...
   LocalFree (pszBuffer);
Il est plus rapide d'allouer de la mémoire locale que d'allouer de la mémoire globale. Mais les allocations à partir du tas local sont limitées à 64 Ko, qui doivent être partagés entre tous les programmes appelant la DLL. Il est préférable d'utiliser de la mémoire locale lorsque les besoins portent sur des blocs mémoire de taille réduite à durée de vie courte.

La mémoire globale est allouée et gérée à l'aide des fonctions GlobalAlloc, GlobalLock, GlobalUnlock et GlobalFree -- comme dans cet exemple :
   HGLOBAL hglb;
   char* pszBuffer;
   hglb = GlobalAlloc (GHND, 2048);
      // GHND alloue la mémoire en tant qu'amovible et
      // initialisée à 0
      // 2048 représente la quantité de mémoire à allouer...
   pszBuffer = GlobalLock (hglb);
   ...
   GlobalUnlock (hglb);
   GlobalFree (hglb);
La fonction GlobalAlloc alloue la mémoire par multiples de 4 Ko.

Si vous voulez partager la mémoire allouée dans la DLL avec d'autres programmes, vous devriez l'allouer en utilisant le drapeau GMEM_SHARED. Si vous souhaitez partager la mémoire par échange dynamique de données, vous devez l'allouer en utilisant le drapeau GMEM_DDESHARE.

Agissez avec prudence lorsque vous stockez de données en utilisant des variables statiques

Si vous essayez de stocker des données dans une DLL en utilisant des variables globales ou statiques, ne soyez pas surpris si ces données ont été modifiées la prochaine fois que vous charger votre DLL. Les données mémorisées de cette manière seront communes à toutes les applications qui accèdent à cette DLL. Quel que soit le nombre d'applications qui utilisent une DLL, il n'y a qu'une seule instance de la DLL. Le meilleur moyen de contourner ce problème consiste à retourner les structures de la DLL et à les réintroduire en cas de besoin.

Descripteurs de fichier

Il est impossible de partager des descripteurs de fichier entre des applications ou des DLL. Chaque application a sa propre table de descripteurs de fichier. Pour que deux applications utilisent le même fichier à l'aide d'une DLL, elles doivent ouvrir individuellement le fichier toutes les deux.

1.4 Construction d'une DLL à l'aide de Visual C++

La procédure à suivre pour construire une DLL à l'aide de Visual C++ est la suivante :

  1. Démarrez Visual C++.
  2. Créez un nouveau projet en choisissant Nouveau dans le menu Projet. Sélectionnez les options suivantes :

    • Sélectionnez le Type de projet " Bibliothèque de liens dynamiques Windows (.DLL) "
    • Désactivez la case à cocher " Utiliser les classes de base Microsoft ".
    Vous pouvez également fixer ou afficher ces options ultérieurement en sélectionnant Projet dans le menu Options.
  3. Ajoutez vos fichiers .C et .DEF existants au projet en utilisant la boîte de dialogue qui s'affiche lorsque vous sélectionnez Edition dans le menu Projet, ou saisissez directement votre code dans la fenêtre d'édition de Visual C++. (Voir l'exemple de code .C et .DEF ci-dessous.)
  4. Dans le menu Projet, choisissez l'option Construire <votre_nom>.DLL option.

1.5 Exemple de DLL C

La DLL ci-dessous contient la fonction GetDiskInfo, qui peut être appelée dans Visual Basic. Cette fonction retourne l'espace disque disponible, le nom du lecteur courant et le nom du volume.
      Exemple de code C, DISKINFO.C :

   #include <windows.h>
   #include<dos.h>
 
   int CALLBACK LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
   LPSTR lpszCmdLine)

   // La ligne qui suit est requise uniquement sous Windows version 3.1
   // Win32 ne requiert pas ou ne prend pas en charge UnlockData()
   {
      if (wHeapSize > 0)
         UnlockData (0);  //Déverrouille le segment de données de la bibliothèque.
      return 1;
   }

   void __export CALLBACK GetDiskInfo (char *cDrive, char *szVolumeName,
   unsigned long *ulFreeSpace)
   {
      unsigned drive;
      struct _diskfree_t driveinfo;
      struct _find_t c_file;

      _dos_getdrive (&drive);
      _dos_getdiskfree( drive, &driveinfo );

      if (!_dos_findfirst( "*.*", _A_VOLID, &c_file ))
         wsprintf( szVolumeName, "%s", c_file.name);
      else
         wsprintf ( szVolumeName, "NO LABEL");

      *cDrive = drive + 'A' -1;

      *ulFreeSpace = (unsigned long) driveinfo.avail_clusters * (unsigned
         long) driveinfo.sectors_per_cluster * (unsigned long)
         driveinfo.bytes_per_sector;
  }
Utilisez le fichier DISKINFO.DEF suivant dans Visual C++ :
LIBRARY diskinfo
DESCRIPTION 'GetDiskInfo peut être appelé dans Visual Basic'
EXETYPE WINDOWS 3.1
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE SINGLE
HEAPSIZE 4096
EXPORTS
GetDiskInfo @1
REMARQUE : Le nom de LIBRARY dans le fichier .DEF doit être le même que le nom du fichier .DLL, sinon Visual Basic affichera le message " Erreur au chargement de la DLL ". Par exemple, créez le fichier DISKINFO.DLL en utilisant l'instruction LIBRARY DISKINFO du fichier .DEF ci-dessus.

SECTION B

2.0 Appel de DLL dans Visual Basic

Dans Visual Basic, toutes les fonctions que vous voulez appeler, y compris les fonctions de DLL, doivent d'abord être déclarées à l'aide de l'instruction Declare. Vous pouvez déclarer vos fonctions dans la section Déclarations d'un formulaire ou d'un module. Si vous déclarez une procédure ou une fonction de DLL dans un formulaire, elle est privée pour ce formulaire. Pour la rendre publique, vous devez la déclarer dans un module. Vous trouverez ci-dessous un exemple d'instruction Declare.
   Declare Sub getdiskinfo Lib "c:\somepath\diskinfo.dll"
      (ByVal mydrive As String, ByVal myvolume As String, free As Long)
Vous devez saisir l'ensemble de l'instruction Declare sous la forme d'une seule et unique ligne. Cette instruction Declare particulière déclare la procédure définie par l'utilisateur, GETDISKINFO, qui se trouve dans le fichier créé par l'utilisateur, DISKINFO.DLL.

Une fois que vous avez déclaré la fonction, vous pouvez l'appeler et l'utiliser comme n'importe quelle autre fonction Visual Basic.

2.1 Paramètres des DLL

Les DLL étant généralement écrites en langage C, elles peuvent utiliser un large éventail de paramètres qui ne sont pas directement pris en charge par Visual Basic. En conséquence, lors du transfert de paramètres, le programmeur doit trouver le type de données approprié à communiquer.

Transfert des arguments par valeur ou par référence

Par défaut, Visual Basic transfère tous les arguments par référence (lors du transfert par référence, Visual Basic fournit une adresse 32 bits). Cependant, de nombreuses fonctions de DLL attendent le transfert d'un argument par valeur. Cela peut être réalisé en faisant précéder la déclaration d'argument par le mot clé ByVal.

Les sections ci-après expliquent les procédures à suivre pour convertir des paramètres en Visual Basic.

Paramètres numériques de 8 à 16 bits

Transférez les paramètres numériques de 8 à 16 bits (int, short, unsigned int, unsigned short, BOOL et WORD) comme Integer (nombre entier).

Paramètres numériques de 32 bits

Transférez les paramètres numériques de 32 bits (long, unsigned long et DWORD) en tant que LONG.

Descripteurs d'objet

Tous les descripteurs sont des valeurs entières uniques de 16 bits associées à une fenêtre. Comme ils sont communiqués par valeur, transférez ces paramètres en tant qu'Integer.

Chaînes

Les chaînes comprennent les types de données LPSTR et LPBYTE (pointeur de caractères et pointeur de caractères non signés). Transférez ces paramètres sous la forme (ByVal paramname As String). Pour passer directement des chaînes Visual Basic, transférez-les sous la forme (param As String).

Pour des informations supplémentaires sur le transfert de chaînes entre Visual Basic et une DLL C, consultez l'article suivant dans la Base de connaissances Microsoft :
118643  (http://support.microsoft.com/kb/118643/ ) Procédures pour transférer une chaîne ou des tableaux de caractères entre Visual Basic et une DLL C
REMARQUE : Les chaînes Visual Basic nécessitant un traitement spécial, ne les transférez pas directement si la DLL ne le requiert pas explicitement.

Pointeurs de valeurs numériques

Transférez les pointeurs de valeurs numériques en n'utilisant tout simplement pas le mot clé ByVal.

Structures

Si le type défini par l'utilisateur Visual Basic correspond à la structure escomptée par la DLL, le structure peut être transférée par référence.

REMARQUE : Il n'est pas possible de transférer les structures par valeur.

Pointeurs de tableaux

Transférez le premier élément du tableau par référence.

Pointeurs de fonctions

Visual Basic ne prenant pas en charge les fonctions de rappel, les fonctions de DLL qui incluent des pointeurs désignant des fonctions ne peuvent pas être utilisées avec Visual Basic.

Pointeurs nuls

Lorsqu'une DLL attend un pointeur nul, transférez-le sous la forme (ByVal paramname As Any). Vous pouvez utiliser 0& comme valeur de paramname lors de l'appel de la DLL.

2.2 Dépannage

Vous trouverez ci-dessous les solutions aux problèmes les plus courants.

Les ressources système ne cessent de baisser après l'appel de la DLL

Si votre DLL utilise des objets GDI, vous devez penser à les libérer une fois qu'ils ont été utilisés. Cette opération peut ne pas sembler évidente dans Visual Basic, mais si vous utilisez le kit de développement de logiciel Windows lorsque vous créez un objet GDI (CreateBrushIndirect par exemple), vous devez ensuite le supprimer à l'aide de l'instruction DeleteObject.

Erreur pour convention d'appel de DLL incorrecte

Le plus souvent, cette erreur est due à l'omission ou à l'inclusion par erreur du mot clé ByVal dans l'instruction Declare. Elle peut aussi être due à un transfert de paramètres erronés.

Erreur au chargement de la DLL

Cette erreur survient lorsque vous appelez une procédure de bibliothèque de liens dynamiques et que le fichier spécifié dans l'instruction Declare de la procédure ne peut pas être chargé. Vous pouvez utiliser la fonction LoadLibrary de l'API Microsoft Windows pour en savoir plus sur les raisons d'un échec de chargement de DLL.

Erreur de protection mémoire

Des erreurs de protection mémoire surviennent lorsque votre programme écrit sur un bloc mémoire qui ne lui appartient pas. Les deux raisons les plus courantes en sont :

  • Vous avez dépassé une limite de tableau. Le langage C ne contrôle pas la validité de l'indice de tableau sur lequel vous écrivez. C'est pourquoi vous pouvez facilement écrire sur un bloc mémoire qui ne vous appartient pas.
  • Vous utilisez un pointeur désignant un emplacement mémoire que vous avez libéré. La meilleure option consiste à rendre tous les pointeurs nuls après avoir libéré leur mémoire.
Une erreur de protection mémoire peut également se produire lorsqu'un type de variable incorrect est transféré à la fonction de DLL.

2.3 Exemple de programme d'appel Visual Basic

L'appel d'une DLL dans un programme Visual Basic comprend deux parties distinctes : d'abord vous déclarez la fonction, ensuite vous l'utilisez dans un code d'événement.

Voici un exemple d'instruction Declare. Il convient de placer l'instruction Declare dans la section Déclarations générales d'un module ou d'un formulaire.
   ' Saisissez l'instruction Declare suivante sous la forme d'une seule et unique ligne :
   Declare Sub getdiskinfo Lib "c:\somepath\diskinfo.dll"

      (ByVal mydrive As String, ByVal myvolume As String, free As Long)
Spécifiez les instructions ByVal exactement comme indiqué, autrement vous risquez une erreur de protection mémoire.

7 Une fois la fonction déclarée, vous pouvez l'utiliser dans un code d'événement. L'exemple ci-après utilise une fonction de la DLL dans le code d'événement Command1 Click :
   Sub Command1_Click ()
      Dim drive As String * 1
      Dim drive As String * 20
      Dim free As Long
      Call getdiskinfo(drive, volume, free)
      Text1.Text = drive
      Text2.Text = volume
      Text3.Text = Str$(free)
   End Sub

Les informations contenues dans cet article s'appliquent au(x) produit(s) suivant(s):
  • Microsoft Visual Basic 3.0 Édition professionnelle
  • Microsoft Visual Basic 3.0 Édition professionnelle
Mots-clés : 
kbhowto kbprogramming KB106553
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.
Retired KB ArticleExclusion de responsabilité concernant les contenus obsolètes dans la Base de connaissances
Cet article concerne des produits pour lesquels Microsoft n'offre plus de support. Il est par conséquent fourni « en l'état » et ne sera plus mis à jour.