Ce tutoriel se base sur le compilateur
Delphi, mais est facilement transposable sur n'importe quel
autre language, sauf bien sur en MsC++ où il est un peu sans
objet vu qu'il n'y a normalement pas moyen de lancer un prog
sans connaître tout ce qui va suivre... |
Une grande particularité qui passe
souvent inaperçue, c'est que d'un point de vue multitâche,
win95 n'a vraiment rien à voir avec win3.1. En effet, sous
win3.1, il fallait dire explicitement au système que l'on
désirait lui rendre un moment la main, tandis que sous win95,
ce dernier ne vous pose même pas la question : il la prend...
|
Ce qui fait que l'application ne doit
pas continuellement rapeller l'os pour maintenir ce dernier
dans un état correct. En d'autres termes, ça n'est pas parce
que vous excécutez une boucle à vide que win plante. D'ailleurs,
en général, il plante pour bien moins que ça :) |
C'est bien beau, cela nous permet
en fait d'ignorer le système, et d'agir exactement comme si
il n'était pas là. Evidement, ignorer Windows signifie également
ignorer l'utilisateur qui a lancé votre programme, car ce
n'est pas l'application qui se charge de gérer le clavier
ou la souris, mais c'est l'OS, d'ailleurs c'est bien fait
pour lui : c'est son rôle. |
Bref, vous pouvez choisir de l'ignorer,
mais votre application sera, disons, peut conviviale. Sinon,
il va falloir travailler avec lui. En général, vous lui donnez
des ordres (ouvre une fenêtre...), et lui vous donne des messages
(la fenêtre est ouverte...) |
Lui donner des ordres, c'est assez
simple, il suffit d'appeler l'api correspondant, avec les
paramètres bien comme il faut. Cela suppose évidemment que
vous connaissiez le nom de cette api, donc la dll qui la contient
ainsi que les paramètres à envoyer. Pour cela, pas de mystère,
documentation, références et aide en ligne ne seront pas de
trop. |
Pour les messages, c'est plus comique... |
. |
L'expéditeur, la poste et le destinataire
|
Imaginons, par exemple, que l'utilisateur
clique avec sa souris sur une de vos fenêtres, que vous avez
ouverte avec une belle api (ou que le compilateur à ouvert
pour vous, en appelant la même api). Windows vous envoie un
message vers une sorte de boite au lettre, dans une zone mémoire
entre lui et vous. En fait, il met ce message dans une file
d'attente, car d'autres messages sont peut-être arrivés avant
ce click. |
Vous, car vous êtes un bon programmeur,
vous allez régulièrement vérifier le contenu de cette boite
au lettre, en prenant le message le plus ancien (selon un
système FIFO : first in, first out). Vous regardez ce que
dit ce message, vous agissez en conséquence, et vous prennez
le message suivant, agissant en conséquence, etc etc... |
A un moment, vous lirez "l'utilisateur
a pressé le bouton gauche de la souris sur la fenêtre untelle".
Il y a fort à parier que quelque part dans le programme, se
trouve la routine qui doit recevoir ce genre de message, une
sorte de routine Gere_La_Souris. Il est clair en effet que
tout caser dans le programme principal serait une erreur,
nous sommes à l'ère de la programmation modulaire, voyons
voyons... |
Donc, en somme, dans la boucle principale
de votre programme, vous servez de relai aux messages venant
de Windows, les retransferant à qui de droit (ici, envoyons
le message à la routine de gestion de la souris relative à
la fenêtre untelle, chaque fenêtre devrait avoir sa gestion
de souris propre). |
En fait, c'est même encore plus gag,
car comme on module, on module, et on module encore, on n'envoie
pas directement le message à la routine souris de la fenêtre,
on envoie le message "bouton pressé" à une certaine routine
"fenetre untelle", qui, elle, va se charger de redistribuer
le message à sa propre routine souris. |
C'est pratique ce genre de gestion,
car quand on a une vingtaine de fenêtre, ça facilite la lecture
du programme, chaque module se chargant de redistribuer son
propre courier, un peu comme des burreaux de triages de plus
en plus petits jusqu'au destinataire final. |
Windows
|
|
Fenêtre----------Votre programme principal--------Fenêtre
| | | | | | |
Clavier,souris... Fenêtre Clavier, souris...
| | |
Clavier, souris...
|
Remarquons au passage que le programme
principal ne doit pas néccessairement redistribuer le message
aux fenêtre, si ce message ne concerne que lui, du genre un
message de Quit, comme une fenêtre ne doit pas forcément redistribuer
à des sous-contrôles, si ce qu'elle doit faire ne concerne
qu'elle : Fermer... |
. |
Le pony express |
Nous avons donc vu que Windows envoie
ses messages dans une file d'attente, d'où notre programme
va les rechercher. Mais que se passerait-il si il devait envoyer
un message précis à une fenêtre, un message prioritaire qui
n'a pas le temps de passer à travers toute cette file d'attente?
Et bien il ne se gênerait pas, il l'enverrait directement
à la fenêtre interessée, qui redistribuerait etc... |
Windows fait donc la distinction entre
deux types de messages : les messages qui passent par la file
d'attente (la plupart), et ceux qui arrivent directement aux
fenêtres. Pour utiliser les termes consacrés, on parlera de
"Queued Messages" et de "Nonqueued Messages", qui sont respectivement
les messages normaux et les messages express qui ne passent
pas par la file d'attente. |
. |
Le callback |
Pour la boucle principale, sa programmation
ne fait pas beaucoup de mystère. On tourne en rond en allant
rechercher (via une api, forcément) le message le plus ancient,
on le traite (en fait on ne fait rien du tout car une api
fait tout pour nous, c'est à se demander a quoi sert encore
notre boucle principale), et on boucle. On sortira de la boucle
une fois qu'on aura recu le message "c'est fini", ce qui entrainera
la fin du programme. |
Mais parlons-en, du traitement de
ces messages. Car pour appeller une routine, il faut savoir
où elle est, et donc connaitre son addresse en mémoire. C'est
pourquoi quand on ouvre une fenêtre, il faut communiquer à
Windows une adresse (un pointeur, donc) qui contient l'adresse
de la routine (une fonction) associée à cette fenêtre. (bon,
je simplifie un peu mais on ne va pas faire de chichi, hein?
:)) Windows utilise cette adresse pour ses messages express,
et également pour les messages normaux si la boucle principale
lui demande de s'occuper lui-même de la distribution. |
Et en fait, ce qui se passe à l'échelle
du programme, se passe également à l'échelle de la fenêtre.
Chaque message (ou presque) possède une routine, et donc a
une adresse (un pointeur) qui lui est associé. C'est toujours
comme cela que l'on fait, on ne fait pas des conditions dans
tous les sens, on sait qu'au message numéro untel correspond
la routine pointée par l'addresse untelle, ces pointeurs se
trouvant dans des tableaux, et roulez jeunesse. |
Et c'est excactement ce que font les
compilateurs autres que MsC++, avec ces procédures associées
à des Events qui semblent s'appeler toutes seules. Vous connaissez
à présent tout le mécanisme qui à précédé cet appel qui n'a
rien de magique. Et à présent que vous le conaissez, vous
allez pouvoir le contrôler... |
. |
Un peu de concret |
En général, vous devriez laisser la
gestion de la boucle principale à votre compilateur préféré,
car il fait bien ça et c'est plus pratique. Si je prends un
cas concret en Delphi, le programme principal se résume souvent
à : |
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
|
Il faut savoir que la boucle principale
décrite plus haut réside en fait dans Application.Run. Si
vous voulez reprogrammer la boucle principale, il vous faut
éviter d'appeler Application.Run et vous taper toute la gestion
des messages. |
Sauf que bien sur, Delphi est malin
et a déjà prévu le coup, on peut faire très facilement : |
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Fin:=False;
Repeat
Application.ProcessMessages;
Until Fin;
|
où Fin serait un booléen qui serait
modifié dans un des Events de Form1 (par exemple : OnCloseQuery)
|
Tant qu'on y est pour les précisions
: Application.ProcessMessages va lire tous les messages de
la "boite au lettre" jusqu'à ce qu'elle soit vide, alors que
Application.HandleMessage ne va en lire qu'un (le plus ancien).
C'est à vous de voir ce que vous préférez... |
Application.ProcessMessages (ou l'autre)
va donc lire les messages et les redistribuer aux fonctions
associées aux fenêtres, ces dernières les redistribuant aux
Events définis, du genre MouseDown. |
Si vous désirez reprogrammer entièrement
la boucle principale, je vous suggère de vous commenter sur
les api GetMessage, TranslateMessage, et DispatchMessage,
mais ceci sort du cadre de ce texte. |
Bien, nous progressons, nous sommes
à présent capables de rajouter quelque chose dans la boucle
principale, pratique quand vous avez besoin d'avoir une routine
appelée non stop pour un refresh d'une animation, par exemple.
Mais on peut faire encore mieux. |
. |
Détourner la fonction windows |
Donc, vous savez qu'à chaque fenêtre
est associée une fonction, que vous pouvez éventuellement
reprogrammer. Ce qui cette fois-ci a une réelle utilité, car
beaucoup d'utilitaires et gadjets windows envoient des Nonqueued
Messages, qui ne sont bien sur pas gérés par le compilateur,
et qui sont donc perdus, empechant l'utilisation, par exemple,
du Systray. |
D'un autre côté, vous devez être en
train de vous dire : "il est fou le mec, il veut qu'on reprogramme
toute la fonction windows, et donc se taper les liens vers
tous les events de cette fenêtre?". Rassurez-vous, nous allons
utiliser un vieux trucs des programmeurs sous Dos : le patchage
(whaou le franglais :)) ce qui signifie que nous allons intercepter
les messages qui arrivent, si ces messages sont pour nous,
nous les gérons, et sinon, nous rapellons l'ancienne routine
(qui est toujours quelque part) avec ce message, et le tour
est joué, le compilo n'y verra que du feu :) |
La procédure doit avoir (en Delphi)
l'en-tête suivante (le nom, on s'en fiche) : |
Function WinMsg(Win:HWnd;Msg:Word;wParam,lParam:LongWord):LongWord;StdCall
|
Win contient le handle de la fenêtre
(ce renseignement est inutile, je trouve) |
Msg contient le message en lui-même
|
wParam et lParam contiennent des paramètres
éventuels pour ce messages. |
. |
WinMsg devrait en général être du
style : |
Function WinMsg(Win:HWnd;Msg:Word;wParam,lParam:LongWord):LongWord;StdCall
Case Msg Of
Valeur:Begin
(*les messages qu'on intercepte*)
End;
Else WinMsg:=CallWindowProc(OldMsg,Win,Msg,wParam,lParam);
End;
End;
|
La valeur retournée par WinMsg est
une sorte de 'réponse' a Windows, en général ce dernier n'en
demande pas et on se contente de retourner 0. |
OldMsg:=Pointer(SetWindowLong(Form.Handle,GWL_WNDPROC,LongWord(@WinMsg)));
|
Form.Handle est le handle de la fenêtre
dont on désire modifier l'adresse, GWL_WNDPROC signifie que
cet appel de SetWindowLong est fait pour modifier cette adresse
(car SetWindowLong a bien d'autres utilités), et on passe
comme troisième paramètre l'adresse de notre routine à nous,
pendant que cette Api nous retourne l'adresse de l'ancienne
routine, ce qui est justement ce qu'il nous fallait pour terminer
la résolution de notre problème. |
. |
C'est totalement QFD... |