Logo

Marco Cantù

L'essentiel sur Pascal

Traduit de l'anglais par Iannis Papageorgiadis ipapag@village.uunet.be

Chapitre 9:
Programmer Windows en Pascal

Delphi fournit une encapsulation complète pour les API Windows de bas niveau en utilisant Pascal Objet et la bibliothèque des composants visuels (VCL); il est ainsi rarement nécessaire de construire des applications Windows en utilisant le Pascal et en appelant directement des fonctions API Windows. Néanmoins, les programmeurs qui souhaitent utiliser des techniques spéciales non supportées par la VCL ont encore cette possibilité en Delphi. On n'a besoin d'utiliser cette approche que pour des cas spéciaux, comme le développement de nouveaux composants Delphi basés sur des appels API insolites, et nous ne souhaitons pas nous étendre sur les détails. Par contre, nous verrons quelques éléments de l'interaction de Delphi avec le système d'exploitation et l'une ou l'autre technique dont les programmeurs Delphi pourraient tirer profit.

Les Handles de Windows

Parmi les types de données introduits par Windows dans Delphi, les handles représentent le groupe le plus important. Le nom de ce type de données et le type sont définis dans l'unité Windows comme suit :
type
  THandle = LongWord;
Les types de données Handle sont implémentés comme des nombres, mais ils ne sont pas utilisés comme tels. En Windows, un handle est une référence vers une structure de données interne du système. Par exemple, lorsque vous travaillez avec une fenêtre (ou une fiche Delphi), le système vous donne un handle vers la fenêtre. Le système vous informe que la fenêtre avec laquelle vous êtes en train de travailler est, par exemple, la fenêtre numéro 142. A partir de ce moment, votre application peut demander au système de travailler sur la fenêtre numéro 142 -- pour la déplacer, modifier sa taille, la réduire à une icône, etc. Beaucoup de fonctions API Windows comportent en fait un handle comme premier paramètre. Ceci ne s'applique pas uniquement aux fonctions agissant sur les fenêtres : d'autres fonctions API Windows ont comme premier paramètre un handle GDI, un handle de menu, un handle d'instance, un handle de bitmap, ou un handle parmi les nombreux autres types de handle.

En d'autres mots, un handle est un code interne que l'on utilise pour se référer à un élément spécifique que le système répertorie par son handle, y compris des fenêtres, des bitmaps, des icônes, des blocs mémoire, des curseurs, des fontes, des menus, etc. Dans Delphi on devra rarement utiliser directement les handles, puisqu'ils sont cachés à l'intérieur des fiches, des bitmaps et des autres objets Delphi. Ils deviennent utiles lorsqu'on cherche à appeler une fonction API Windows qui n'est pas supportée par Delphi.

Voici, pour compléter cette description, un exemple simple illustrant les handles de Windows. Le programme WHandle comporte une fiche simple, ne contenant qu'un bouton. Dans le code nous répondons à l'événement OnCreate de la fiche et à l'événement OnClick du bouton comme indiqué ci-dessous dans la description textuelle de la fiche principale :

object FormWHandle: TFormWHandle
  Caption = ‘Handle de la fenêtre’
  OnCreate = FormCreate
  object BtnCallAPI: TButton
    Caption = ‘Appel API’
    OnClick = BtnCallAPIClick
  end
end
Dès la création de la fiche, le programme récupère le handle de la fenêtre correspondante à la fiche en accédant à la propriété Handle de la fiche même. Nous appelons IntToStr pour convertir la valeur numérique du handle en une chaîne et nous l'ajoutons au titre de la fiche, comme on le voit à la figure 9.1 :
procedure TFormWHandle.FormCreate(Sender: TObject);
begin
  Caption := Caption + ‘ ‘ + IntToStr (Handle);
end;
Puisque FormCreate est une méthode de la classe de la fiche, elle a accès directement aux autres propriétés et méthodes de la classe. Dans cette procédure nous pouvons donc simplement faire référence directement aux propriétés Caption et Handle de la fiche.

Figure 9.1 : L'exemple WHandle affiche le handle de la fenêtre de la fiche. A chaque exécution de ce programme, vous obtiendrez une valeur différente.

Si vous exécutez ce programme plusieurs fois, vous obtiendrez généralement des valeurs de handle différentes. En fait, cette valeur est déterminée par Windows et est retournée à l'application. (Les handles ne sont jamais déterminés par le programme, ils n'ont pas de valeurs prédéfinies; ils sont déterminés par le système qui génère de nouvelles valeurs à chaque exécution du programme).

Lorsque l'utilisateur presse le bouton, le programme appelle simplement une fonction API Windows, SetWindowText, qui modifie le texte ou le titre de la fenêtre passée en tant que premier paramètre. Pour être plus précis, le premier paramètre de cette fonction API est le handle de la fenêtre que nous souhaitons modifier :

procedure TFormWHandle.BtnCallAPIClick(Sender: TObject);
begin
  SetWindowText (Handle, ‘Salut’);
end;
Ce code a le même effet que celui du gestionnaire d'événement précédent qui modifiait le texte de la fenêtre en donnant une nouvelle valeur à la propriété Caption de la fiche. Dans ce cas, appeler une fonction API n'a pas de sens, parce qu'il existe une technique Delphi plus simple. Cependant, quelques fonctions API n'ont pas de correspondant en Delphi, comme on le verra plus loin dans des exemples plus avancés.

Les déclarations externes

Un autre élément important de la programmation Windows est représenté par les déclarations externes. A l'origine, elles étaient utilisées pour lier le code Pascal à des fonctions externes écrites en assembleur; la directive external est utilisée dans la programmation Windows pour appeler une fonction d'une DLL (une bibliothèque liée dynamiquement). En Delphi, il existe un certain nombre de telles déclarations dans l'unité Windows :
 
// déclaration forward
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;

// déclaration externe (au lieu du code réel)
function LineTo; external ‘gdi32.dll’ name ‘LineTo’;
Cette déclaration signifie que le code de la fonction LineTo est stocké dans la bibliothèque dynamique GDI32.DLL (une des plus importantes bibliothèques système de Windows) sous le même nom. En fait, dans une déclaration external,  nous pouvons spécifier que notre fonction fait référence à une fonction d'une DLL qui avait à l'origine un nom différent.

On a rarement besoin d'écrire de telles déclarations, puisqu'elles sont déjà listées dans l'unité Windows et dans beaucoup d'autres unités système de Delphi. La seule raison pour laquelle on pourrait être amené à écrire des déclarations externes serait l'appel de fonctions d'une DLL personnalisée, ou l'appel de fonctions non documentées de Windows.
 

Remarque : Dans la version 16 bits de Delphi, la déclaration external utilisait le nom de la bibliothèque sans l'extension et elle était suivie par la directive name (comme dans le code ci-dessus) ou par une directive index, suivie par le numéro ordinal de la fonction à l'intérieur de la DLL. Le changement reflète une modification système dans la façon d'accéder aux bibliothèques. Bien que Win32 permette encore l'accès aux fonctions DLL par numéro, Microsoft a déclaré que cet accès ne serait plus possible à l'avenir. Remarquez également que l'unité Windows remplace les unités WinProcs et WinTypes de la version 16 bits de Delphi.

Une fonction de rappel Windows

Au chapitre 6, nous avons vu que Pascal Objet supporte des types procéduraux. Une utilisation courante des types procéduraux consiste à fournir des fonctions de rappel à une fonction API Windows.

Avant tout, qu'est-ce qu'une fonction de rappel (callback) ? Quelques fonctions API accomplissent une action donnée sur un nombre d'éléments internes du système, comme toutes les fenêtres d'un certain type. Ces fonctions, appelées également fonctions énumérées, demandent comme paramètre l'action à accomplir sur chacun des éléments, passée en tant que fonction ou en tant que procédure compatible avec un type procédural donné. Windows utilise des fonctions de rappel dans d'autres circonstances, mais nous nous limiterons à ce cas simple.

Considérons maintenant la fonction API EnumWindows, dont le prototype est le suivant (copié depuis le fichier d'aide de Win32) :

BOOL EnumWindows(

  WNDENUMPROC lpEnumFunc,  // address of callback function
  LPARAM lParam // application-defined value
  );
Bien sûr, il s'agit de la définition en C. Nous pouvons voir dans le fichier WINDOWS.PAS la définition correspondante en Pascal :
function EnumWindows (
  lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall;
En consultant le fichier d'aide, nous voyons que la fonction passée par paramètre devrait être du type suivant (encore en C) :
BOOL CALLBACK EnumWindowsProc (
  HWND hwnd, // handle of parent window
  LPARAM lParam // application-defined value
  );
Ceci correspond à la définition de type procédural Delphi que voici :
type
  EnumWindowsProc = function (Hwnd: THandle;
    Param: Pointer): Boolean; stdcall;
Le premier paramètre est le handle de la fenêtre principale en exécution, tandis que le second est la valeur que nous avons passée en appelant la fonction EnumWindows. En fait, en Pascal, le type TFNWndEnumProc n'est pas défini complètement; il s'agit simplement d'un pointeur. Ceci veut dire que nous devons fournir une fonction avec le paramètre approprié et l'utiliser alors comme paramètre en prenant l'adresse de la fonction au lieu de l'appeler. Malheureusement, ceci signifie aussi que le compilateur ne fournira pas d'aide en cas d'erreur dans le type d'un des paramètres.
 
Windows demande aux programmeurs d'utiliser la convention d'appel stdcall pour chaque appel d'une fonction API Windows ou de passer une fonction de rappel au système. Delphi utilise, par défaut, une convention d'appel plus efficace, indiquée par la directive register.
 
Voici la définition d'une fonction véritablement compatible, qui lit le titre de la fenêtre dans une chaîne et l'ajoute dans une boîte liste (ListBox) d'une fiche donnée :
function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall;

var

  Text: string;

begin

  SetLength (Text, 100);

  GetWindowText (Hwnd, PChar (Text), 100);

  FormCallBack.ListBox1.Items.Add (

    IntToStr (Hwnd) + ': ' + Text);

  Result := True;

end;
La fiche possède une boîte liste (ListBox) occupant presque toute la surface, et un petit panel sur le haut hébergeant un bouton. Lorsque le bouton est pressé, la fonction API EnumWindows est appelée et la fonction GetTitle lui est passée en paramètre :
procedure TFormCallback.BtnTitlesClick(Sender: TObject);

var

  EWProc: EnumWindowsProc;

begin

  ListBox1.Items.Clear;

  EWProc := GetTitle;

  EnumWindows (@EWProc, 0);

end;
Nous aurions pu appeler la fonction sans stocker d'abord temporairement la valeur dans une variable de type procédural, mais nous souhaitions voir clairement ce qui se passait dans cet exemple. L'effet de ce programme est vraiment très intéressant, comme on peut le voir à la figure 9.2. L'exemple Callback affiche une liste de toutes les fenêtres principales tournant dans le système. La plupart d'elles sont vraiment des fenêtres cachées qu'habituellement on ne voit jamais (et bon nombre d'entre elles n'ont en fait pas de titre).
 
Figure 9.2 : La sortie de l'exemple Callback, listant les fenêtres principales courantes (visibles ou cachées)


 

Un programme Windows minimal

Pour compléter ce tour d'horizon de la programmation Windows et du langage Pascal, nous proposons une application très simple mais complète, construite sans utiliser la VCL. Le programme prend simplement le paramètre ligne de commande (stocké par le système dans la variable globale cmdLine) et en extrait l'information à l'aide des fonctions Pascal ParamCount et ParamStr. La première fonction retourne le nombre de paramètres, la seconde retourne le paramètre à une position donnée.

Bien que  les utilisateurs spécifient rarement des paramètres ligne de commande dans un environnement avec interface utilisateur graphique, les paramètres ligne de commande Windows sont très importants pour le système. Par exemple, une fois que l'on a défini une association entre une extension de fichier et une application, on peut faire tourner un programme en sélectionnant simplement un fichier associé. Pratiquement, lorsqu'on double-clique sur un fichier, Windows démarre le programme associé et passe le fichier sélectionné en tant que paramètre ligne de commande.

Voici le code source complet du projet (un fichier DPR et non pas un fichier PAS) :

program Strparam;

uses
  Windows;

begin
  // afficher toute la chaîne
  MessageBox (0, cmdLine, 'Ligne de commande de StrParam', MB_OK);
  // afficher le premier paramètre
  if ParamCount > 0 then
    MessageBox (0, PChar (ParamStr (1)), '1er paramètre de StrParam', MB_OK)
  else
    MessageBox (0, PChar ('Il n''y a pas de paramètres'),
                '1er paramètre de StrParam', MB_OK);
end.
Le code d'affichage utilise la fonction API MessageBox pour éviter simplement d'importer la VCL entière dans le projet. Un pur programme Windows comme celui-ci a l'avantage d'occuper une très petite quantité mémoire; le fichier exécutable du programme est d'environ 16 Kbytes.

Pour fournir un paramètre ligne de commande à ce programme, on peut utiliser la commande Paramètres du menu Exécuter de Delphi. Une autre technique consiste à ouvrir l'Explorateur Windows, à localiser le répertoire (le dossier) qui contient le fichier exécutable du programme et à faire glisser le fichier qu'on souhaite faire exécuter sur le fichier exécutable. L'Explorateur Windows démarrera le programme en utilisant le nom du fichier déposé en tant que paramètre ligne de commande. La figure 9.3 affiche tant l'Explorateur que le résultat correspondant (output).

Figure 9.3 : On peut fournir un paramètre ligne de commande à l'exemple StrParam en faisant glisser un fichier sur le fichier exécutable dans l'Explorateur Windows. Le résultat de cette opération de glissement sur la partie supérieure de la figure est affiché à la portion la plus basse :

Conclusion

Ce chapitre présentait une introduction à la programmation Windows de bas niveau au départ d'une discussion sur les handles et d'un programme Windows très simple. Pour les tâches habituelles de la programmation Windows, on utilisera généralement le support de développement visuel fourni par Delphi et basé sur la VCL. Mais cela dépasse le cadre de cet ouvrage, limité au langage Pascal.

Le chapitre suivant traitera des types variant, un ajout très étrange au système de type de Pascal, introduit pour fournir un support OLE complet.

Chapitre suivant : Les types Variant

© Copyright Marco Cantù, Wintech Italia Srl 1995-99