Créer des dll en C compatibles avec VB 6 23/06/2003
Par
Romain Puyfoulhoux niveau : intermédiaire
Dans Les API en Visual Basic, l'auteur nous explique comment faire appel aux API Windows dans une application VB 6. Ces API sont des librairies contenant un ensemble de fonctions exportées et utilisables depuis un autre langage, y compris Visual Basic.
Mais celui-ci ne sait pas créer de dll non activeX. Pour cela, il faut faire appel à un autre langage, le C par exemple.
Avant-Propos 1. Convention d'appel des arguments 2. Exemple de fonction simple 3. Noms des fonctions exportées 4. Appel d'une fonction depuis Visual Basic 5. Correspondance des types 6. Passer une chaîne de caractères par valeur 7. Passer une chaîne de caractères par référence 8. Chaîne de longueur fixe 9. Les types définis par l'utilisateur (UDT) 10. Passer un tableau de numériques 11. Passer un tableau de chaînes de caractères 12. Passer un tableau de structures
Avant-Propos
Ecrire sa propre dll en C, pour l'appeler depuis VB, peut être utile dans certains cas :
- si vous avez besoin d'une fonctionnalité difficile ou impossible à réaliser en VB
- si vous devez utiliser des librairies qui n'ont pas été conçues pour être facilement utilisées en VB
- si une partie de votre programme a besoin d'une grande vitesse d'exécution
Dans cet article nous supposerons que vous savez créer une dll avec votre compilateur C, et nous n'aborderons donc que la compatibilité et l'utilisation d'une dll avec VB 6.
Les codes sources ont été testés avec Visual Basic 6 et Visual C++ 6.
1. Convention d'appel des arguments
Le passage d'arguments à une fonction correspond pour la machine à une copie de ces arguments dans une zone mémoire que l'on appelle la pile.
Selon la convention utilisée, ces arguments seront copiés de gauche à droite, c'est-à-dire dans l'ordre où ils sont écrits dans votre source ou de droite à gauche.
Visual Basic 6, comme beaucoup d'autres langages (Pascal, Fortran, etc...), passe les arguments de droite à gauche. Vous n'avez pas à vous en soucier si votre programme est basé sur un seul langage, mais ici votre dll en C doit utiliser la même convention que Visual Basic.
Pour cette raison vous devez utiliser le mot clé __stdcall.
2. Exemple de fonction simple
Le fichier en-tête Def.h:
#include "windows.h" #define export __declspec (dllexport)
export void __stdcall HelloWorld(void );
Fichier .c :
#include "Def.h"
void __stdcall HelloWorld(void ) { MessageBox(NULL,"Hello !","Message",MB_OK);
}
3. Noms des fonctions exportées
Chez certains compilateurs, dont Visual C++ 6 et MinGW, l'utilisation de __stdcall a pour effet de modifier
les noms des fonctions exportées lors de la compilation (cette modification s'appelle une décoration).
Par exemple, avec Visual C++ 6 notre fonction HelloWorld() sera exportée sous le nom " _HelloWorld@0 ".
Mais vous pouvez tout de même spécifier le nom qu'aura votre fonction en l'indiquant dans un fichier .def.
Voici un exemple de fichier .def :
LIBRARY Fonctions DESCRIPTION "Essai de dll" EXPORTS HelloWorld _HelloWorld@0
La première ligne contient le nom de la dll. La deuxième ligne, optionnelle, est une description de la dll.
Les noms des fonctions exportées commencent après le mot clé EXPORTS. Pour chaque fonction, le nom que vous voulez lui
donner est suivi du nom qui a été donné par le compilateur.
Note pour Visual C++ 6 : pour connaître les noms des fonctions donnés par le compilateur, cochez l'option
"Generate Map File" dans l'onglet "Link" de la fenêtre des propriétés du projet. Ainsi un fichier .map contenant ces
informations sera généré lors de la compilation.
4. Appel d'une fonction depuis Visual Basic
Nous déclarons notre fonction, qui pour VB est en fait ici une procédure, en suivant la syntaxe habituelle :
Private Declare Sub HelloWorld Lib "Fonctions.dll" ()
Le nom de la fonction est sensible à la casse : si vous déclarez la fonction "helloworld" alors que la dll
contient la fonction "HelloWorld", vous aurez un message d'erreur.
A l'exécution du programme, Windows recherche la dll dans le répertoire courant, dans celui de l'exécutable
ainsi que dans les répertoires Windows et Windows\System. Lorsque vous exécutez votre projet dans Visual Basic,
ce n'est pas l'exécutable qui est dans le répertoire de votre projet qui est lancé.
Donc si votre dll n'est que dans le répertoire de votre projet, vous risquez d'obtenir un message d'erreur vous signifiant
que le fichier de la dll est introuvable. Pour éviter ce problème, copiez votre dll dans le répertoire Windows ou Windows\System.
Si vous recevez le message d'erreur "Can't find DLL entry point ...", c'est parce que la fonction que vous essayez
d'appeler n'a pas été exportée, ou a été exportée sous un nom différent.
5. Correspondance des types
Pour pouvoir écrire en VB les déclarations des fonctions et des structures de la dll, vous devez connaître
les équivalences entre les types de variables du C et ceux de Visual Basic.
| VB 6 | C / C++ | | Integer | bool, short | | Long | int, long | | N/A | unsigned short, unsigned int, unsigned long | | Single | float | | Double | double |
En C un int occupe 2 octets sur un système 16 Bits (Windows 3.1 par exemple) et 4 octets sur un système 32 bits.
Il n'y a pas d'équivalent en VB aux types non signés du C. Dans vos déclarations vous pourrez faire correspondre
un "unsigned short" à un Integer en VB, et un "unsigned long" ou un "unsigned int" à un Long. Mais n'oubliez pas que les
valeurs maximales autorisées pour les variables de types non signés sont le double de celles pour les types signés.
La constante true vaut -1 en vb, alors qu'elle est égale à 1 en C++.
6. Passer une chaîne de caractères par valeur
En VB les chaînes sont stockées sous la forme d'un type appelé BSTR.
En C, le type de chaîne est LPSTR qui est un pointeur vers une chaîne terminée par le caractère nul.
Passer une chaîne BSTR par valeur revient à transmettre un pointeur sur le premier caractère de la chaîne, ce qu'attend votre fonction C.
Avant de passer la chaîne à la fonction C, vous devez lui allouer un espace mémoire suffisant, par exemple avec
la fonction String(). Dans cet exemple la chaîne passée en paramètre est modifiée par la fonction GetMessage.
La fonction C :
void __stdcall GetMessage(LPSTR chaine) { strcpy(chaine,"Hello World !"); }
L'appel en vb :
Private Declare Sub GetMessage Lib "Fonctions.dll" (ByVal chaine As String )
Sub Test()
Dim chaine As String chaine = String(255, vbNullChar) GetMessage chaine
End Sub
7. Passer une chaîne de caractères par référence
Si la fonction C attend un pointeur sur un LPSTR, passez la chaîne de caractères par référence.
void __stdcall GetMessage(LPSTR *chaine) { strcpy(*chaine,"Hello World !"); }
Private Declare Sub GetMessage Lib "Fonctions.dll" (ByRef chaine As String )
Sub Test()
Dim chaine As String chaine = String (255, vbNullChar) GetMessage chaine
End Sub
8. Chaîne de longueur fixe
En VB :
Dim chaine as String * 10
En C :
char chaine[10];
La ligne écrite en VB déclare une chaîne pouvant contenir 10 caractères. La ligne écrite en C déclare une chaîne
pouvant contenir 9 caractères, le 10 ème étant réservé pour le caractère nul marquant la fin de la chaîne. Indiquez dans le
programme VB et dans la dll la même taille pour vos chaînes de caractères fixes, comme dans l'exemple ci-dessus, mais sachez
que le nombre de caractères disponibles sera la taille indiquée - 1 (donc ici 9 caractères).
9. Les types définis par l'utilisateur (UDT)
Alignement des champs
VB 6 aligne les champs d'un UDT sur 4 octets. Pour pouvoir passer un UDT à votre dll, celle-ci doit stocker
les structures de la même façon. Les champs de type short, byte ou bool de vos structures C doivent donc occuper 4 octets.
Note pour Visual C++ 6 : Pour pouvoir passer correctement des UDT à votre dll, allez dans les paramètres du projet et dans
l'onglet C/C++, sélectionnez la catégorie "Code Generation". Donnez au champ "Struct member alignement" la valeur
"4 Bytes ". Ceci ajoute "/Zp4" dans les options du projet.
Les UDT doivent être passés par référence. Les noms des champs ne sont pas obligatoirement les mêmes,
seul le type de chaque champ doit correspondre.
Chaînes de caractères dans un UDT
Les chaînes de longueur fixe sont contenues tel quel dans la structure, aussi bien en VB qu'en C.
Celles de longueur variable sont stockées en C sous la forme d'un pointeur vers le premier caractère, donc elles peuvent
être déclarées de type String en VB. Dans l'exemple ci-dessous, les champs d'une structure sont modifiés par la dll :
struct DATA { short x; long y; char strFixe[11]; double reel; LPSTR strVariable;
}
;
void __stdcall InitData (DATA * data) { data->x = 15; data->y = 52445;
lstrcpyn(data->strFixe,"abcdefghij",11); data->reel = 2459.65;
strcpy(data->strVariable,"chaîne de longueur variable"); }
Private Type TDATA x As Integer y As Long strFixe As String * 11 reel As Double strVariable As String End Type
Private Declare Sub InitData Lib "Fonctions.dll" (donnees As TDATA)
Sub Test()
Dim donnees As TDATA
donnees.strVariable = Space(30) InitData donnees
End Sub
10. Passer un tableau de numériques
Passez simplement le premier élément du tableau par référence. Ainsi la fonction C recevra l'adresse du premier
élément. Dans cet exemple, la fonction DoubleElements() multiplie par 2 tous les éléments d'un tableau, le nombre d'éléments
étant passé dans le deuxième paramètre.
void __stdcall DoubleElements(int * tableau, long lngNbItems)
{ long i; for (i=0;i<;lngNbItems;i++) { tableau[i] = 2 * tableau[i];
}
}
Private Declare Sub DoubleElements Lib "Fonctions.dll" (tableau As Long , ByVal lngNbItems As Long )
Sub Test()
Dim elements(0 To 9) As Long , i As Long
For i = 0 To 9 elements(i) = i Next DoubleElements elements(0), 10
End Sub
11. Passer un tableau de chaînes de caractères
En VB les tableaux de chaînes de caractères et les tableaux de structures sont stockés sous la forme d'un
descripteur de tableau appelé SAFEARRAY, dont voici la déclaration en C :
typedef struct FARSTRUCT tagSAFEARRAY { unsigned short cDims; unsigned short fFeatures; unsigned long cbElements; unsigned long cLocks; void HUGEP* pvData;
SAFEARRAYBOUND rgsabound[1];
}
SAFEARRAY;
La structure SAFEARRAYBOUND étant déclarée ainsi :
typedef struct tagSAFEARRAYBOUND { unsigned long cElements; long LBound;
}
SAFEARRAYBOUND;
Une fonction C qui reçoit en paramètre un tableau Visual Basic de type String doit déclarer ce tableau
sous la forme d'un pointeur vers un pointeur sur un SAFEARRAY. Les chaînes de caractères contenues dans ce tableau
sont de type BSTR.
Vous pouvez accéder aux données du tableau en appelant la fonction SafeArrayAccessData.
Elle reçoit en paramètres un pointeur vers le descripteur de tableau et l'adresse de la destination.
Après l'exécution de la fonction, le deuxième paramètre contient un pointeur sur un pointeur vers les données,
et le nombre de verrous du descripteur de tableau est incrémenté.
Quand vous n'avez plus besoin du tableau vous devez appeler la fonction SafeArrayUnaccessData.
Dans l'exemple suivant, la fonction UpperCaseElements met en majuscules toutes les chaînes de caractères
d'un tableau.
void __stdcall UpperCaseElements(SAFEARRAY **tableau) {
BSTR *chaine; HRESULT ret; unsigned long i;
if ((ret = SafeArrayAccessData(*tableau,(void **) &chaine))==S_OK) { for (i = 0; i < (*tableau)->rgsabound->cElements; i++) { CharUpper((LPTSTR) chaine[i]);
}
SafeArrayUnaccessData(*tableau);
}
}
Private Declare Sub UpperCaseElements Lib "Fonctions.dll" (tableau() As String )
Sub Test()
Dim elements(0 To 5) As String , i As Long
For i = 0 To 5 elements(i) = "élément " & i Next
UpperCaseElements elements()
End Sub
12. Passer un tableau de structures
Nous utilisons là aussi les SAFEARRAY. Le principe est le même que pour les tableaux de chaînes de caractères.
struct DATA { long x; BSTR chaine;
}
void __stdcall InitArray(SAFEARRAY **tableau) { DATA *elt; HRESULT ret; unsigned long i;
if ((ret = SafeArrayAccessData(*tableau,(void **) &elt))==S_OK) { for (i = 0; i < (*tableau)->rgsabound->cElements; i++) { elt[i].x ++; SysFreeString(elt[i].chaine);
elt[i].chaine = SysAllocString(OLESTR("hello"));
}
SafeArrayUnaccessData(*tableau);
}
}
Private Type data x As Long chaine As String End Type
Private Declare Sub InitArray Lib "Dvp.dll" (tableau() As data)
Sub Test()
Dim elements(2 To 6) As data, i As Long
For i = 2 To 6 elements(i).x = 10
elements(i).chaine = "chaine " & i Next
InitArray elements()
End Sub
Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduit en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
|