Accéder aux
Fonctions de l’API 32 et structures C
Sous Visual dBASE 7.01
par Nicolas Martin
Remerciements : je tiens à remercier tout particulièrement Romain Strieff et Dan Howard pour leur conseils et leur aimable contribution à la relecture de cet article.
Introduction

Visual dBASE permet de réaliser pratiquement tout ce que vous pouvez imaginer, grâce à la richesse des fonctions et du langage, qui découle de l’héritage considérable apporté par les versions antérieures depuis ses origines, comme premier langage de Bases de données pour PC. L’avènement des dernières versions actuelles sous Windows, et l’introduction du langage de programmation objet comme l’arête dorsale de la gestion des données et de l’interface Homme Machine ont doté le produit des meilleures capacités pour réaliser des applications de Bases de données d’excellente qualité.

Une application développée et conçue grâce à Visual dBASE est avant tout - à ce jour - une application Windows. En d’autres termes, elle doit se plier aux règles et respecter les contraintes imposées par ce système d’exploitation. Afin de garder une certaine homogénéité et garantir une bonne intégration et exécution au sein du système, Visual dBASE doit exploiter au mieux les ressources et les bibliothèques mises à disposition par celui-ci pour bâtir une application. Ces ressources sont accessibles au travers de l'Application programming interface de Windows, appelée plus couramment API Windows.

Visual dBASE, au travers des outils visuels de toute dernière génération mis à disposition pour la construction d’une application, qu’elle soit ou non orientée gestion de données, permet de réaliser avec une extrême simplicité la plupart des opérations complexes requises lors de l’élaboration d’une telle application, tout en offrant un excellent compromis entre la gestion de la complexité de l'API et la richesse des possibilités offertes. Étant avant tout une application Windows, une application construite avec Visual dBASE utilise déjà de nombreuses fonctions offertes par l’API 32 de Windows ; mais l’avantage principal d’un tel outil est qu’il transforme de façon plus abordable un domaine réservé initialement aux seuls spécialistes, et surtout il permet de recentrer l’effort du développement non plus sur la programmation système mais sur les spécificités de l’application.

Toutefois, et pour répondre à des situations particulières, il arrive que certaines caractéristiques ou options ne soient pas directement réalisables ou offertes par l’outil ou le langage Visual dBASE. Dans ce cas, et dans la mesure où ces exigences restent avant tout compatibles de la philosophie générale du système d’exploitation Windows, il faut hélas quitter le sympathique environnement pour s’aventurer dans les arcanes de la programmation Windows, afin d’utiliser directement les fonctions de l’API Windows 32 bits.

Le canevas de cet article est le suivant :

  1. Introduction
  2. Présentation des fonctions de l'API 32
  3. Premières notions de langage C
  4. Les chaînes de caractères et les pointeurs C
  5. Le prototype et la déclaration des fonctions de l'API avec Visual dBASE
  6. La déclaration des constantes de l'API 32
  7. Exécution d'une fonction de l'API 32
  8. Les structures C et Visual dBASE
  9. Création d'un exécutable utilisant les fichiers de structure
  10. Encore plus loin avec les structures
  11. Conclusion

Présentation des fonctions de l'API 32

Les concepteurs de Visual dBASE ont introduit dans le produit une ouverture de façon à rendre utilisables les fonctions des bibliothèques 32 bits contenues dans les fichiers DLL. A ce propos, Visual dBASE 7.01 ne peut utiliser que les bibliothèques 32 bits, à l’inverse de son aîné 5.7 qui ne connaît que le monde 16 bits, compatibilité avec Windows 3.1 oblige.

Les fichiers DLL sont des fichiers binaires qui contiennent du code et/ou des ressources (bitmaps, icônes) qui peuvent être partagées de façon simultanée par plusieurs applications. Windows lui-même est bâti autour de ces DLLs, utilisables dans nos propres applications.

Ces fonctions sont dénommées dans Visual dBASE “fonctions externes”. L’utilisation de telles fonctions dans un programme est détaillée dans l’aide en ligne, chercher le mot “extern” ou taper help extern dans la fenêtre de commande.

Nous allons voir comment utiliser de ce type de fonction, et nous orienter spécifiquement sur celles offertes par l’API Windows 32, afin de bien cerner leurs exigences et leurs limites d’emploi. Le but recherché consiste avant tout à acquérir la maîtrise d'une technique qui va permettre d'exploiter et d'utiliser les fonctions de l'API 32.

Ce n'est pas là la seule difficulté à surmonter lorsque l'on s'attaque à l'API 32, car en général, le point délicat consistera à dénicher dans la pléthore de fonctions API disponibles, celle qui permettra d'effectuer l'action désirée.

Dans toutes ses versions, Visual dBASE 7.01 est livré avec un fichier d'aide Windows particulier dénommé “Win 32 Programmer's Reference”. Nous l'appellerons par la suite “Aide sur l'API 32”. On trouvera ce fichier dans le répertoire _DBWINHOME\Help\MSHelp (où le répertoire _DBWINHOME signifie celui dans lequel vous avez installé Visual dBASE).

Nous considérerons tout d'abord comme support, dans cet article, la fonction API 32 dénommée : ShellExecute. Cette fonction permet de lancer depuis Visual dBASE une autre application Windows, sans apparition d'une fenêtre DOS, à la différence de la commande RUN() qui fait partie du langage.

Commencez par rechercher cette fonction dans le fichier d'aide API 32 cité ci-avant. Vous obtiendrez la page suivante :

Voyons à présent en détail les différentes indications portées en rouge :

Les types utilisés dans les fonctions de l'API 32 constitue un sujet important, qui fait appel à quelques notions de langage C. Entrons un peu plus dans le vif du sujet.

Premières notions de langage C

L’interface de programmation Windows peut sembler obscure au premier abord, surtout lorsqu’on a l’habitude d’utiliser un langage de haut niveau comme Visual dBASE. En effet, elle a été conçue, réalisée en langage C, et était initialement destinée à être utilisée par des programmeurs en C. Par conséquent, la difficulté d’utilisation de cette interface tient essentiellement au fait qu’il faut s’adapter aux particularités de ce langage.

Le but de ce chapitre n'est certainement pas d'apprendre le C, ni encore moins le C++. Il s'agit simplement de connaître les quelques éléments  de ce langage qui vont permettre d'utiliser les fonctions de l'API.

Une des différences principales entre le C et Visual dBASE réside dans le typage et la structure des données. Contrairement à Visual dBASE, les données en C sont toujours déclarées avec un type explicite. Le C fait également la distinction entre les variables simples et les pointeurs. Il est important d’en comprendre les différences essentielles :

La gestion dynamique de la mémoire s’effectue donc manuellement, c’est le programmeur qui décide de la taille mémoire à affecter, et qui doit la libérer lorsque celle ci n’est plus utilisée. Quand on pense au nombre d’opérations d’allocation/libération de la mémoire nécessaires dans un langage objet (comme l'ajout ou la modification d’une propriété, d'une méthode, la création d’instances d’objets,…), on comprend vite que certains programmeurs C++ qui ont ensuite goûté à un langage où la gestion de la mémoire est automatisée ne souhaitent plus retourner à l’âge de pierre…

Pour en revenir au type de données, l’API Windows utilise bien évidemment les types de données natifs du C. Ces types natifs sont en lettres minuscules. Les types dérivés sont en général en majuscule.

Un pointeur peut être déclaré de façon explicite en C, dans ce cas, le nom de la variable est précédé d'un astérisque (*). Dans les types dérivés, l' * est généralement contenue dans le nom du type, comme, par exemple, ceux de Windows, dont le nom de type commence souvent par les lettres LP, soit Long Pointer.

Voici, par exemple, quelques déclarations de variables en C :

char  c = 'a'      // Variable caractère, sur 1 octet, initialisée à la lettre 'a'.
int   *k           // Pointeur sur un integer

Exemple de tableau en C :

short   tab[2][3]        // Crée un tableau de 2 x 3 éléments, qui sont des "short".
                         // tab est en réalité un pointeur sur le premier short
                         // de la zone mémoire réservée pour le tableau

Exemple de chaînes de caractères  en C :

char  st[] = "Bonjour!"    //  Crée une chaîne, dont la taille mémoire est
                           //  exactement sa longueur + 1 (à cause du Null final),
                           //  sur laquelle pointe le pointeur st.
                           //  Cette chaîne est considérée comme figée en mémoire.

char  st_tab[256]          //  Ici, on réserve un tableau de 256 octets pour y stocker,
                           //  si on le souhaite, des caractères, au maximum 255 + '\0'

Le tableau ci après donne les types de base et les pointeurs utilisés dans Windows, qui dérivent de types C :
 

Type C natif Taille mémoire (octets) Exemple d'autres Types équivalents utilisé dans Windows Type Visual dBASE Déclaration de variable dans le prototype de fonction Visual dBASE  Déclaration de pointeur dans le prototype de fonction Visual dBASE 
char 1 BYTE  Numeric CCHAR  
unsigned char 1 UCHAR  Numeric CUCHAR  
short 2 WORD  Numeric CSHORT CPTR SHORT
unsigned short 2 USHORT  Numeric CUSHORT CPTR CUSHORT
int 4 DWORD, HWND, BOOL, HDC  Numeric CINT CPTR CINT
UINT 4    Numeric CUINT CPTR CUINT
long 4 LPVOID  Numeric CLONG CPTR CLONG
unsigned long 4 ULONG  Numeric CULONG CPTR CULONG
float 4    Numeric CFLOAT CPTR CFLOAT
double 8    Numeric CDOUBLE CPTR CDOUBLE
LDOUBLE 10    Numeric CLDOUBLE CPTR CLDOUBLE
LOGICAL 1    Logical CLOGICAL CPTR CLOGICAL
void N/A   N/A CVOID

Quelques précisions sur ce tableau :

Il est important de garder à l’esprit que ce n’est pas tant le nom du type qui importe, mais surtout SA TAILLE MÉMOIRE qui lui correspond, directement ou via le pointeur. Visual dBASE effectue ensuite la conversion automatique dans le type déclaré lors de l'appel de la fonction, selon les indications fournies dans le prototype.

Windows utilise dans les déclarations de fonctions de nombreux autres types dérivés des types de base C figurant dans le tableau. Tous ces autres types peuvent être définis dans les déclarations EXTERN à partir par des Types Visual dBASE natifs.

Une seule catégorie n'est pas supportée par Visual dBASE, il s'agit des fonctions CALLBACK, que nous n'aborderons pas ici. Brièvement, c'est un équivalent du type Function Pointer (FP) de Visual dBASE.

Les chaînes de caractères et les pointeurs C

Après avoir vu les types simples, ce chapitre traite le sujet particulier des chaînes de caractères et des pointeurs en C, et leur utilisation avec Visual dBASE dans les prototypes des fonctions de l'API.

Une chaîne de caractères est comme son nom l’indique une suite de caractères. Ces caractères peuvent être stockés :

A l’extrême, une chaîne Unicode peut être vue comme une chaîne d’octets, puisque 1Short = 2Chars. Dans ce cas, on s’aperçoit qu’un octet sur deux vaut chr(0). Visual dBASE adopte en interne la convention Unicode pour le stockage des chaînes de caractères, comme de nombreuses autres applications 32 bits. Il faut être vigilant lorsque l’on manipule directement les octets d’une chaîne de caractères Visual dBASE au moyen des fonctions setByte et getByte, qui elles agissent au niveau de l'octet.

Exécutez les quelques lignes de code suivantes pour visualiser comment les chaînes sont stockées dans dBASE. Notez que commence à 0.

s = new String("Bonjour")
for j = 0 to (s.Length*2)-1
  ?s.getByte(j), chr(s.getByte(j))
next j

Lorsqu'une chaîne de caractères doit être fournie à une fonction de l'API, il faut respecter impérativement le type attendu, byte ou Unicode.

Note: Une indication importante à connaître et qui concerne les chaînes de caractère et le langage C : celui-ci impose que le dernier caractère d’une chaîne soit obligatoirement le caractère chr(0), encore appelé Null char, ou '\0'. Visual dBASE tient compte de cette contrainte lorsqu’il effectue les appels des fonctions externes et qu'il doit passer une chaîne de caractères en argument, mais cette règle doit toujours rester présente à l’esprit, plus particulièrement lorsque des conversions de type byte <-> Unicode interviennent.

Une chaîne de caractères en C consiste toujours en une suite de CHAR ou de WORD (si la chaîne est Unicode), sur laquelle pointe un “pointeur” C de type CHAR ou de WORD. Ce pointeur est lui-même une variable, qui contient une adresse mémoire, l'adresse du premier caractère de la chaîne qu'il pointe. Déclarer une chaîne de caractères en C revient toujours à déclarer un pointeur - c'est à dire une adresse mémoire - sur cette chaîne. La longueur d'une chaîne est fixée implicitement par le premier Null de la chaîne.

Pour utiliser les chaînes, deux prototypes de paramètres existent dans Visual dBASE, il s'agit des mots clé CSTRING et CPTR. Ils désignent tous deux un pointeur sur une chaîne, mais ils présentent les différences fondamentales suivantes :
 
 

Déclaration CSTRING Déclaration CPTR
Conversion automatique implicite de Unicode vers Byte lors d'un appel à la fonction externe.
La chaîne peut contenir des caractères Null, mais j'ai rencontré des problèmes avec des chaînes contenant des Null.
Aucune conversion lors de l'appel

La chaîne peut contenir des caractères Null.

Conversion automatique implicite de Byte vers Unicode si la fonction retourne une chaîne. 
La règle du Null char s'applique à la chaîne retournée, avec la même restriction d'emploi.
Le type retour de la fonction ne peut pas être CPTR.


Le prototype et la déclaration des fonctions de l'API avec Visual dBASE

Revenons sur la fonction ShellExecute que nous avons abordé au chapitre Présentation des fonctions de l'API 32. Nous allons à présent prototyper, cet à dire déclarer cette fonction avant de l'utiliser pour expliciter de quelle façon Visual dBASE doit l'appeler dans un programme.

Voyons d'abord la syntaxe générale d'une déclaration de fonction externe. L'aide en ligne, concernant la rubrique “extern”, propose deux écritures possibles, mais elles peuvent se généraliser à la seconde expression :

Dans l'ordre, nous retrouvons :

Revenons à la fonction ShellExecute. D’après les indications de l ‘aide en ligne API 32, les types suivant sont utilisés : Ces types sont des types Windows, à l'exception de INT. Il faut à présent trouver l’équivalent de ces types dans Visual dBASE.

Heureusement, le travail est en partie déjà effectué. Le répertoire _DBWINHOME\Include contient un certain nombre de fichiers prototypes, (fichiers portant l’extension .h), contenant l’ensemble des déclarations utilisées avec les fonctions de l’API 32.

Le fichier contenant les déclarations des types Windows et C est le fichier Windef.h. Il suffit donc d’inclure ce fichier dans le code source de votre programme :
// Inclure le fichier Windef.h
#include <Windef.h>
A présent, il faut déclarer la fonction en respectant rigoureusement les types spécifiés. Là encore, le répertoire _DBWINHOME\Include contient l'information souhaitée. Il comporte un fichier, dénommé win32api.prg, qui contient les déclarations des fonctions de l’API 32. Prototyper notre fonction devient un jeu d’enfant : il suffit de rechercher la ligne comportant la déclaration de la fonction (ShellExecute dans notre cas) et la recopier dans notre code source :
// Déclaration du Prototype de la fonction ShellExecute
extern HINSTANCE ShellExecute(HWND, LPCSTR, LPCSTR, LPCSTR, ;
                              LPCSTR, CINT) shell32 ;
                              from "ShellExecuteA"

Compilons alors le programme constitué de ces deux lignes de code : Une erreur se produit. Elle est due à une déclaration qui pose problème, HWND , figurant dans la fonction. En effet, HWND est un mot clé de Visual dBASE, d’où un conflit.

La meilleure solution est de remplacer la déclaration HWND par HANDLE, qui donne le bon résultat. Cela donne donc :

// Déclaration du Prototype de la fonction ShellExecute
extern HINSTANCE ShellExecute(HANDLE, LPCSTR, LPCSTR, LPCSTR, ;
                              LPCSTR, CINT) shell32 ;
                              from "ShellExecuteA"

Recompilons. Cette fois, c’est correct ; la fonction est déclarée, et prête à être employée.

Ce problème lié à HWND est le seul que j’ai rencontré à ce jour, tous les autres types des fonctions de win32API.prg  que j'ai utilisé jusqu'à présent fonctionnent correctement.

Pour illustrer ce qui a été dit plus haut et concernant le mot clé from dans cette commande, remplacez la déclaration de la fonction par la suivante, qui élimine le mot clé from :

// Déclaration du Prototype de la fonction ShellExecuteA, sans le mot clé from
extern HINSTANCE ShellExecuteA(HANDLE, LPCSTR, LPCSTR, LPCSTR, ;
                              LPCSTR, CINT) shell32

Compilez : c'est OK, il faudra simplement utiliser la fonction en appelant ShellExecuteA(<Paramètres>)au lieu de shellExecute(<Paramètres>).

En résumé, l’étape délicate de la déclaration du prototype de la fonction API s’est donc limitée à


La déclaration des constantes de l'API 32

Considérons toujours la même fonction ShellExecute et reprenons la page du fichier d’aide correspondante. Intéressons nous à présent au paramètre nShowCmd. Ce paramètre, qui représente la façon dont la fenêtre du nouveau programme sera ouverte, peut prendre les différentes valeurs indiquées :

Comment connaître la valeur de ces constantes ? Une fois de plus, Le répertoire _DBWINHOME\Include détient la réponse. Il comporte en effet d’autres fichiers prototypes, qui regroupent des déclarations de constantes utilisées dans certaines des fonctions de l’API 32. Il n’y a pas de règle générale quand à l’inclusion de tel ou tel fichier, cela va dépendre des fonctions de l’API que vous voulez utiliser. Ces constantes sont en général regroupées dans un même fichier selon leur nature. D’une manière générale, la règle à adopter pour l’inclusion des fichiers de constantes serait la suivante : Dans notre cas, les constantes SW_... sont toutes définies dans le fichier Winuser.h. Il suffit pour s’en assurer d’ouvrir ce fichier et de rechercher le texte “SW_.

Le fichier comporte la section de déclarations suivante :
. . .
//
// ShowWindow() Commands
//
#define SW_HIDE              0
#define SW_SHOWNORMAL        1
#define SW_NORMAL            1
#define SW_SHOWMINIMIZED     2
#define SW_SHOWMAXIMIZED     3
#define SW_MAXIMIZE          3
#define SW_SHOWNOACTIVATE    4
#define SW_SHOW              5
#define SW_MINIMIZE          6
#define SW_SHOWMINNOACTIVE   7
#define SW_SHOWNA            8
#define SW_RESTORE           9
#define SW_SHOWDEFAULT      10
#define SW_MAX              10
. . .
Pour notre exemple, une seule ligne de code en entête du fichier code source est donc nécessaire pour déclarer l’ensemble de ces constantes  :

// Inclure le fichier Winuser.h
#include <Winuser.h>

Une alternative à l'utilisation des fichiers include pour la déclaration des fonctions et des constantes de l'API consisterait à déclarer directement celles-ci dans le code, au moyen des primitives #define et des types disponibles dans Visual dBASE. Cela est possible, mais plus fastidieux car il faut reprendre toutes les définition de la fonction et des constantes, d'où un risque d'erreur. Les fichiers include de Visual dBASE sont là pour nous servir.

Enfin, pour clore ce chapitre, les puristes auront pu constater qu’à partir du moment où le fichier Winuser.hest inclus, la déclaration d'inclusion du fichier Windef.h devient superflue. En effet, le fichier Winuser.h. effectue lui-même (pour vous en assurer, regardez les premières lignes de ce fichier) l'inclusion du fichier Windef.h. Dans notre cas, la seule inclusion du fichier Winuser.h est donc suffisante pour déclarer à la fois les types et les constantes utilisés par ShellExecute.

Exécution d'une fonction de l'API 32

Voyons à présent comment exécuter une fonction de l'API. Nous allons lancer simplement la calculatrice Windows. Celle-ci se trouve dans le répertoire Windows par défaut, C:\Windows, le nom du fichier exécutable est : Calc.exe

Consultons de nouveau dans l'aide sur la fonction ShellExecute , la section détaillant le contenu des paramètres :

Écrivons à présent la commande Visual dBASE complète, à la suite des commandes de déclaration saisies dans les chapitres : Cela donne :

// Inclure le fichier Winuser.h
#include <Winuser.h>

// Déclaration du Prototype de la fonction ShellExecute
extern HINSTANCE ShellExecute(HANDLE, LPCSTR, LPCSTR, LPCSTR, ;
                              LPCSTR, CINT) shell32 ;
                              from "ShellExecuteA"

ShellExecute( _app.FrameWin.hwnd, "open", "calc.exe", Null, "C:\Windows", SW_SHOWNORMAL )

Exécutez le programme. La calculatrice Windows s'affiche. Cet exemple a montré l'utilisation de paramètres de différents types, numériques, chaînes de caractères, constantes de l'API 32.

Il reste à présent un type de paramètre dont nous avons seulement révélé l'existence, il s'agit des structures C. Elles font l'objet de la fin de cet article, car leur maniement nécessite d'avoir acquis les premières notions de base développées jusqu'à présent.

Les structures C et Visual dBASE

Si le C est resté longtemps une référence dans le monde du développement logiciel avant que les langages objets ne soient introduits, c'est sans doute en partie grâce à la puissance que lui confèrent les structures.

Afin d'illustrer les structures C, nous allons considérer à présent le cas de la fonction API 32 nommée GetVersionEx, fonction qui permet d'obtenir la version du système d'exploitation de l'ordinateur.

Recherchons d'abord l'aide sur cette fonction dans le fichier API :

Cette fonction utilise un seul paramètre dénommé lpVersionInformation, qui est un un pointeur C sur une structure dénommée OSVERSIONINFO. Cliquons sur ce lien OSVERSIONINFO  indiqué dans le paramètre. Nous obtenons l'écran suivant :

Voyons à présent en détail les différentes indications portées en rouge :

Puisque nous avons proposé cette analogie entre les structures C et les objets, voici maintenant quelles sont les différences fondamentales entre eux, cela facilitera la compréhension du concept  :
 
Structure C Classes et Objets
La structure C est une entité statique.

Il s'agit d'un ensemble de variables ou de pointeurs déclarés, de taille fixe. ils sont placés les uns à la suite des autres en mémoire, dans l'ordre dans lequel ils sont déclarés. Leur emplacement en mémoire ne change pas.

L'espace mémoire occupé par une structure C est fixe, et correspond exactement à la somme des occupations mémoires des variables membres qui la constituent.

Par opposition, un objet est entièrement dynamique.

Il comprend des éléments (propriétés, méthodes) qui peuvent être modifiés, ajoutés, supprimés, etc...

L'occupation mémoire des membres d'un objet n'est pas forcément contiguë, puisque une gestion dynamique de l'espace mémoire est effectuée.

La taille d'un objet est variable, et dépend de son contenu à un instant donné.

Une structure ne peut contenir que des variables ou des pointeurs. Un objet peut contenir des propriétés (qui sont elles mêmes des variables ou des pointeurs) et des méthodes.

Pour mieux se représenter d'une structure, voici l'occupation mémoire de la structure C OSVERSIONINFO :
 

 Occupation mémoire : Total = 148 octets Champ de la structure
Offset : n à n + 3 dwOSVersionInfoSize
Offset : n + 4 à n + 7 dwMajorVersion
Offset : n + 8 à n + 11 dwMinorVersion
Offset : n + 12 à n + 15 dwBuildNumber
Offset : n + 16 à n + 19 dwPlatformId
Offset : n + 20 à n + 147 szCSDVersion

Structures et classes étant différentes par nature, il n'est pas possible d'utiliser directement la seconde pour simuler la première.

Manipuler le contenu d'une structure avec Visual dBASE nécessite l'accès à un bloc de mémoire, représentant le contenu de la structure (les différents membres), dans lequel on intervient directement au niveau des octets.

Fondamentalement, le seul mécanisme offert consiste à considérer le bloc de mémoire comme une chaîne de caractères Visual dBASE, puis, grâce aux méthodes  setByte et getByte, à lire et modifier le contenu de la chaîne octet par octet. Même si cette méthode fonctionne, autant dire qu'elle est extrêmement ardue et lourde, et qu'il faut passer un temps certain avant d'arriver au bon résultat.

Heureusement, il existe une autre méthode bien plus efficace.

Même si dans Visual dBASE nous n'avons pas de structures, nous avons un outil beaucoup plus puissant que les structures C : ce sont les objets et les classes.

Nous allons à présent voir comment simuler le comportement d'une structure C, et parvenir, dans un langage de classes comme Visual dBASE, à remplacer les structures par des classes, et obtenir un maniement de ces dernières avec la même souplesse que les objets.

Parmi les outils et les exemples livrés avec Visual dBASE 7.01, figure un ensemble de fichiers qui vont permettre d'atteindre ce but. Les fichiers sont les suivants :

Nous allons voir comment utiliser ces 4 fichiers.
  1. Nous allons représenter chaque structure C par un objet créé à partir de la classe Structure, fournie dans le fichier  _DBWINHOME\Samples\Structure.prg.

  2. Cette classe Structure met à disposition la propriété .value ainsi que les méthodes addMember(), setMember(), getMember(), et length() qui vont permettre de paramètrer et contrôler l'objet créé à partir de la classe Structure.
  3. La propriété .value  reçoit une chaîne de caractères Visual dBASE, celle précisément qui représente le contenu des variables C de la structure C.
  4. Pour décrire les membres de la structure C, la méthode addMember() permet d'ajouter à l'objet un nouveau membre.

  5. Pour décrire ce membre, il faut indiquer, comme en C,  son type, ainsi que son nom.
    Les membres devront être déclarés dans le même ordre que dans la structure C. Grâce au type fourni lors de l'appel à addMember, la position et l'occupation du membre dans la chaîne .value est calculée et conservée avec la description du membre.
  6. Pour accéder au contenu d'un membre, 2 methodes setMember() et getMember() permettent de lire et modifier le contenu du membre spécifié, à partir de la chaîne .value.
  7. Enfin, la taille de la structure, en octets, peut être connue par un appel à la méthode length().
La description des méthodes et des propriétés de la classe Structure figure en commentaire, dans l'entête du fichier _DBWINHOME\Samples\Structure.prg.

Voyons à présent sur un cas concret ce que cela donne. Nous allons appeler la fonction API 32 GetVersionEx , puis accéder aux membres de la structure OSVERSIONINFO.

Les lignes de code décrites ci-après ne figurent pas forcément dans le même ordre que celui dans lequel elles devraient être exécutées, mais cela aide à la compréhension et aux explications.

Tout d'abord voyons les fichiers Include. Une rapide recherche sur les constantes et les types API utilisés avec GetVersionEx et OSVERSIONINFO montre qu'il faut inclure les fichiers ci-après.
Le fichier StructAPI.h doit de plus être inclus car nous utilisons la classe Structure :

// Fichiers Include nécessaires
#include <Windef.h>
#include <Winbase.h>
#include <StructAPI.h>

Ensuite, il faut charger la description de la classe Structure, qui est contenue dans le fichier _DBWINHOME\Samples\Structure.prg:

// Charge la classe Structure
set procedure to '&_dbwinhome.samples\structure.prg' additive

A présent regardons comment déclarer la classe OSVERSIONINFO correspondant à la structure C. Celle-ci hérite de la classe Structure, définie dans le fichier _DBWINHOME\Samples\Structure.prg que nous venons de charger. Ensuite, la méthode addMember est utilisée pour déclarer les membres de la structure. Cette méthode utilise comme paramètres :

Les membres doivent être saisis dans le même ordre que celui de la structure C. Dans notre exemple, la déclaration prend la forme suivante :

// Cette définition de classe correspond à la définition de la structure C
// de type _OSVERSIONINFO. La fonction addMember crée les membres de la structure,
// en utilisant le Type et le Nom du champ.
class _OSVERSIONINFO of Structure
   super::addMember( TYPE_DWORD,   "dwOSVersionInfoSize" )
   super::addMember( TYPE_DWORD,   "dwMajorVersion" )
   super::addMember( TYPE_DWORD,   "dwMinorVersion" )
   super::addMember( TYPE_DWORD,   "dwBuildNumber" )
   super::addMember( TYPE_DWORD,   "dwPlatformId" )
   super::addMember( TYPE_STRING,  "szCSDVersion", 128 )

   // Cette ligne permet de renseigner le premier membre de la structure, qui
   // doit, comme précisé dans l'aide API 32, contenir la taille de la structure.
   // la méthode length() de la classe Structure permet d'obtenir sa taille
   // totale exprimée en octets. Ainsi, lors de la création de l'instance d'objet
   // à partir de la classe _OSVERSIONINFO, ce membre sera automatiquement initialisé.
   super::setMember( "dwOSVersionInfoSize", this.length( ))
endclass

Vous aurez sans doute noté que la dernière ligne de la classe _OSVERSIONINFO appelle la méthode setMember. En effet, la page d'aide sur la structure OSVERSIONINFO indique que le membre dwOSVersionInfoSize doit contenir la taille de la structure. En invoquant la méthode length qui retourne la taille de la structure, ce membre sera  renseigné automatiquement.

A présent, l'appel de la fonction GetVersionEx nécessitant un prototype, celui-ci peut être obtenu en recopiant les lignes correspondantes du fichier _DBWINHOME\Include\Win32api.prg :

// Prototype de la fonction GetVersionEx tel qu'il est libellé dans Win32api.prg
extern BOOL        GetVersionEx( LPSTRUCTURE ) kernel32 ;
                   from "GetVersionExA"

Quelques variables locales sont utilisées, notamment OSVERSIONINFO qui contient l'objet créé à partir de la classe _OSVERSIONINFO :
local  OSVERSIONINFO, dwPlatformId

L'objet OSVERSIONINFO va être maintenant créé comme une instance de la classe _OSVERSIONINFO  :

// Crée l'objet structure
OSVERSIONINFO = new _OSVERSIONINFO( )

Maintenant que l'objet structure OSVERSIONINFO est créé, la fonction GetVersionEx peut être appelée afin de renseigner le contenu de l'objet.

NOTEZ que c'est la propriété .value qui est passée comme paramètre à l'appel de la fonction, puisque c'est cette chaîne de caractères qui représente le bloc de mémoire dans lequel les membres de la structure C sont stockés  :

// Appelle la fonction GetVersionEx en passant le paramètre OSVERSIONINFO.value,
// qui repéresente le contenu de la structure en mémoire
GetVersionEx( OSVERSIONINFO.value )

Le reste du code permet d'afficher les valeurs des membres qui viennent d'être renseignés par GetVersionEx, grâce à la méthode getMember.

Une macro commande, dénommée LOWORD, et déclarée dans le fichier Windef.h, permet de récupérer uniquement le mot 16 bits de poids faible d'un mot de 32 bits. Son équivalent HIWORD pour les 16 bits de poids fort existe également. Vous pouvez consulter cet endroit du fichier Windef.h, il contient des macros utiles pour séparer ou concatener des mots binaires.

// Affiche le contenu des membres de la structure
? 'Major Version', OSVERSIONINFO.getMember( 'dwMajorVersion' )
? 'Minor Version', OSVERSIONINFO.getMember( 'dwMinorVersion' )
? 'Build Number', LOWORD( OSVERSIONINFO.getMember( 'dwBuildNumber' ))
dwPlatformId = OSVERSIONINFO.getMember( 'dwPlatformId' )

do case
 case dwPlatformId == VER_PLATFORM_WIN32s
  ? 'Win32s on Win 3.1'
 case dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
  ? 'Win32 on Windows 95'
 case dwPlatformId == VER_PLATFORM_WIN32_NT
  ? 'Win32 on Windows NT'
endcase

Le programme Visual dBASE complet et correspondant à cet exemple peut être chargé par le lien en bas de page. Exécutez le. Dans la fenêtre de commande Visual dBASE s'affiche le numéro de version de Windows, l'indice de génération, ainsi que la version du système d'exploitation.

Création d'un exécutable utilisant les fichiers de structure

Il est tout à fait possible d'utiliser la méthode décrite dans le chapitre précédent dans un programme exécutable généré avec Visual dBASE, à condition de suivre les indications suivantes :

  1. Inclure dans votre fichier projet .prj une référence aux fichiers suivants :
  2. Lors du déploiement de l'application, il faut prévoir de placer une copie du fichier DLL Structmem.dll dans le même répertoire que votre programme exécutable.

Encore plus loin avec les structures

Bien qu'elle constitue un excellent point de départ, la classe Structure de Structure.prg souffre de quelques petites lacunes, qui brident dans certains cas son champ d'application.

Pour cette raison, j'ai créé ma propre classe StructureEx, compatible de la classe Structure, et améliorée des points figurant ci-dessous. Cette classe peut remplacer la classe Structure.

Voici en quelques points les amélioration apportées :

  1. Le nombre de types possibles supportés par la classe Structure pourrait être facilement étendu, car d'autres types, déclarés dans le fichier _DBWINHOME\Include\StructAPI.h,  sont supportés par la DLL Structmem.dll. En particulier, le TYPE_CHAR , ou  TYPE_BYTE qui est souvent utilisé dans les structures C, ainsi que le type TYPE_POINTER pour les déclarations de pointeurs.
  2. La classe structure est un objet simple (classe object()), l'accès à un membre s'effectue par une recherche sur le nom, ce qui peut entraîner un traitement relativement long dans le cas où la structure contient de nombreux membres. La classe StructureEx est dérivée d'un AssocArray ce qui est plus approprié, rapide, et qui simplifie les traitements.
  3. Certaines structures C, comme la structure DCB pour les communications série, (rechercher cette page dans l'aide API), utilisent des champs de bits. En d'autres termes, certains membres sont décrits par des groupes de bits. L'accès à des groupes de bits au moyen de la classe Structure n'est pas très simple. Avec StructureEx, ce type de membre peut être déclaré et géré comme les autres types.
Sa description et son utilisation dépassant le cadre de ce premier article, elle sera disponible et fera l'objet d'un prochain article.

Conclusion

En fin de compte, utiliser les appels directs à l'API 32 de Windows depuis Visual dBASE 7.01 n'est pas une tâche aussi ardue, dès lors que l'on s'y prend de façon rationnelle. Il ne s'agit pas là d'une présentation exhaustive du sujet, mais d'une première approche simple pour pouvoir démarrer. Bien entendu, d'autres méthodes sont possibles, chacun est libre d'utiliser sa technique personnelle, mais j'ai pensé que cette démarche, et les quelques notions de langage C auxquelles elle fait appel, méritaient d'être présentées compte tenu de leur simplicité de mise en oeuvre.

Visual dBASE montre une fois de plus que, grâce à son approche objet, il permet de s'adapter à un type de situation pour lequel il n'a pas forcément été prévu.

Il n'en reste pas moins que cette API Windows demeure parfois d'un abord délicat compte tenu de certaines contraintes dues à sa conception initiale.

Maintenant, à vous de jouer !

Nicolas Martin
 

Pour charger le programme d'exemple,  cliquer ici
(c'est un fichier autoexécutable de 31 Ko)