24 novembre 2003 Par Bob et CGi Chapitre 1 : Les bases d'un programme Windows 1. Cration d'une bote de dialogue 2. Cration d'une fentre 3. Affichage dans une fentre 4. Lecture dans un fichier et bote de dialogue 5. Pas pas pour C++ Builder 6. Lecture d'un fichier WAV 7. Cration d'une application console 8. Fentre classique ou ressource ? Chapitre 2 : Les botes de dialogue 1. Fonctionnement gnral 2. Initialisation 3. Le contrle 'Edit' 4. Le contrle 'Check Box' 5. Le contrle 'Radio Button' 6. Le contrle 'List Box' 7. Le contrle 'Progress bar' 8. Le contrle 'Combo box' 9. Synthse sur les contrles 10. Deux projets rcapitulatifs 11. MessageBox() 12. Parcourir l'arborescence Chapitre 3 : Les fentres 1. Fentres et botes de dialogue 2. Fonctionnement gnral d'une fentre 3. Rcupration des messages 4. Cration d'une fentre 5. Destruction d'une fentre 6. Contexte d'affichage 7. Gestion de base d'une fentre 8. Interactions avec l'utilisateur 9. Les timers 10. Un projet rcapitulatif 11. Affichage de texte dans une fentre 12. Utilisation de polices personnalises 13. Affichage d'une image 14. Dessin 15. Fond personnalis 16. Commandes systmes 17. Menus 18. Cration dynamique d'un contrle Chapitre 4 : Le systme de fichier Page 1 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php 1. Introduction 2. Cration et ouverture d'un fichier 3. Lecture/Ecriture dans un fichier 4. Manipulations sur les fichiers 5. Enumration de fichiers 6. Manipulations sur les dossiers 7. Manipulations des chemins Chapitre 5 : Le multithreading 1. Introduction 2. Notion de processus et de thread 3. Partage des tches 4. Synchronisation 5. Premier exemple 6. Arrt d'un thread 7. Rcupration des messages 8. Communication inter-threads 9. Sections Critiques 10. Fonctions d'attente 11. Evnements 12. Smaphores 13. Trois projets simples 14. Performances
Chapitre 1 Les bases d'un programme Windows 1. Cration d'une bote de dialogue Cours thorique : Bien comprendre le principe de fonctionnement d'une application Windows est essentiel avant de commencer programmer. Je vais ici exposer le principe global de fonctionnement d'une application crant une fentre. Tout d'abord, il convient de bien garder l'esprit qu'un programme fonctionnant sous Windows et grant une fentre doit rester en dialogue permanent avec Windows. Le programme ne connait (a priori) rien sur son environnement. C'est Windows qui signale l'application si elle doit redessiner le contenu d'une de ses fentres, ou encore si l'utilisateur essaie de fermer la fentre. Cette communication se fait au travers de messages que Windows envoie chaque fentre concerne. C'est au programme d'effectuer la rception de ces messages et de les transmettre aux fonctions grant les diffrentes fentres. La rception de ces messages ne doit donc pas prendre de Page 2 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php retard, et le traitement de chacun des messages doit tre bref. En cas de retard, le redessinement des fentres n'est donc plus assur, ce qui a pour consquence des fentres blanches, indplaables, similaires celles des programmes "plants". Chaque fentre est associe une fonction ou procdure de fentre (Window Proc). Parfois plusieurs fentres peuvent tre associes une mme procdure. Chaque message reu est transmis la procdure correspondante qui se chargera de traiter ce message (redessiner la fentre, la redimensionner, afficher un caractre entr par l'utilisateur...). Une partie du travail de rafraichissement de la fentre est pris en charge par Windows. Le programme n'a redessiner que la zone client de sa fentre (et non pas la barre de titre, les menus ventuels...). Projet N1 : - Objectif : crer une bote de dialogue et une procdure trs simple charge de la grer. - Ralisation : la bote de dialogue sera cre grce l'diteur de ressources de VC++. Le programme sera charg de la rception des messages et du traitement des plus simples. Tlcharger le projet ici. - Le projet pas pas : Tout d'abord, il s'agit de crer le projet dans VC++ : File/New/Projects/Win32 Application. Donnez ensuite un nom votre projet et cliquez 'Ok'. Ensuite, slectionnez 'An empty project' puis 'Finish' et 'Ok'. Nous venons de crer un projet vide, il ne contient aucun fichier de code. Ajoutons maintenant un fichier de code et fichier de ressources. Allez dans File/New/Files/C++ Source File. Appelez ce fichier main.cpp et validez. Refaites la mme opration mais avec un fichier de type Ressource Script nomm res. Fermez le fichier qui s'est ouvert dans l'diteur. Le script de ressources est associ un fichier d'en-tte cr et mis jour automatiquement, mais qu'il convient d'ajouter. Dans le feuillet FileView du panneau de gauche, faites un clic droit sur Header Files et slectionnez Add Files to Folder. Ajoutez le fichier 'ressource.h' qui doit tre dans le mme rpertoire que votre projet. Crons prsent le modle de notre bote de dialogue. Allez dans le feuillet RessourceView du panneau de travail ( gauche). Faites un clic droit sur res ressources et slectionnez Insert/Dialog/New. Nous ne modifirons pas cette bote de dialogue. Occupons-nous maintenant du code qui va constituer le programme. Tout d'abord, les fichiers d'en-tte, deux sont ncessaires, Windows.h et ressource.h que nous avons inclus prcdemment. Le point d'entre du programme est la fonction WinMain(). Elle est excute automatiquement par Windows lorsque le programme est dmarr. Mais avant cette fonction, nous aurons la dfinition de la procdure de notre fentre principale, MainPoc(). #include <Windows.h> #include "ressource.h"
Page 3 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { Nous allons maintenant crer la bote de dialogue grce la fonction CreateDialog(). Le paramtre HINSTANCE est un identifiant de notre application, on passe l'identifiant fourni par Windows en paramtre de la fonction WinMain(). On donne ensuite l'identifiant ressource de notre bote de dialogue. Elle n'a pas de fentre parent et la procdure est la fonction MainProc(). La fonction retourne une variable de type HWND qui identifie notre fentre. Puis, on affiche cette fentre. Toute fentre cre est invisible par dfaut. HWND hDlg; hDlg=CreateDialog(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc); ShowWindow(hDlg,SW_SHOW); Occupons-nous maintenant de la rception des messages. La rception des messages est prise en charge par la fonction GetMessage(). Cette fonction retourne FALSE ds que le message WM_QUIT est reu, ce qui indique que l'application doit tre ferme. MSG msg; while(GetMessage(&msg,NULL,0,0)==TRUE) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } Il ne reste plus qu' dfinir le comportement de notre fentre dans quelques cas simples: elle doit tre ferme et l'application quitte si l'utilisateur presse 'ok' ou essaie de quitter. La fermeture de la fentre ne signifie pas forcment l'arrt de l'application. Ces deux comportements correspondent deux messages distincs: WM_QUIT indiquie que l'application doit se terminer, WM_CLOSE indique la fermeture de la fentre. Mais dans ce cas, on a une fentre un peu particulire, puisqu'il s'agit d'une bote de dialogue. Sa procdure est dfinie de la mme manire que celle d'une fentre. LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam) { int Select; switch(message) { case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { case IDOK: EndDialog(Dlg,0); PostQuitMessage(0); return TRUE; case IDCANCEL: EndDialog(Dlg,Select); PostQuitMessage(0); Page 4 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php return TRUE; } default: return FALSE; } } Cette procdure est trs simple, elle ne ragit qu' la pression des boutons 'Ok' et 'Cancel' (le bouton ou la "croix"). En rponse l'un ou l'autre de ces vnements, la procdure demande la fermeture de la bote de dialogue et termine l'application en envoyant un message WM_QUIT grce la fonction PostQuitMessage(). 2. Cration d'une fentre Cours thorique : Dans la premire partie de ce chapitre, nous avons vu comment crer une boite de dialogue. La cration d'une boite de dialogue grce la fonction CreateDialog() est relativement simple puisqu'une grande partie du travail est effectu par CreateDialog(). Nous allons donc voir maintenant comment crer une fentre. Cette fentre pourra tre personnalise et ne rferrera aucune resource. Notre programme n'utilisera donc pas de resources. Comme nous avons pu le voir dans la partie prcdente, le programme n'intervient en aucun cas dans le dessin de la fentre (barre de titre, menu...). Le programme n'est responsable que du redessinement de la zone client de la fentre. Comme il existe plusieurs types de fentre (avec un menu, sans menu, pouvant tre minimise ou pas...) il est ncessaire que de crer un 'protoype de fentre'. Ce prototype est aussi appel classe de la fentre. C'est sur ce prototype que Windows se basera pour crer la fentre. Une fois le prototype de fentre cr, nous demanderons Windows de crer une fentre suivant ce prototype. La fentre cre devra elle aussi disposer d'une procdure permettant de traiter les messages qu'elle reoit. Cette procdure est trs similaire celle grant la boite de dialogue de la partie prcdante. Elle ne traite cependant pas exactement les messages de la mme manire. Projet N2 : - Objectif : crer une fentre sans utiliser de resource et une procdure permettant sa gestion. - Ralisation : la fentre sera cre sans utiliser de resource et sa procdure traitera les messages les plus simples. Tlcharger le projet ici. - Le projet pas pas : Il s'agit tout d'abord de crer un projet dans Microsoft Visual C++. Le projet sera cr de la mme manire que dans la partie prcdente, mais sans y ajouter de Ressource Script. Page 5 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Le point d'entre WinMain() est un standard toute application Windows, ce programme dbutera donc de la mme manire que le prcdant. #include <Windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { Il faut mintenant crer une nouvelle classe de fentre. Cette classe est cre au moyen de la fonction RegisterClassEx(). La cration de cette classe paut paratre fastidieuse et comprend un grand nombre de paramtres. nous n'utiliserons pas tous les paramtres, donc ne vous effrayez pas si vous ne comprenez pas totalement l'utilit de chaqun d'eux. WNDCLASSEX principale; principale.cbSize=sizeof(WNDCLASSEX); principale.style=CS_HREDRAW|CS_VREDRAW; principale.lpfnWndProc=MainProc; principale.cbClsExtra=0; principale.cbWndExtra=0; principale.hInstance=hInstance; principale.hIcon=LoadIcon(NULL,IDI_APPLICATION); principale.hCursor=LoadCursor(NULL,IDC_ARROW); principale.hbrBackground=reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); principale.lpszMenuName=NULL; principale.lpszClassName="std"; principale.hIconSm=LoadIcon(NULL,IDI_APPLICATION); RegisterClassEx(&principale); principale.style=CS_HREDRAW|CS_VREDRAW indique que notre fentre devra tre redessine en cas de redimentionnement horizontal ou vertical. Nous indiquons ensuite quelle procdure sera charge de grer la fentre. L'instance de notre application est elle aussi passe en paramtre. Les curseurs ou icones de notre fentre sont ceux par dfaut de Windows. La fentre n'as pas de menu, la couleur de fond est celle de Windows par dfaut. Le nom de la classe de la fentre 'std' est un choix personnel. Il n'a d'importance que pour nous. Une fois la structure remplie, un appel RegisterClassEx() demande Windows de mmoriser la classe. Maintenant que nous disposons de notre classe de fentre, il ne reste plus qu'a la crer grce la fonction CreateWindowEx(). HWND hWnd; hWnd=CreateWindowEx( WS_EX_CLIENTEDGE, "std", "Notre fentre", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, Page 6 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php NULL, hInstance, NULL );
ShowWindow(hWnd,SW_SHOW); La rception des messages se fait exactement de la mme manire que dans le projet prcdent. MSG msg; while(GetMessage(&msg,NULL,0,0)==TRUE) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } La dfinition de la procdure de cette fentre est la mme que dans le programme prcdent. Mais son fonctionnement interne est diffrent. Tout d'abord, cette fentre ne contient pas de bouton. Sa fermeture se fera lors d'un clic sur la 'croix'. Les messages non traits sont passs DefWindowProc(). Les messages traits seront WM_PAINT et WM_DESTROY. WM_PAINT indique que nous devons redessiner la zone client. Ici le traitement de ce message ne fera rien et pourrait tre ignor. Il est inclu dans le seul but de comprendre le mcanisme de redessinement d'une fentre. Le message WM_DESTROY indique que la fentre est en train d'tre dtruite (probablement suite au clic sur la 'croix'). Le fermeture de la fentre ne signifie pas forcment l'arret de l'application. Mais comme dans ce cas notre application n'est forme que d'une fentre, nous demanderons terminer l'application dans ce cas en postant un message WM_QUIT. LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT paintst; switch (mes) { case WM_PAINT: hDC=BeginPaint(hWnd,&paintst); EndPaint(hWnd,&paintst); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hWnd, mes, wParam, lParam); } } Le traitement du message WM_PAINT peut vous paraitre trange. En effet, pourquoi effectuer un BeginPaint() et un EndPaint() alors que nous ne dessinons rien. La raison est trs simple. A partir du moment ou nous dcidons de traiter ce message, nous devons assurer un traitement minimum par la suite de ces deux fonction, mme si rien n'est dessin. 3. Affichage dans une fentre Page 7 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Maintenant que nous savons crer une fentre, nous allons voir comment dessiner dans sa zone client. L'affichage dans la zone client d'une fentre se fait au niveau du message WM_PAINT. Pour provoquer l'envoi d'un tel message, il faut demander le redessinement d'une zone de la fentre. Lors du dessin dans la partie WM_PAINT, il sera impossible d'afficher hors de la zone de redessinement. Pour dessiner dans une fentre, nous allons utiliser un contexte d'affichage. Il est propre la fentre dans laquelle nous dessinons et nous est fourni par Windows. Il identifie en fait la zone de l'cran dans laquelle nous allons dessiner. Projet N3 : - Objectif : crer une fentre et afficher l'heure. - Ralisation : la cration de la fentre se fera de manire identique au projet prcdent. Seule sera ajoute la partie permettant l'affichage de l'heure courante. Tlcharger le projet ici. - Le projet pas pas : La cration de la fentre se fait exactement de la mme manire que dans le projet prcdent. Nous allons simplement utiliser les fonctions contenues dans stdio.h. #include <Windows.h> #include <stdio.h>
HWND hWnd; hWnd=CreateWindowEx( WS_EX_CLIENTEDGE, Page 8 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php "std", "Notre fentre", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); ShowWindow(hWnd,SW_SHOW); Comme nous allons afficher l'heure, il est important que nous soyons prvenus de manire rgulire. Nous allons demander Windows de nous avertir toutes les secondes, de manire afficher l'heure rgulirement. Notre fentre recevra un message WM_TIMER toutes les secondes. SetTimer(hWnd,NULL,1000,NULL); La rception des messages se fait exactement de la mme manire que dans le projet prcdent. MSG msg; while(GetMessage(&msg,NULL,0,0)==TRUE) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } La procdure fonctionne de manire identique que prcdemment. Nous allons ajouter le traitement du message WM_TIMER qui sera recu toutes les secondes. Ce message demandera le redessinement d'une zone de l'cran (la zone ou nous afficherons l'heure). LRESULT CALLBACK MainProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam) { HDC hDC; PAINTSTRUCT paintst; RECT rcClient; switch (mes) { case WM_TIMER: rcClient.top=0; rcClient.left=0; rcClient.right=100; rcClient.bottom=50; RedrawWindow(hWnd,&rcClient,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_ERASE NOW|RDW_NOCHILDREN); return 0; Un message WM_PAINT sera donc reu toutes les secondes et chaque fois que la fentre doit tre redessine. La fentre doit tre redessine si elle est couverte puis dcouverte par une autre fentre (par exemple). Nous allons afficher l'heure en utilisant une police spcifique. Une fois cette police cre, il est ncessaire d'indiquer que nous allons l'utiliser dans notre contexte d'affichage (HDC). Le contexte d'affichage (ou Device Context) rfre la zone client de notre fentre et contient tous ses paramtres graphiques (disposition sur l'cran, nombre de couleurs affichables...). Il est identifi par Page 9 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php la variable hDC qui nous est passe par Windows au travers de la fonction BeginPaint(). Une fois la police cre et le texte dessin, il convient de dtruire la police. L'heure courante sera rcupre grace la fonction GetLocalTime(). case WM_PAINT: char buf[256]; SYSTEMTIME CurrentTime; HFONT hFont; hFont=CreateFont (20,0,0,0,700,FALSE,FALSE,FALSE,0,OUT_DEFAULT_PRECIS,CLIP_ DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH|FF_DONTCARE,"Comic Sans MS"); hDC=BeginPaint(hWnd,&paintst); SelectObject(hDC,hFont); GetLocalTime(&CurrentTime); sprintf(buf,"%d : %d : % d",CurrentTime.wHour,CurrentTime.wMinute,CurrentTi me.wSecond); TextOut(hDC,0,0,buf,strlen(buf)); EndPaint(hWnd,&paintst); DeleteObject(hFont); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hWnd, mes, wParam, lParam); } } Il est essentiel de bien comprendre le principe d'affichage dans une fentre car il est trs utilis par Windows. Quelque soit le cas, l'affichage se fait toujours entre deux fonctions qui indiquent le dbut et la fin de l'affichage. Il ne faut jamais oublier de librer le contexte d'affichage aprs l'avoir utilis. Ici, la libration du contexte d'affichage se fait par la fonction EndPaint(). 4. Lecture dans un fichier et bote de dialogue Cours thorique : Nous allons maintenant revenir sur un programme utilisant des ressources. En effet, l'utilisation de boites de dialogues cres en ressources permettent bien souvent de gagner un temps prcieux pour des programmes de petites tailles et de faible complexit. C'est le cas du programme que nous allons raliser ici. Nous allons donc aborder deux thmes principaux: les boites de dialogues et les controles prdfinis, ainsi que la lecture d'un fichier par des fonctions de l'API Windows, donc sans utiliser les fonctions du C traditionnel. Les controles prdfinis sont les 'edit boxes', 'combo boxes', 'buttons', etc. Leur utilisation est trs simple car ils sont grs de manire entirement autonome. Ils envoient la fentre qui les a crs des messages de notification pour indiquer les actions de l'utilisateur: clic sur le boutton, saisie d'un caractre... La lecture du fichier se fait de manire relativement simple, et fonctionne par un systeme de Page 10 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php HANDLE (comme beaucoup de fonctions de l'API Windows) qu'il faut crer puis dtruire. Projet N4 : - Objectif : crer une boite de dialogue permettant la saisie d'un fichier et affichant les 500 premiers octets de ce fichier. - Ralisation : nous allons ici utiliser une ressource pour crer la boite de dialogue. La lecture du fichier se fera au niveau de la procdure de la boite de dialogue car elle est trs brve. Nous utiliserons ici une fonction qui permet de crer la boite de dialogue et de relever les messages, la partie 'main' du programme sera donc trs courte (2 lignes). Tlcharger le projet ici. - Le projet pas pas : Nous allons crer un programme avec un fichier de ressources, inclure les fichiers correspondants (voir parties prcdentes). La fonction 'WinMain()' est excessivement simple puisque la fonction DialogBox() s'occupe de crer la bote de dialogue et de rcuprer ses messages. Cette fonction retourne seulement une fois que la boite de dialogue est ferme. #include <Windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc); return 0; } La procdure de la bote de dialogue est trs simple, elle ne gre que trois messages provenant de trois bouttons. IDOK est envoy lors d'un clic sur le boutton OK, IDCANCEL est envoy lors d'un clic sur la 'croix' et IDC_LIRE est un message envoy lors d'un clic sur le boutton IDC_LIRE que nous avons cr. Les variables cres en dbut de procdure serviront la lecture du fichier que nous verrons aprs. LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam) { int Select; char buf[501]; HANDLE hFile; DWORD Read; switch(message) { case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { case IDC_LIRE: Page 11 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php La relle nouveaut de ce programme va se faire ici. Il s'agit tout d'abord de rcuprer le nom de fichier entr par l'utilisateur. Comme il est entr dans un contrle prdfini, nous allons utiliser la fonction GetDlgItemText() et nous plaerons ce nom de fichier dans le buffer cr au debut de cette procdure sous le nom de 'buf'. Une fois ce nom de fichier rcupr, nous allons essayer d'ouvrir le fichier donn en lecture. Si nous n'y parvenons pas, un message d'erreur sera affich grce la fonction 'MessageBox()'. Le traitement du message sera alors arrt. Remarque: la fonction MessageBox() ne retourne qu'une fois que la bote de dialogue cre est ferme. Mais la rcupration des messages de notre bote de dialogue est assure par MessageBox(). Le traitement prolong de ce message n'est donc pas gnant dans ce cas puisque les messages continueront d'arriver notre procdure. GetDlgItemText(Dlg,IDC_FILENAME,buf,256); hFile=CreateFile (buf,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FI LE_ATTRIBUTE_NORMAL,NULL); if(hFile==INVALID_HANDLE_VALUE) { MessageBox (Dlg,"Erreur, impossible d'ouvrir le fichier spcifi.","E rreur",MB_OK); return 0; } Arrivs ce stade nous savons que le fichier saisi est valide et qu'il a pu tre ouvert. Il ne reste plus qu' lire les 500 premiers octets. Ils seront placs dans le buffer. Comme cette chane vas tre afiche, elle doit tre termine par un caractre NULL. C'est pour cette raison que 'buf' a t dfini comme un tableau de 501 caractres (500 pour la lecture et 1 pour le caractre NULL). Comme la lecture peut faire moins de 500 octets, nous nous baserons sur la valeur retourne par ReadFile() pour ajouter le caractre NULL. Puis nous afficherons ce buffer dans l'edit box cr a cet effet grace SetDlgItemText(). ReadFile(hFile,buf,500,&Read,NULL); CloseHandle(hFile); buf[Read]=''; SetDlgItemText(Dlg,IDC_TEXT,buf); return 0; Le reste du traitement de la procdure ne prsente pas de difficult particulire. Il est similaire aux traitements prcdents. La seule diffrence notalbe est lors de la fermeture de la bote de dialogue. Comme elle a t cre avec la fonction DialogBox() il ne faut pas utiliser PostQuitMesage () pour la quitter mais EndDialog(). case IDOK: EndDialog(Dlg,0); return TRUE; case IDCANCEL: EndDialog(Dlg,0); return TRUE; } default: return FALSE; } } Page 12 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Ce programme est relativement simple. La gestion de l'interface graphique ne prend que quelques lignes. Nous voyons donc bien l'intrt de l'utilisation des ressources et donc contrles prdfinis. Sans l'aide des ressources, ce programme aurait t beaucoup plus compliqu raliser. 5. Pas pas pour C++ Builder Nous allons reprendre la 4me partie : "Lecture dans un fichier et bote de dialogue." mais adapte C++ Builder. Deux procdures sont traites dans ce document une pour BCB6 et une pour BCB4. Pour les projets sans bote de dialogue la procdure est la mme sauf qu'il n'y a pas besoin de rajouter de fichiers ressources. BCB 6 Faire "Nouveau". (appelle la bote de dialogue Nouveaux lments) Sur la bote de dialogue "Expert console" : Sur "Type de source" slectionner "C" ou "C++". (Pour l'exemple cela n'a pas d'importance) Aucune des CheckBox droite ne doit tre coche. Clicker sur le bouton "Ok". Maintenant nous avons le code minimum pour ce type d'application: #include <windows.h> #pragma hdrstop
//--------------------------------------------------------------------------- #pragma argsused WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow) { return 0;} Enregister le projet sous le nom choisi. Ici "main.cpp" et "main.bpr". (Faire "Fichier" puis "Tout enregistrer") J'ai utilis "Resource WorkShop" (livr avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel diteur de ressource. Puis il faut ajouter le fichier ressource au projet: Sur le menu faire "Projet" puis "Ajouter au projet" Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)" Puis slectionner le fichier ici "Dialog1.rc" Puis faire Ouvrir Le code source est identique a l'exemple Visual C++ (J'ai mis les mmes noms d'identificateur) Page 13 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Les source des fichiers "Dialog1.rc" et "ressource.h" sont a la fin de ce document. Fichier "main.cpp": #include <windows.h> #pragma hdrstop //Pas obligatoire #include "ressource.h"
Page 14 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php case IDOK: EndDialog(Dlg,0); return TRUE; case IDCANCEL: EndDialog(Dlg,0); return TRUE; } default: return FALSE; } } BCB 4 Faire "Nouveau". (appelle la bote de dialogue Nouveaux lments) Sur la bote de dialogue "Nouveaux lments" Onglet "Nouveau" DbClk sur l'icne "Expert console". (appelle la bote de dialogue "Expert d'application console") Sur la bote de dialogue "Expert d'application console" : Sur "Type de fentre" slectionner "Windows (GUI)". Sur "Type d'excution" slectionner "EXE". Clicker sur le bouton "Terminer". Maintenant nous avons le code minimum pour ce type d'application: #include <windows.h> #pragma hdrstop #include <condefs.h>
//--------------------------------------------------------------------------- #pragma argsused WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return 0; } Enregister le projet sous le nom choisi. Ici "main.cpp". (Faire "Fichier" puis "Tout enregistrer") J'ai utilis "Resource WorkShop" (livr avec BCB) pour faire le fichier Dialog1.rc mais il peut se faire avec n'importe quel diteur de ressource. Puis il faut ajouter le fichier ressource au projet: Sur le menu faire "Projet" puis "Ajouter au projet" Selectionner le type de fichier voulu ici "Fichier ressource (*.rc)" Puis slectionner le fichier ici "Dialog1.rc" Puis faire Ouvrir Cela va ajouter cette ligne dans le code : USERC("Dialog1.rc"); Le reste du code est identique a l'exemple Visual C++ (J'ai mis les mmes noms Page 15 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php d'identificateur) Fichier "main.cpp": #include <windows.h> #pragma hdrstop #include <condefs.h> #include "ressource.h"
#define IDC_FILENAME 101 #define IDC_TEXT 102 #define IDC_LIRE 103 6. Lecture d'un fichier WAV Cours thorique : A prsent, nous allons raliser un nouveau projet qui sera dj un programme 'utile' en soi. Evidemment, il restera trs sommaire et se contentera de lire un fichier WAV slectionn par l'utilisateur. Nous inviterons tout d'abord l'utilisateur choisir le fichier qu'il veut lire partir d'une bote de dialogue 'parcourir'. Une fois que l'utilisateur aura choisi le fichier qu'il dsire lire, il pourra choisir de l'couter. La lecture du fichier WAV se fera grce une fonction multimdia fournie par Windows: sndPlaySound(). Cette fonction est entirement autonome et lira le fichier en arrire plan sans la moindre intervention de la part du programme. Cet automatisme est trs pratique puisqu'il permet de lire le fichier sans se soucier de rien. Cependant, les possibilits de cette fonction restent Page 17 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php restreintes. On pourra se satisfaire de cette fonction dans quasiment toutes les applications multimdias. Projet N5 : - Objectif : crer une boite de dialogue permettant la saisie d'un fichier WAV puis sa lecture. - Ralisation : nous allons utiliser un fichier ressources qui sera ici trs adapt. Le programme ne contiendra que la fonction WinMain() et une procdure pour la boite de dialogue. Tlcharger le projet ici. - Le projet pas pas : Comme dans le programme prcdent, nous allons crer un projet d'application Win32, ainsi qu'un fichier de ressources. La fonction WinMain() contiendra simplement l'appel de notre bote de dialogue. Remarquons tout de mme que la fonction sndPlaySound() que nous allons utiliser se trouve dans la librairie 'winmm.lib' qui n'est pas une librairie standard. Il faut donc ajouter cette librairie. Ceci peut tre fait grce au menu Project/Setting/Link/Input. Dans le champ contenant dj les librairies par dfaut, ajoutez 'winmm.lib'. Sans cet ajout, nous obtiendrions une erreur la compilation indiquant que la fonction ne peut pas tre trouve. La bote de dialogue contiendra un champ 'Edit Box' (IDC_NOMDEFICHIER)dans lequel sera affich le nom du fichier lire. Nous cocherons la case 'Read Only' dans les options de ce champ de manire empcher l'utilisateur de saisir lui mme un nom de fichier. De cette manire, la possibilit de choix errons se trouve dj limite. Nous ajouterons un bouton 'Parcourir' avec pour identifiant 'IDC_PARCOURIR' et un bouton 'Lire' (IDC_LIRE). Pour quitter, l'utilisateur devra utiliser la 'croix' (ID_CANCEL). La fonction WinMain() ne diffre pas par rapport au programme prcdent (si ce n'est dans les enttes inclure). Nous ajouterons une variable globale de manire pouvoir accder l'instance du programme dans toutes les fonctions du programme (et ici dans la procdure de la bote de dialogue). #include <Windows.h> #include <mmsystem.h> #include "ressource.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { hInst=hInstance; DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc); return 0; Page 18 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php } La procdure de la bote de dialogue n'est pas trs complique. Il faudra toutefois y ajouter les boutons 'Lire' et 'Parcourir'. Le bouton parcourir sera le plus difficile ajouter. Nous allons rserver un buffer de taille MAX_PATH (qui est une constante marquant la taille maximale d'un nom de fichier), ainsi qu'une structure OPENFILENAME qui nous servira pour la bote de dialogue 'Parcourir'. LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM lParam) { int Select; char buf[MAX_PATH]; OPENFILENAME DlgInfs;
switch(message) { case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { case IDC_PARCOURIR: La fonction qui affiche la bote de dialogue 'Parcourir' est GetOpenfileName(). De manire personnaliser cette bote de dialogue nos besoins, nous passons une structure contenant les caractristiques de la bote de dialogue. Comme nous n'utiliserons pas une grande partie des membres de cette structure, nous allons tous les mettre 0 grce la fonction memset() du C standard. Ceci constitue une bonne habitude prendre. Pour plus d'informations sur les paramtres et les possibilits de cette fonction rfrez vous l'aide de fournie par Microsoft. memset(&DlgInfs,0,sizeof(OPENFILENAME));
DlgInfs.lStructSize=sizeof(OPENFILENAME); DlgInfs.hwndOwner=Dlg; DlgInfs.hInstance=hInst; DlgInfs.lpstrFilter="Fichiers WAV*.WAV"; DlgInfs.lpstrFile=buf; DlgInfs.nMaxFile=MAX_PATH; DlgInfs.lpstrTitle="Choisissez le fichier lire."; DlgInfs.Flags=OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST; if(GetOpenFileName(&DlgInfs)) SetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf); return TRUE; Lorsque l'utilisateur pressera le bouton 'Lire', il faudra rcuprer le nom de fichier saisi dans le champ 'IDC_NOMDEFICHIER' et le lire grce la fonction sndPlaySound(). case IDC_LIRE: GetDlgItemText(Dlg,IDC_NOMDEFICHIER,buf,MAX_PATH);
sndPlaySound(buf,SND_ASYNC); return TRUE; Il ne reste plus qu' grer la sortie de la bote de dialogue. Dans le cas ou l'utilisateur quitte le programme, il faut stopper la lecture du son en cours. Pour cela on utilisera encore la fonction sndPlaySound() avant de quitter. case IDCANCEL: Page 19 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php sndPlaySound(NULL,NULL); EndDialog(Dlg,0); return TRUE; } default: return FALSE; } } Ce programme ne prsente aucune difficult particulire. Il se suffit lui mme bien qu'tant particulirement pauvre. Ceci permet de donner une ide de la gestion des botes de dialogues qui seront dans le futur intgres un programme bien plus important. Bien que la gestion de telles botes de dialogue constitue un dtail dans un programme plus important, il est ncessaire de bien comprendre leur fonctionnement de manire ne pas perdre de temps et pouvoir crer rapidement une interface graphique agrable. 7. Cration d'une application console Cours thorique : Avant de s'intresser aux applications consoles, il convient de bien comprendre de quoi il s'agit. Une application console n'est pas un programme DOS. C'est un programme Windows utilisant les fonctions de l'API Windows mais qui s'affiche dans une console DOS au lieu d'une fentre classique. Un tel programme ne peut pas tre dmarr en mode DOS rel. L'avantage de ces programmes est qu'il est inutile de se proccuper de l'affichage de la fentre puisque toutes les interventions sur la fentre sont prises en compte par le systme. Ce type d'application est trs utile pour de petits utilitaires fonctionnant en ligne de commande (bien qu'ils paraissent austres!). Cependant, ces programmes fonctionnent de la mme manire que les applications DOS (pour ce qui est de l'entre sortie sur l'cran). On pourra donc utiliser les fonctions printf() et scanf() du C standard pour afficher des donnes l'cran. De mme les fonctions de flux standard C++ cin et cout sont utilisables. Cette partie ne fera pas l'objet d'un projet, mais les applications console seront utilises plus tard pour illustrer des programmes simples et ne ncessitant pas ou peu de communications avec l'utilisateur, de manire rduire la taille des programmes. 8. Fentre classique ou ressource ? Cours thorique : Maintenant que nous avons cr et utilis ces deux types de fentres, il convient de s'interroger sur leur utilisation. Faut il prfrer les ressources ou plutt les fentres classiques ? Quels sont les avantages des une et des autres ? Page 20 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Les fentres cres partir de ressources (souvent les botes de dialogue) sont videmment bien plus rapides crer. De plus leur utilisation ncessite beaucoup moins de code. Dans ce cas, pourquoi continuer utiliser des fentres classiques cres avec l'API Windows ? Si les ressources peuvent sduire, il faut cependant s'en mfier. Elles conviennent trs bien pour des botes de dialogue invitant l'utilisateur saisir des valeurs, ou dfinir certains paramtres. C'est d'ailleurs leur rle principal. En les utilisant dans ce contexte on gagne un temps trs important par rapport des fentres cres 'manuellement'. Cependant, l'affichage dans une bote de dialogue ne se prte que peu la personnalisation. Pour afficher des donnes comme des images, des polices personnalises ou autre, il reste prfrable d'utiliser une fentre classique. De mme si la fentre doit grer des interventions utilisateur comme une touche entre, un clic, il faut alors passer une fentre classique. Dans ces cas, la gestion automatise devient en fait un obstacle l'interception de ces vnements. Avant de se lancer dans la programmation d'une fentre classique ou au contraire de fentres ressources, il faut donc rflchir l'utilisation qui sera faite de ces fentres. Ni l'une ni l'autre de ces mthodes ne sont bannir. Au contraire, il faut connatre les deux et savoir utiliser l'une ou l'autre ds qu'il le faut. La facilit des ressources ne doit pas en faire un rflexe systmatique. Bien entendu on ne peut pas tablir des caractristiques types pour lesquelles il faut absolument utiliser tel ou tel type de fentre. Cette dcision doit prendre en compte les exigences du programme... Je ne me risquerais pas dresser une liste exhaustive de tous les cas ou l'utilisation de telle ou telle mthode est prfrable mais j'invite seulement le programmeur rflchir avant d'utiliser les ressources. Chapitre 2 Les botes de dialogue 1. Fonctionnement gnral Cours thorique : Avant de se lancer dans la programmation d'applications plus complexes, il est ncessaire de bien comprendre le schma de fonctionnement de la bote de dialogue. Il se rapproche du fonctionnement d'une fentre classique sans pour autant tre le mme. Il conviendra donc d'viter les analogies entre ces deux types de fentres. La procdure qui gre une bote de dialogue peut tre excessivement simple. Pour toute notification, Windows appelle la procdure et lui demande de traiter le message. Si elle est en mesure de le traiter, elle doit retourner TRUE. Dans le cas contraire elle retourne FALSE et Windows effectuera le traitement par dfaut pour le message. Le minimum est donc d'assurer la fermeture de la boite de dialogue grce la fonction EndDialog(). En effet, la fermeture de la boite Page 21 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php de dialogue ne fait pas partie du traitement par dfaut des messages, ce qui est logique puisqu'il est impossible Windows de 'deviner' le bouton qui doit quitter la bote de dialogue. Ds qu'une boite de dialogue traite un message, elle doit donc retourner TRUE (sauf indication contraire). Remarquez la structure du switch avec le default qui retourne FALSE. Ds qu'un message n'est pas trait, la procdure retourne donc FALSE. On peut considrer le fonctionnement d'une bote de dialogue en trois temps. Tout d'abord l'initialisation. C'est ce moment que les diffrents contrles sont initialiss. Par exemple, le statut des 'check boxes' sera dfini. Cette phase est gnralement indispensable, mais dans certains cas, une bote de dialogue n'aura besoin d'aucune valeur par dfaut. L'initialisation se fait avant que la boite de dialogue ne soit affiche, grce un message (WM_INITDIALOG). Si ce message n'est pas trait, c'est dire si la procdure ne contient pas de 'case WM_INITDIALOG', aucun champ n'aura de valeur particulire. La deuxime phase de fonctionnement d'une boite de dialogue est celle durant laquelle l'utilisateur effectue les modifications qu'il dsire. Il modifie des champs, 'coche' des options... Ce traitement peut se faire de manire entirement autonome si aucune action particulire n'est ncessaire. Cette phase ne se refltera donc pas forcment dans la procdure. Dans certains cas cette phase sera inutile, par exemple pour un message d'erreur. Une fois que l'utilisateur a fait les modifications qu'il dsire, il va falloir les 'rcuprer'. Le statut de tous les contrles doit tre analys de manire modifier les variables au sein mme du programme. En effet, les modifications effectues par l'utilisateur ne se refltent en rien au niveau des variables du programme. Une fois que le programme mis jour toutes les variables ncessaires, la boite de dialogue peut se terminer. 2. Initialisation Cours thorique : L'initialisation d'une bote de dialogue se fait grce au message WM_INITDIALOG. Si une bote de dialogue ne dsire pas effectuer de traitement particulier sa cration, elle ignore simplement ce message. Lorsque ce message est envoy la boite de dialogue, celle-ci n'est pas encore affich. C'est ce moment que les valeurs par dfaut des contrles sont dfinies. Ces valeurs seront dfinies grce des fonctions de l'API Windows. De plus, comme la fentre n'est pas encore visible, il est possible de modifier sa taille, sa position, sans que cela apparaisse l'utilisateur (fonction SetWindowPos() par exemple). La valeur retourne aprs le traitement du message WM_INITDIALOG dtermine si le champ par dfaut aura ou non le focus. Le focus est en fait l'entre clavier. Si un champ possde le focus et qu'une touche est tape au clavier, c'est ce champ et lui seul que cela sera signal. Dans un champ demandant un mot de passe (par exemple), il est trs utile de passer la focus au champ par dfaut, de manire viter l'utilisateur de devoir cliquer dans le champ avant de saisir sont mot de passe. Pour Page 22 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php que Windows donne le focus au champ par dfaut, la procdure doit retourner TRUE. Dans le cas contraire, elle doit retourner FALSE. 3. Le contrle 'Edit' Cours thorique : Ce contrle dj t vu dans les projets prcdents. C'est un des plus simples utiliser. Comme pour tous les contrles, on peut distinguer deux aspects l'utilisation de celui-ci. Le premier aspect est son apparence. Elle est modifie grce l'diteur de ressources du compilateur. Si vous utilisez Visual C++, il vous suffit de double cliquer sur le contrle pour accder ses proprits. La deuxime partie du fonctionnement de ce contrle se fait au travers de la procdure qui gre la bote de dialogue. Elle consiste en la dfinition ou la rcupration de l'tat du contrle (ici le texte qu'il contient). Avant d'utiliser le contrle, il faut donc dfinir son apparence, ainsi qu'un identifiant, permettant au programme de l'identifier. L'identifiant d'un contrle est gnralement de la forme 'IDC_' suivi du nom du contrle tout en majuscules. Le compilateur se charge de maintenir jour un fichier dfinissant toutes les constantes utilises. Pour Visual C++, ce fichier est 'ressource.h'. Il est possible de personnaliser de nombreuses caractristiques pour un contrle de type 'Edit'. Voici une liste regroupant les styles les plus utiliss. Cette liste n'est pas exhaustive, rfrez vous l'annexe A pour plus de prcisions. Si vous utilisez un diteur de ressources, vous devriez pouvoir modifier les styles grce une interface graphique. Si ditez vos ressources en mode texte, les styles sont mis entre crochets. Read Only [ES_READONLY] : Afficher le contrle sur fond gris. Il est impossible de modifier le texte, mais il peut toutefois tre slectionn et copi. Disabled : Le contrle est dsactiv. Il apparat en gris et il est impossible de slectionner le texte. Cette option ne sera accessible qu' partir d'un diteur de ressources car elle ne constitue pas un style. Pour dsactiver un contrle, utilisez la fonction EnableWndow() lors de l'initialisation de la bote de dialogue. Number [ES_NUMBER] : Indique que l'on ne peut saisir que des chiffres dans ce contrle. Le point n'est pas accept. Pour entrer un nombre virgule, il faudra donc utiliser un contrle classique. Password [ES_PASSWORD] : Affiche des toiles la place des caractres saisis. Le texte ne peut pas tre copi. Multiline [ES_MULTILINE] : Indique que le contrle peut contenir plusieurs lignes. Pour effectuer un retour la ligne, il faut afficher le caractre '\r\n'. Au clavier, il faut taper CTRL+ENTER. AutoHScroll [ES_AUTOHSCROLL] - AutoVScroll [ES_AUTOVSCROLL] : Indique si le contrle doit dfiler horizontalement ou verticalement. Pour permettre un contrle de dfiler Page 23 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php verticalement il faut dsactiver le dfilement horizontal. Une fois que l'apparence du contrle est convenable, il faut pouvoir lui assigner une valeur ou rcuprer la valeur entre par l'utilisateur. Ces manipulations se font au travers de 4 fonctions : SetDlgItemInt() : permet de dfinir la valeur d'un contrle grce un entier. Il est possible d'indiquer si cet entier est sign ou non. L'identifiant du contrle est celui dfinit dans les ressources (IDC_... en gnral). GetDlgItemInt() : permet de rcuprer la valeur numrique d'un contrle. Cette fonction chouera si le contrle contenait du texte. SetDlgItemText() : permet d'afficher une chane de caractres dans le contrle. Cette chane de caractres peut priori avoir n'importe quelle longueur. GetDlgItemText() : permet de rcuprer la chane de caractres contenue dans le contrle. Il est indispensable de prciser la longueur maximale de la chane qui sera rcupre (elle correspond la taille du buffer dans lequel elle sera stocke). 4. Le contrle 'Check Box' Cours thorique : Le contrle 'Check Box' fait lui aussi partie de l'un des plus simples. Il peut contenir trois tats, coch, non coch ou indtermin. On peut lui attribuer diffrents styles qui modifient son apparence et parfois son fonctionnement. Par dfaut, ce contrle ne peut tre que dans deux tats, coch ou non coch. En modifiant le style on peut permettre l'utilisateur de spcifier un troisime tat, indtermin, qui correspond une case coche mais grise. Voici une liste des styles principaux pouvant tre appliqus ce contrle (les styles modifiant l'apparence graphique du contrle ne sont pas traits ici). Pour une liste exhaustive des diffrents types consultez l'annexe A. Tri-state [BS_3STATE] [BS_AUTO3STATE] : le contrle peut prendre 3 tats au lieu de deux. Auto : [BS_AUTO3STATE] [BS_AUTOCHECKBOX] : permet de crer un contrle dont l'tat est modifi automatiquement lorsque l'utilisateur l'utilise. Si ce style n'est pas activ, la bote de dialogue reoit un message WM_COMMAND et doit elle-mme modifier l'tat du contrle. Comme on pouvait s'y attendre, il suffit de deux fonctions pour le manipuler, une pour modifier son tat, et l'autre pour rcuprer son tat. CheckDlgButton() : permet de modifier l'tat du contrle. Il suffit de spcifier la boite de dialogue qui le contient ainsi que son identifiant et un paramtre commandant l'tat du contrle. Les trois tats possibles sont BST_CHECKED, BST_INDETERMINATE et BST_UNCHECKED. Le contrle sera redessin automatiquement, c'est dire que lorsque la fonction retournera, l'tat graphique du contrle aura t modifi. IsDlgButtonChecked() : permet de rcuprer l'tat du contrle. Cette fonction retourne un Page 24 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php entier non sign qui peut prendre une des trois valeurs prsentes ci-dessus. L'utilisation de contrles 'Check box' se rsume donc peu de choses. Elle pourra tre mise en parallle avec les contrles 'Radio buttons', qui fonctionnement de manire similaire, mais avec un systme de groupes. 5. Le contrle 'Radio Button' Cours thorique : Le contrle 'Radio Button' est assez similaire au contrle 'Check Box', si ce n'est qu'un seul de ces contrles peut tre coch la fois. Il se prsente gnralement sous forme d'un rond cocher. Bien entendu, vous pouvez utilisez plusieurs de ces contrles dans un mme fentre et crer des groupes, de sorte qu'un seul des contrle par groupe ne puisse tre slectionn, et non pas un seul pour l'ensemble de la fentre. Si vous dsirez crer seulement un groupe dans une fentre, il est inutile de crer un groupe. En effet, par dfaut, un seul contrle peut tre slectionn dans l'ensemble de la fentre. Si vous utilisez l'diteur de ressources de Visual, la cration de groupes est simplifie. Pour crer un groupe de radio buttons, il faut en insrer un, et lui mettre le style 'Group'. Puis on insre les autres radios du groupe mais sans mettre le style 'Group'. Pour crer un nouveau groupe, il suffit de refaire la mme manipulation autant de fois que ncessaire. Si vous ditez vos ressources en mode texte, le principe est le mme. On met le style WS_GROUP au premier des radios puis on insre les autres la suite. L'ensemble des styles est dcrit dans l'annexe A. La plupart modifient seulement l'apparence graphique du contrle et ne sont pas trs difficiles utiliser. Retenez simplement un style assez important : Auto [BS_AUTORADIOBUTTON] : ce contrle indique que les radios sont grs automatiquement. Vous n'avez pas demander de dcocher les radios lorsqu'un autre est coch. Gnralement, ce style est toujours utilis, sauf cas trs particuliers. Pour cocher ou dcocher les radios, il faut utiliser les fonctions ChekDlgButton() et IsDlgButtonChecked(). Ces fonctions sont identiques celles utilises avec les contrles 'Check Box'. Bien entendu, le style indtermin n'est pas disponible ici. Remarquons que lorsque vous utilisez CheckDlgButton(), les autres radios ne sont pas dcochs, mme en utilisant le style Auto. Enfin, vous pouvez utilisez la fonction CheckRadioButton() pour cocher un radio et dcochez les autres. Pour cela, les radios doivent avoir ts crs dans l'ordre (les identifiants doivent tre en ordre croissant). Il suffit ensuite de passer l'identifiant du premier radio, celui du dernier, ainsi que le radio slectionner. 6. Le contrle 'List Box' Page 25 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Le contrle 'List Box' est plus complexe que ceux tudis prcdemment. Il ne permet pas seulement de contenir un tat entr par l'utilisateur, il permet d'afficher des informations sous forme de liste, de trier la liste par ordre alphabtique Ce contrle aura donc de multiples utilisations, d'autant plus qu'il est relativement simple utiliser. Il ne se manipule pas grce des fonctions mais au travers de messages, qui seront envoys par l'intermdiaire de la fonction SendMessage(). La liste peut tre prsente sous deux formes : soit en une seule colonne et avec un dfilement vertical, soit sur plusieurs colonnes et avec un dfilement horizontal. Les colonnes auront obligatoirement la mme taille. Elles ne peuvent pas comporter d'enttes. Voici les styles applicables ce contrle. Pour une liste exhaustive, consultez l'annexe A. Remarquez que par dfaut, c'est la slection simple qui est utilise. Sort [LBS_SORT] : dtermine si le contrle trie ou non la liste par ordre alphabtique. Multi-Column [LBS_MULTICOLUMN] : permet d'afficher la liste sur une ou plusieurs colonnes. Si ce style est activ, il faut utiliser un dfilement horizontal. Horizontal Scroll [WS_HSCROLL] : dfilement horizontal de la liste. Vertical Scroll [WS_VSCROLL] : dfilement vertical de la liste. Multiple Selection [LBS_MULTIPLESEL] : autorise la slection multiple. L'utilisateur peut slectionner les lments de la liste en cliquant dessus. Extended Selection [LBS_EXTENDEDSEL] : autorise la slection multiple. L'utilisateur slectionne les lments de la liste grce aux touches SHIFT et CTRL. No Selection [LBS_NOSEL] : interdit toute sorte de slection. L'utilisation du contrle est relativement simple. Nous ne verrons ici que les tches lmentaires, ajout et suppression d'un lment, rcupration de la slection courante. La mthode de rcupration de la slection courante dpend bien entendu du mode de slection choisi. Pour une slection unique, cette action est simple. Elle s'avre un peu plus dlicate pour un contrle slections multiples. L'ensemble des messages utilisables avec ce contrle est prsent dans l'annexe A. Voici les messages les plus simples : LB_ADDSTRING : ajoute un lment la liste et demande le tri de la liste si celle-ci le style 'Sort'. LB_INSERTSTRING : ajoute un lment la liste et le place une position dtermine. Aprs l'insertion, la liste n'est pas trie. LB_SETITEMDATA : permet d'associer une valeur 32 bits un lment de la liste. Cette valeur peut par exemple tre un pointeur sur les donnes que reprsente cet lment. LB_DELETESTRING : supprime l'lment dsign. LB_GETCURSEL : retourne la slection courante pour un contrle slection unique. Page 26 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php LB_GETSELITEMS : place dans un tableau l'ensemble des lments slectionns dans un contrle slections multiples. LB_SETCURSEL : modifie la slection courante dans un contrle slection unique. LB_GETCOUNT : retourne le nombre total d'lments contenus par le contrle. 7. Le contrle 'Progress bar' Cours thorique : Le contrle 'Progress Bar' est l'un des plus simples utiliser. Ses styles tant dfinis au niveau ressources, l'utilisation se borne la dfinition de la position. Pour cela, il faut tout d'abord donner 2 valeurs numriques qui constituent le minimum et le maximum de la barre de progression. Une fois cela fait, il ne reste plus qu' fournir une position courante. Avant de pouvoir utiliser ce contrle, il faut penser l'initialiser. On doit placer en dbut de programme un appel la fonction InitCommonControls() ou la fonction InitCommonControlsEx(), de manire forcer le chargement des Dlls permettant d'utiliser ce contrle. L'appel l'une de ces deux fonctions peut tre fait n'importe quel moment (mais avant d'utiliser le contrle) et ne doit tre fait qu'une seule fois. Si cet appel n'est pas fait, la bote de dialogue de s'affichera pas. Les styles pouvant tre utiliss avec ce contrle sont simples, ils sont donns dans l'Annexe A. Voici les messages utiliss pour commander ce contrle : PBM_SETRANGE permet de dfinir les valeurs numriques du minimum et du maximum pouvant tre atteints. Les valeurs de cet intervalle, tout comme la position courante sont des entiers, il faut donc prvoir un intervalle assez large pour ne pas que l'on distingue de saccades lors de la progression. Cet intervalle doit donc tre au moins de la taille (en pixels) de la barre. De manire gnrale, on peut opter pour un intervalle 0 - 1000. PBM_SETPOS dfinit la position actuelle de progression. Cette valeur doit bien entendu tre comprise dans l'intervalle fourni par le message PBM_SETRANGE. 8. Le contrle 'Combo box' Cours thorique : Le contrle 'Combo Box' est l'un des plus complexes. Il peut servir dans diffrents 'modes de fonctionnement'. On peut l'utiliser pour permettre l'utilisateur de faire un choix dans un menu droulant ou permettre l'utilisateur d'entrer un choix personnalis. Nous ne verrons ici que l'utilisation de ce contrle en tant que menu droulant permettant l'utilisateur de faire un choix dans une liste prdtermine. C'est l'utilisation la plus pratique de ce contrle car elle permet de s'assurer que le choix fait est valide. Page 27 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Voici un aperu des styles applicables ce contrle : CBS_DROPDOWN est le style correspondant au type de 'Combo box' que nous avons dcrit. La liste est affiche si l'utilisateur clique sur le contrle et elle ne peut pas tre modifie. CBS_SORT permet d'activer le tri automatique du contrle par ordre alphabtique. Pour utiliser ce contrle, vous pouvez utiliser les messages suivants : CB_ADDSTRING permet d'ajouter une entre la liste. La liste sera trie si le style CBS_SORT est utilis. CB_SETCURSEL permet de dfinir la slection courante. CB_GETCURSEL permet de retourner la slection courante. CB_SETITEMDATA permet d'associer une valeur 32 bits une entre. Cette valeur peut tre utilise pour identifier les entres si la liste est trie par exemple. CB_GETITEMDATA retourne la valeur 32 bits associe avec l'entre spcifie. Pour dterminer la taille de ce contrle (notamment la hauteur de la liste), on pourra utiliser la fonction SetWindowPos() qui sera vue plus tard. 9. Synthse sur les contrles Cours thorique : Les contrles qui ont t prsents prcdemment l'ont t de manire brve. Ce tutorial ne prtend absolument pas fournir une rfrence exhaustive de toutes les possibilits d'utilisation des contrles. Cette prsentation des contrles les plus courants permettra simplement de se familiariser avec leur utilisation. En effet, quel que soit le type de contrle utilis, la mthode pour le manipuler reste toujours la mme. Une fois le principe de fonctionnement des contrles compris, il sera facile de l'tendre une utilisation plus complexe. Les contrles prdfinis offrent une excellente manire de guider l'utilisateur et de le limiter. De plus, leur utilisation demande relativement peu de code et permet de crer rapidement une interface agrable. Les contrles offrent de nombreuses fonctionnalits qu'il faut savoir exploiter. On remarquera cependant que ces contrles sont assez difficilement personnalisables. Pour crer une interface graphique plus pousse ou plus personnalise, il faudra donc recourir d'autres mthodes que des contrles. Il est tout de mme possible de personnaliser les contrles (couleurs, etc...) mais ces mthodes ne permettant une personnalisation complte de l'interface. Il faut tout de mme rappeler que les ressources ne sont pas le seul moyen de mettre en place les contrles, ils constituent simplement un 'raccourci' pour leur utilisation. Tous les contrles peuvent tre intgrs de manire dynamique dans une fentre ou un bote de dialogue en utilisant la fonction CreateWindowEx(). Dans le cas d'une interface dynamique, c'est dire ne se prsentant pas systmatiquement sous la mme forme, l'utilisation des ressources est fortement dconseille car Page 28 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php elle 'fixe' les contrles au moment de la compilation. La cration des contrles au moment de l'affichage permet donc une plus grande souplesse, bien qu'exigeant une mise en place plus contraignante. 10. Deux projets rcapitulatifs Cours thorique : Voici 2 projets qui prsentent des utilisations extrmement simples de quelques contrles lmentaires. Seul le projet 06a sera dcrit ici, le second tant trs similaire. Projet N6a : Ces projets prsentent une utilisation extrmement simple de quelques contrles lmentaires. Aucune vrification n'est faite quant la validit des entres faites par l'utilisateur. Remarquons que du fait des contrles utiliss, l'utilisateur ne peut pas fournir de slection non valide ( part entrer un nom vide). C'est d'ailleurs pour a qu'il est indispensable d'initialiser les contrles 'Radio' ou 'Combo box' car sans cela, on pourrait avoir une slection non valide (aucune slection en fait). Les contrles sont bien entendus initialiss lors du message WM_INITDIALOG ( ce moment, la bote de dialogue n'est pas encore affiche). La liste n'est modifie qu'au moment o l'utilisateur presse le bouton 'Ajouter'. A ce moment, on rcupre le statut de tous les contrles que l'on a placs et on vrifie ventuellement la validit des donnes entres. Si tout est correct, on ajoute les donnes voulues la liste. Ici, on aurait quelques peines rcuprer les informations contenues dans la liste au moment de la validation de la bote de dialogue. Pour faciliter cela, il faudrait stocker chaque ligne dans une structure approprie. On associerait ensuite chaque entre de la liste son index dans notre tableau grce au message LB_SETITEMDATA. Tlcharger les projets comments [VC++] : Projet 06a - Projet 06b. Tlcharger les projets comments [BCB] : Projet 06a [BCB] - Projet 06b [BCB]. 11. MessageBox() Cours thorique : La fonction MessageBox() permet d'afficher une boite de dialogue prdfinie contenant le texte spcifi. Elle est trs utile pour afficher des messages d'erreurs, informer l'utilisateur. Elle retourne l'identifiant du bouton qui a t press pour la quitter. Cette fonction ne permet quasiment aucune personnalisation de la bote de dialogue qu'elle affiche, elle n'est donc rellement utile que dans le cas de messages envoys l'utilisateur, souvent de manire informative avec pour seul choix Page 29 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php le bouton 'Ok'. Les types de boites de dialogue pouvant tre affichs sont : MB_ABORTRETRYIGNORE MB_OK MB_OKCANCEL MB_RETRYCANCEL MB_YESNO MB_YESNOCANCEL Les icnes suivantes peuvent tre affiches sur la bote de dialogue : MB_ICONEXCLAMATION MB_ICONWARNING MB_ICONINFORMATION MB_ICONASTERISK MB_ICONQUESTION MB_ICONSTOP MB_ICONERROR MB_ICONHAND La fonction retournera l'une des valeurs suivantes : IDABORT IDCANCEL IDIGNORE IDNO IDOK IDRETRY IDYES 12. Parcourir l'arborescence Cours thorique : Pour permettre l'utilisateur de slectionner un dossier ou un fichier, on utilisera respectivement les fonctions SHBrowseForFolder() et GetOpenFileName(). Ces deux fonctions affichent les botes de dialogue standard de Windows pour parcourir l'arborescence la recherche d'un dossier ou d'un fichier. Notons que la constante MAX_PATH dsigne la longueur standard pour un chemin. Les buffers chargs de recevoir les noms de fichiers ou les chemins sont supposs Page 30 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php faire au moins cette taille. Voici un exemple d'utilisation de la fonction SHBrowseForFolder() : BROWSEINFO bi; LPITEMIDLIST Item; // Ici, la taille du buffer ne peut pas tre passe // buffer est suppos tre de taille MAX_PATH (ou plus) char buffer[MAX_PATH];
// On met tous les champs inutiliss 0 memset(&bi,0,sizeof(BROWSEINFO)); // hDlg est le HWND de la boite de dialogue qui demande l'ouverture // Ou NULL si la boite de dialogue n'a pas de fentre parent bi.hwndOwner=Dlg; // Contient le rpertoire initial ou NULL bi.pidlRoot=NULL; bi.pszDisplayName=buffer; bi.lpszTitle="Rpertoire courant"; bi.ulFlags=NULL; bi.lParam=NULL; Item=SHBrowseForFolder(&bi); if(Item!=NULL) { // buffer contient le nom du rpertoire slectionn SHGetPathFromIDList(Item,buffer); // buffer contient le chemin complet de la slection } Voici un exemple d'utilisation de la fonction GetOpenFileName() : OPENFILENAME st; char buffer[MAX_PATH];
// Pas de fichier par dfaut buf[0]=''; // On met tous les champs inutiliss 0 memset(&st,0,sizeof(OPENFILENAME)); st.lStructSize=sizeof(OPENFILENAME); // hDlg est le HWND de la boite de dialogue qui demande l'ouverture // Ou NULL si la boite de dialogue n'a pas de fentre parent st.hwndOwner=hDlg; // La syntaxe est : Description1Filtre1Description2Filtre2 st.lpstrFilter="Executables - Fichiers de commandes*.exe;*.bat"; st.lpstrFile=buffer; st.nMaxFile=MAX_PATH; st.lpstrTitle="Recherche de l'excutable"; st.Flags=NULL; // Contient le rpertoire initial ou NULL st.lpstrInitialDir=NULL; if(GetOpenFileName(&st)) // buffer contient notre chemin Chapitre 3 Les fentres 1. Fentres et botes de dialogue Page 31 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Les botes de dialogue sont semblables aux fentres dans le sens o ce sont des fentres. Cependant, il faut tre bien attentif marquer la diffrence qu'il existe entre ces deux types de fentres. Les botes de dialogue sont gres en grande partie par le systme. Crer une boite de dialogue est relativement simple. Les couleurs de fond, le type de fentre ont des valeurs par dfaut si l'on utilise un diteur de ressources. Dans le cas des fentres, il va falloir tout dfinir lors de l'excution. Lors de la cration d'une fentre, il sera ncessaire de prciser un grand nombre de paramtres qui taient passs implicitement au systme grce aux ressources avec les botes de dialogue. Il faut donc bien considrer les botes de dialogue comme un cas 'simplifi' de fentres. Cependant, on ne peut pas appliquer toutes les mthodes fonctionnant sur les fentres aux botes de dialogues ou inversement. Le mode de gestion des botes de dialogue est diffrent de celui des fentres, particulirement au niveau de l'affichage. De manire gnrale, il faut utiliser les botes de dialogue avec des contrles prdfinis. Pour crer des fentres contenant un affichage personnalis, des images, des graphiques, du texte utilisant diffrentes polices, il est prfrable d'utiliser une fentre. C'est donc pour ce type de travaux que les botes de dialogues trouvent leurs limites. Il faut donc retenir que les fentres et les botes de dialogue ne fonctionnement pas de la mme manire (malgr de nombreuses similitudes). Copier - Coller la procdure d'une fentre pour une bote de dialogue ne serait pas une bonne ide car certains messages sont spcifiques aux fentres et d'autres aux botes de dialogue. 2. Fonctionnement gnral d'une fentre Cours thorique : Le fonctionnement gnral des fentres a dj t vu au cours du premier chapitre. Un petit rappel ainsi que quelques prcisions est cependant ncessaire. Lorsqu'une fentre est cre, le systme lui attribue un identifiant. Cet identifiant est appel 'handle'. Il identifi la fentre dans son ensemble. Toute modification de la fentre (dplacement, modification de la taille) se fait grce cet identifiant. Une application utilisant plusieurs fentres pourra donc tre amene dfinir des variables globales contenant les identifiants des diffrentes fentres pour plus de confort. Cette dfinition globale n'est absolument pas ncessaire et peut tre vite dans le cas d'applications simples (le passage en paramtre des identifiants devient rapidement impossible). La fentre d'une application ne correspond absolument pas l'application elle mme. Une application (ou processus) peut tre excute et ne pas avoir de fentre. La cration de la fentre est Page 32 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php effectue par le programme lui mme. De plus, elle est totalement facultative. Aucune fentre ne sera cre par le systme au moment de l'excution de l'application. De plus, la fermeture de la fentre n'entrane pas la fermeture de l'application. C'est au programmeur de faire concorder ces deux vnements si il le dsire. Bien entendu la majorit des applications crent une fentre au dmarrage et se terminent lors de la fermeture de cette mme fentre, mais ce n'est en rien une obligation. De plus, une fentre peut avoir plusieurs tats, visible, cache, minimise... Lorsqu'une fentre est cre, elle n'est pas visible. Elle existe, ont peut la dplacer, afficher dans sa zone client... Cependant, rien de tout ceci n'est visible l'utilisateur. Ds qu'on donne une fentre le statut 'visible', elle apparat la mme position que lorsqu'elle tait invisible. Lorsqu'on dtruit une fentre, elle n'est plus affiche l'cran, mais dans ce cas, on ne peut plus la modifier. Elle n'existe plus pour le systme. Il ne faut donc pas confondre une fentre cache, qui existe toujours bien que n'tant pas visible, et une fentre dtruite, qui elle n'existe plus. Une fentre contient diffrentes zones. La zone client est la zone (gnralement blanche) dans laquelle les donnes de la fentre sont affiches. Les menus, la barre de titre, ne font pas partie de la zone client. La zone client est la seule partie de la fentre qui soit gre par l'application. La taille de la fentre sur l'cran est donc diffrente de celle de la zone client. La zone client est ncessairement de taille gale ou infrieure l'espace occup par la fentre. Pour afficher des donnes dans une fentre, on utilise des coordonnes relatives la zone client. Pour dplacer une fentre, on utilise des coordonnes relatives l'cran (coin suprieur gauche). Pour identifier la zone client, on utilise un contexte d'affichage. Il dfinit l'ensemble des proprits relatives l'affichage dans la zone client (nombre de couleurs disponibles, type de police...). Un contexte d'affichage identifie une zone dans laquelle ont peut afficher n'importe quel type de donnes. Cette zone n'est pas ncessairement affiche l'cran, on peut par exemple afficher des donnes dans une fentre cache en utilisant le mme contexte d'affichage. Dans ce cas, le contexte d'affichage est li la fentre, on peut donc l'obtenir partir de l'identifiant de la fentre concerne. Pour afficher dans la zone client, il n'est donc jamais ncessaire de se proccuper de la position de la fentre sur l'cran, il suffit de possder un 'handle' sur le contexte d'affichage. 3. Rcupration des messages Cours thorique : La rception des messages et leur transmission la procdure de fentre approprie sont essentiels. L'application ne doit jamais cesser 'd'couter' au cas o de nouveaux messages arriveraient. Si l'application arrte de transmettre les messages, la fentre semblera 'bloque' (affichage persistant, impossibilit de la dplacer...). La rcupration des messages peut se faire grce deux fonctions : GetMessage() ou PeekMessage(). La diffrence entre ces deux fonctions est fondamentale, il ne faut donc pas les assimiler. Page 33 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php GetMessage() est une fonction bloquante. Elle attend qu'au moins un message soit prsent dans la file d'attente de la fentre pour retourner. Le temps d'attente est infini, tant qu'aucun message n'est plac dans la file d'attente. PeekMessage() est une fonction non bloquante. Elle vrifie si des messages sont prsents et retourne immdiatement. Si au moins un message tait prsent, elle le retourne, sinon elle ne retourne aucun message. De plus, cette fonction permet de ne pas supprimer les messages de la file d'attente. On peut donc consulter des messages et laisser la boucle de messages principale effectuer le traitement. L'utilisation de ces deux fonctions s'inscrit dans des cadres totalement diffrents. En gnral, GetMessage() est utilis dans une boucle 'while'. Si aucun message n'est prsent, GetMessage() signale au systme qu'il peut donner le processeur une autre application. Placer cette fonction dans une boucle ne gnre donc pas d'occupation processeur tant qu'aucun message n'est prsent dans la file d'attente. PeekMessage() ne fonctionne pas de cette manire. Placer cette fonction dans une boucle 'while' gnre une occupation processeur de 100%. La fonction sera effectivement appele un nombre indfini de fois, occupant ainsi le processeur. La rcupration principale des messages doit donc se faire avec la fonction GetMessage(). Cependant, si l'application est occupe, elle peut appeler de manire rgulire PeekMessage() suffisamment de fois pour traiter tous les messages. Par exemple, toute les secondes, PeekMessage() est appele jusqu' puisement de la file d'attente. Le traitement des messages sera plus lent mais il sera effectu quand mme. Cette solution vite de devoir faire appel au multi-threading (excution simultane de plusieurs parties du programme). La diffrence entre ces deux fonctions doit donc tre clairement comprise. L'utilisation de l'une ou l'autre de ces fonctions tort peut s'avrer catastrophique pour les performances du programme et du systme dans son ensemble. Dans le cas d'une application n'effectuant pas de tches lourdes, l'utilisation de PeekMessage() ne sera pas ncessaire. L'interruption du traitement des messages pour une dure courte (jusqu' un dixime de secondes) est sans consquences. Il faut tout de mme remarquer que GetMessage() ne sera pas rappele tant que le traitement du message courant n'est pas termin. Le traitement des messages par la procdure doit donc si possible tre bref. Voici un exemple classique de boucle ralisant le traitement de messages : MSG msg; while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } La fonction GetMessage() retourne FALSE si le message WM_QUIT est reu. Ce message est envoy la fentre principale pour demander l'arrt de l'application. Par dfaut, ce message n'est pas envoy lors de la destruction de la fentre. 4. Cration d'une fentre Page 34 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Avant de pouvoir crer une fentre, il faut dfinir un type. C'est ce type de fentre qui sera utilis pour afficher la fentre nouvellement cre. Il est possible d'utiliser des types prdfinis ou de crer son propre type de fentres. Les contrles utiliss par les boites de dialogues sont des types de fentres prdfinis. Pour dfinir un nouveau type de fentres, on utilise la fonction RegisterClassEx () (cf. chapitre 1). Une fois le type de fentre dfinit, il faut crer la fentre. Pour cela, on utilise la fonction CreateWindowEx() (cf. chapitre 1). On lui passe le type (la classe) de fentre dsir ou encore une classe prdfinie. Lorsque la fentre est cre, un message WM_CREATE sera envoy. C'est ce moment que l'application doit faire les initialisations qu'elle dsire. La fentre cre est initialement cache. Il faut donc faire un appel ShowWindow() pour l'afficher. Il est possible de passer ShowWindow() le paramtre nCmdShow que Windows passe WinMain(), de cette manire l'tat de base de la fentre peut tre paramtr dans un raccourci. 5. Destruction d'une fentre Cours thorique : La destruction d'une fentre se traduit par l'envoi la fentre concerne d'un message WM_DESTROY. Il ne faut pas confondre ce message avec le message WM_QUIT. WM_QUIT demande la fermeture totale de l'application, le message WM_DESTROY demande simplement la destruction de la fentre en question. Le fait de cliquer sur la 'croix' provoque l'envoi d'un message WM_DESTROY la fentre. Pour faire quitter l'application en mme temps que la fermeture de la fentre, on peut utiliser la fonction PostQuitMessage() qui elle, envoie un message WM_QUIT. Cette fonction doit tre appele ds la rception du message WM_DESTROY. Le 'handle' de la fentre devient invalide ds la destruction de la fentre. Pour tester la validit d'un 'handle', on peut utiliser la fonction IsWindow(). Lorsque ce message est reu, la fentre est dj en cours de destruction. Il est possible de traiter le message WM_CLOSE pour demander une autre action que la destruction de la fentre. On peut par exemple, masquer la fentre. Une pression sur la 'croix' provoquera donc simplement la disparition l'cran de la fentre, mais pas au niveau systme. La fentre pourra par la suite tre raffiche dans le mme tat, par un appel la fonction ShowWindow(). 6. Contexte d'affichage Cours thorique : Page 35 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Un contexte d'affichage (DC) identifie une zone dans laquelle l'application peut afficher tout type de donnes. La zone identifie peut tre situe n'importe quel endroit de l'cran, elle peut tre dplace, ou mme masque par d'autres fentres, sans que l'application en ai conscience. Le fait de dplacer une fentre ne change donc rien la manire dont l'application redessine son contenu. Il en est de mme pour une fentre cache ou n'tant pas au premier plan. Toute fentre possde un contexte d'affichage associ sa zone client. Pour obtenir ce contexte, on peut utiliser la fonction GetDC() en spcifiant la fentre concerne. Si on spcifie un paramtre NULL, la fonction retourne un contexte d'affichage sur la totalit de l'cran. La zone identifie couvre l'ensemble de l'cran et est situe au dessus de toutes les fentres affiches. Sauf cas trs particulier, on n'utilise jamais le contexte d'affichage de la zone cran pour dessiner. Il peut tre utilis lors de la cration de bitmaps, etc... Le contexte d'affichage contient toutes les informations relatives l'affichage (police, nombre de couleurs)... Il n'identifie pas ncessairement une zone cran. Il peut par exemple identifier une feuille dans le cas d'une impression. On pourra donc utiliser les mmes fonctions pour afficher l'cran ou pour envoyer des donnes imprimables mais en modifiant le contexte d'affichage utilis. Lorsqu'on modifie une proprit du contexte d'affichage (ex. : la police courante), on dit qu'on slectionne un objet dans le contexte. Cet slection est effectue grce la fonction SelectObject(). Diffrents types d'objets peuvent tre slectionns dans un contexte d'affichage (polices, images, pinceaux de dessin...). 7. Gestion de base d'une fentre Cours thorique : La gestion d'une fentre se fait deux niveaux : au niveau de la procdure qui gre tous les messages reus par la fentre et par des fonctions permettant diverses manipulation sur la fentre. Voici un rapide aperu des fonctions les plus utiles pour manipuler les fentres : SetWindowText() modifie le texte de la barre de titre. GetWindowText() retourne le texte de la barre de titre. SetWindowPos() modifie la taille de la fentre, sa position sur l'cran, ainsi que sa position par rapport aux autres fentres (Z-order). GetWindowPlacement() retourne des informations sur le statut courant de la fentre spcifie. GetClientRect() retourne la taille de la zone client de la fentre. GetWindowRect() retourne la taille totale occupe par la fentre sur l'cran. AdjustWindowRect modifie la taille de la fentre spcifie. GetParent() retourne la fentre parent de la fentre spcifie. SetParent() modifie la fentre parent de la fentre spcifie. Page 36 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php ShowWindow() modifie l'tat courant d'une fentre. IsIconic(), IsZoomed(), IsWindowVisible() retournent des informations sur l'tat de la fentre courante. GetWindow() retourne la fentre ayant une relation spcifie dans le Z-order par rapport la fentre passe en paramtre. La procdure de fentre, quant elle, gre l'ensemble des messages envoys une fentre. Comme les messages envoys une fentre sont extrmement nombreux, il serait trs pnible de devoir tous les traiter, en particulier pour des fentres simples. L'API Windows fournit la fonction DefWindowProc() qui propose un traitement par dfaut de tous les messages envoys une fentre. Pour un traitement personnalis du message, il ne faut pas appeler la fonction DefWindowProc(). Le traitement par dfaut de la plupart des messages est en gnral satisfaisant. Cependant, pour imposer des comportements personnaliss une fentre, il s'avre obligatoire d'effectuer un traitement personnalis de certains messages (ex. : pour masquer la fentre lors d'une pression sur la 'croix'). Voici un aperu rapide des messages les plus courants : WM_CREATE est envoy une fentre au moment de sa cration. WM_PAINT est envoy lorsqu'une partie de la zone client doit tre redessine. WM_ERASEBKGND est utilis pour demander l'effacement d'une partie de la zone client. WM_SYSCOMMAND est envoy pour le traitement de diffrents vnements pouvant survenir (fentre minimise, restaure, ...). WM_ACTIVATE est envoy lorsqu'une fentre est active ou dsactive. WM_MOVE est envoy lorsque la fentre a t dplace. WM_SIZE est envoy lorsque la taille de la fentre t modifie. WM_CLOSE indique que l'utilisateur demande la fermeture de l'application (en cliquant sur la 'croix' ou en pressant ALT+F4). WM_DESTROY indique que la fentre est dtruite. Ces messages sont les premiers utiliser pour le maniement d'une fentre. De nombreux autres seront utiliss pour grer les interactions avec l'utilisateur (souris, clavier). Ils seront vus plus tard. Dans la plupart des cas, seul un nombre relativement peu lev de messages sera trait par l'application elle mme. Les autres seront passs la fonction DefWindowProc(). 8. Interactions avec l'utilisateur Cours thorique : Une fentre, peut recevoir des donnes de la part de l'utilisateur de deux manires : par le clavier, ou par la souris. Cependant, la fentre ne recevra d'information du systme que si elle est active. Si la fentre n'est pas active elle ne sera informe ni des vnements clavier, ni des Page 37 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php vnements souris. La fentre sera informe de diffrents vnements : pression d'une touche, relchement d'une touche, mouvement de la souris, pression d'un bouton de la souris Une fentre est informe ds que la souris bouge au dessus de sa zone client (si elle est active). Le nombre de messages envoys lorsque la souris se dplace est assez important. Aussi, le traitement de ces messages (WM_MOUSEMOV) doit tre bref. Les messages suivants sont utiliss pour traiter les vnements provenant du clavier : WM_KEYDOWN est envoy lorsqu'une touche est enfonce. Ce message indique le 'Virtual Key Code' correspondant la touche. WM_KEYUP est envoy lorsqu'une touche est releve. Ce message indique le 'Virtual Key Code' correspondant la touche. WM_CHAR indique le caractre ASCII correspondant la touche presse. WM_SYSKEYUP, WM_SYSKEYDOWN, WM_SYSCHAR sont utiliss pour identifier les vnements dus des touches systmes. Remarquons tout de mme la diffrence entre un code ASCII et un 'Virtual Key Code' : le code ASCII identifie un caractre (a,A,E,* ont des codes ASCII diffrents). Un 'Virtual Key Code' identifie une touche physique, a et A ont donc le mme code, mais dans le 2e cas, SHIFT est aussi press (en ASCII SHIFT n'a pas de code, ce qui est normal car cette touche ne correspond pas un caractre). Les messages suivants sont utiliss pour traiter les vnements provenant de la souris : WM_MOUSEMOVE est envoy chaque dplacement de la souris (plusieurs dizaines de fois par seconde). Les coordonnes de la souris sont passes en paramtres lors de l'envoi de ce message. Si le message est reu avec un retard, la position reue sera celle enregistre lorsque le message a t envoy. WM_LBUTTONDOWN est envoy lorsque le bouton gauche de la souris est press. WM_LBUTTONUP est envoy lorsque le bouton gauche de la souris est relev. WM_MBUTTONDBLCLK est envoy lors d'un double clic du bouton gauche de la souris. Ce message n'est envoy que si le style CS_DBLCLKS est utilis lors de la cration du style de la fentre. Les mmes messages sont envoys pour les boutons centraux et droits de la souris. (WM_MBUTTON_ ou WM_RBUTTON_). Pour obtenir la position courante de la souris, une application peut utiliser la fonction GetCursorPos(). Cette position peut ensuite tre convertie grce aux fonctions ScreenToClient() et ClientToScreen(). Les fonctions GetKeyState() et GetKeyboardState() peuvent tre utilises pour connatre l'tat d'une touche ou de l'ensemble du clavier. 9. Les timers Page 38 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Parfois, il peut tre utile d'excuter des tches intervalles fixes. Il existe de nombreux moyens d'effectuer cela, mais une des mthodes les plus simples est d'utiliser les 'timers' fournis par l'API Windows. Les 'timers' ne conviennent qu' des rsolutions de temps faibles, en gnral pas plus de 10 Hz. Un message est envoy intervalles rguliers une fentre spcifie. L'utilisation des timers pour des frquences trop importantes peut nuire aux performances gnrales du systme du fait des nombreux messages envoyer. De plus, le temps de traitement des messages fait qu'il est impossible d'obtenir des intervalles rguliers infrieurs 50ms. Les timers ne constituent donc qu'une mthode simple pour synchroniser un programme dans des conditions assez restreintes. Cependant, ils conviennent pour la plupart des applications bureautiques. Les timers sont gnralement utiliss avec des fentres, cependant, ils peuvent tre utiliss sans fentres. Dans ce cas, le message sera envoy au thread qui a demand la cration du timer. La fonction GetMessage() doit donc tre appele avec un argument hWnd gal NULL de manire na pas associer la rception des messages une fentre spcifique. Pour crer un timer, on utilise la fonction SetTimer() en prcisant l'intervalle de temps dsir. Un message WM_TIMER sera envoy la fentre chaque intervalle de temps. Pour stopper un timer, on utilise la fonction KillTimer(). Comme les timers ont des identifiants, il est possible de crer diffrents timers avec des intervalles diffrents. L'identifiant du timer est pass en paramtre lors de la rception du message WM_TIMER. Voici un exemple utilisant un timer sans fentre : MSG msg; SetTimer(NULL,NULL,60000,NULL);
while(GetMessage(&msg,NULL,0,0)) { if(msg.message==WM_TIMER) MessageBox(NULL,"Dj une minute de pass!","Info",MB_OK); } 10. Un projet rcapitulatif Cours thorique : Le projet prsent ici est simple. Il reprend la mme cration de fentre qu'au chapitre 1.2. La diffrence rside dans le traitement des messages. Tout d'abord, un timer est utilis pour permettre l'affichage de la fentre aprs deux secondes seulement. La fentre est initialement cache. Le timer est cr lors de la rception du message WM_CREATE. Ce n'est pas une ncessit. Il aurait aussi bien pu tre cr aprs l'appel CreateWindowEx() mais le fait de crer le timer dans la procdure permet de regrouper l'ensemble des traitements relatifs la fentre dans sa procdure. Des botes de dialogue sont affiches lorsque divers vnements sont dtects, pression d'un Page 39 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php bouton (gauche ou droit), pression d'une touche. Les clics hors de la zone client ne sont pas pris en charge. Comme la position rcupre par GetCursorPos() est relative l'cran, il faut la convertir en coordonnes relatives la zone client. La fonction ScreenToClient() fournie pas l'API Windows est donc la plus approprie. La destruction de la fentre est suivie de la fermeture de l'application grce un appel de PostQuitMessage(). Tlcharger le projet comment : Projet 07. 11. Affichage de texte dans une fentre Cours thorique : Pour afficher du texte dans la zone client d'une fentre, il faut possder un 'handle' sur le contexte d'affichage de cette fentre. Le texte qui sera affich le sera avec la police courante ainsi que les couleurs de premier plan et de fond courantes. Pour afficher du texte, il existe diverses mthodes. La premire est d'utiliser la fonction TextOut(). Cette fonction affiche le texte sur une seule ligne, sans jamais calculer de retour la ligne. Si le texte dpasse la taille de la fentre, il sera tronqu. La fonction DrawText() permet quant elle de dfinir un rectangle dans lequel le texte doit tre crit. Si le texte dpasse la largeur, il sera mis la ligne, et ceci autant de fois qu'il faudra. La position passe pour dessiner le texte correspond par dfaut au coin suprieur gauche de la chane de texte qui sera dessine. Le texte sera donc dessin vers la droite et vers le bas par rapport au point donn. On peut modifier le point de rfrence pour le positionnement du texte avec la fonction SetTextAlign(). Pour aligner du texte droite, on pourra par exemple placer ce point dans le coin suprieur droit. Le texte sera donc dessin gauche et en bas de la position passe pour dessiner le texte. Les fonctions SetTextColor() et SetBkColor() permettent de dfinir les couleurs courantes de premier plan et d'arrire plan pour l'affichage du texte. La fonction SetBkMode() modifie les proprits du fond (transparent ou opaque). Une fois ces fonctions appeles, le texte dessin le sera systmatiquement avec ces proprits. Pour dessiner du texte en blanc sur fond noir, il suffira donc d'un seul appel chacune de ces fonctions. Il faudra faire un nouvel appel pour repasser en texte noir sur fond blanc. Pour connatre la taille d'un texte l'cran (en pixels), il faut utiliser la fonction GetTextExtentPoint32(). Cette fonction retourne la taille qu'occupera un texte donn dans un contexte d'affichage donne. En effet, la taille du texte dpend du contexte d'affichage car la modification de la police entrane une modification de la taille occupe par le texte. L'utilisation de polices personnalises sera vue dans le chapitre suivant. La fonction suivante dessine un texte en rouge centr dans la zone client de la fentre spcifie. void DrawCenteredText(HWND hWnd,char *text) { HDC hDC; Page 40 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php RECT rcClient; GetClientRect(hWnd,&rcClient); hDC=GetDC(hWnd); SetTextColor(hDC,0x000000FF); SetBkMode(hDC,TRANSPARENT); SetTextAlign(hDC,TA_CENTER|TA_TOP); TextOut(hDC,(int)((float)rcClient.right/2),5,text,strlen(text)); ReleaseDC(hWnd,hDC); } 12. Utilisation de polices personnalises Cours thorique : Pour utiliser une police personnalise pour l'affichage du texte, il faut tout d'abord crer la police spcifie. Pour cela, on ralise un appel CreateFont() pour dterminer la police voulue, la taille, le style... Il faut ensuite slectionner la police dans le contexte d'affichage avec la fonction SelectObject(). Une fois qu'une police est slectionne dans un contexte d'affichage, chaque texte affich le sera avec cette police. SelectObject() retourne le 'handle' de la police prcdemment slectionne. Une fois l'utilisation de la police termine, l'application doit reslectionner l'ancienne police par un nouvel appel SelectObject(). Ensuite, elle doit dtruire la police inutilise par un appel DeleteObject(). La suppression des objets crs par une application est faite automatiquement lorsque celle-ci se termine. Cependant, ils occupent de la place inutilement durant l'excution de l'application. Il est donc important de supprimer les objets inutiliss. De plus, le nombre d'objets disponible pour une application donne est limit, si l'application recre une nouvelle police chaque modification de l'affichage sans supprimer l'ancienne, elle va rapidement atteindre le nombre maximal d'objets. Voici un exemple d'appel CreateFont() : HFONT Police; Police=CreateFont( 20, 0, 0, 0, 700, FALSE, FALSE, FALSE, 0, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Comic Sans MS" ); 13. Affichage d'une image Page 41 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Cours thorique : Avant de pouvoir afficher une image, il faut tout d'abord la charger. Nous ne traiterons ici que les images bitmap. En effet, ce sont les seules images prises en charge par l'API Windows. Pour effectuer des traitements sur les images avec des fonctions du GDI (Graphic Device Interface), il faut donc tout d'abord les convertir au format bitmap. Les images sont considres comme des objets du GDI, au mme titre que les polices. On peut donc utiliser les fonctions de manipulation d'objet (ex. : SelectObject() ou DeleteObject()) aussi bien sur des images que sur des polices. On peut charger une image partir du disque ou encore partir des ressources du programme. Pour cela, on peut utiliser deux fonctions : LoadBitmap() ou LoadImage(). La fonction LoadBitmap() est beaucoup moins complte que LoadImage(), il est donc prfrable d'utiliser cette dernire. Ce n'est cependant pas une obligation. Une fois l'image charge en mmoire, le GDI retourne un 'handle' (de type HBITMAP) sur l'image. Elle peut alors tre affiche. Le rsultat de l'affichage dpend bien entendu du contexte d'affichage utilis (en particulier du nombre de couleurs qu'il comprend). Pour afficher l'image, on utilisera la fonction DrawState(). Cette fonction n'affiche pas ncessairement des bitmaps, elle peut aussi afficher des icnes par exemple. De plus, cette fonction est capable de modifier la taille d'affichage du bitmap. Il n'est cependant pas recommand d'agrandir la taille des images l'affichage car l'agrandissement est alors de trs mauvaise qualit. Voici un exemple de fonction dessinant une image charge depuis un fichier dans la fentre spcifie : void PrintBmp(HWND hWnd, char *filename) { HBITMAP hBmp; HDC hDC; hBmp=(HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE); hDC=GetDC(hWnd); DrawState(hDC,NULL,NULL,(LPARAM)hBmp,NULL,0,0,0,0,DST_BITMAP); DeleteObject(hBmp); ReleaseDC(hWnd,hDC); } 14. Dessin Cours thorique : L'affichages de 'dessins' (lignes, cercles, points) dans une fentre passe par le GDI. Les dessins se font avec un pointeur. Par dfaut, le pointeur est un pixel unique. Tracer une ligne donne donc le rsultat attendu : une ligne de 1 pixel de large. Pour dessiner une ligne plus large, il va falloir remplacer le pointeur courant par un pointeur plus large. Pour cela, on utilise le fonction CreatePen (). Elle retourne une variable de type HPEN. Ensuite, il faut slectionner le pointeur ainsi cr dans le contexte d'affichage courant. Bien entendu, il faudra, tout comme avec les polices, supprimer les Page 42 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php pointeurs ds que ceux-ci ne sont plus utiliss. Voici un aperu des fonctions utilises pour dessiner : SetPixel() dessine un pixel unique, indpendamment du pointeur courant. GetPixel() retourne la couleur du pixel la position spcifie. MoveToEx() dplace la position courante du curseur de dessin. LineTo() trace une ligne depuis la position courante du curseur de dessin jusqu' la position spcifie. La ligne est trace avec le pointeur courant. Arc(), ArcTo() et AngleArc() dessinent des ellipses ou des portions d'ellipses. Polyline() dessine une srie de lignes connectes. 15. Fond personnalis Cours thorique : Jusqu' prsent, nous avions laiss Windows effacer le fond. L'effacement de la zone client avant le redessinement n'est pas ncessaire, la mthode la plus simple et la plus rapide est de redessiner par dessus l'ancienne zone. Toutefois, si l'on veut redessiner une partie de la zone client seulement ou si le nouveau dessin ne couvre pas toute la zone (du texte avec fond transparent par exemple) il est ncessaire d'effacer d'abord. Pour cela, il faut utiliser le message WM_ERASEBKGND. Lorsque ce message est envoy, un 'handle' sur le contexte d'affichage effacer est pass en paramtre. Il faut imprativement utiliser le 'handle' pass en paramtre pour effectuer toutes les oprations de dessin. L'application dispose de plusieurs solutions pour effacer la zone client. Elle peut effectuer des tches complexes, afficher des images, puis un texte... Si les images ne couvrent pas toutes la zone, il faut effacer les parties qui resteront visibles. Pour cela, on peut utiliser la fonction FillRect(). Cette fonction ne prend pas en paramtre une couleur mais un HBRUSH. On peut crer des 'brushes' partir d'images bitmap avec CreatePatternBrush() ou encore partir d'une couleur unie avec CreateSolidBrush(). Si l'application dsire simplement modifier la couleur de fond de sa zone client, elle n'est pas oblige de traiter le message WM_ERASEBKGND. Elle peut simplement utiliser CreateSolidBrush() et passer le rsultat au membre hbrBackground de la structure WNDCLASSEX lors de la cration de la classe de la fentre. De cette manire la fonction DefWindowProc() crasera le fond elle mme avec la couleur demande. 16. Commandes systmes Cours thorique : Pour personnaliser une application, on peut tre amen modifier le traitement standard des commandes systmes. Pour cela, on utilise le message WM_SYSCOMMAND. Ce message est Page 43 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php envoy en diverses occasions : dplacement de la fentre, utilisation du menu de contrle sur la barre de titre (fermer, minimiser, restaurer), activation de l'cran de veille, droulement du menu dmarrer... En interceptant ce message, une application peut dfinir des ractions personnalises aux traitements standard Windows. Par exemple, masquer la fentre au lieu de la dtruire lors d'un clic sur la 'croix'. Comme le mme message est envoy (WM_SYSCOMMAND) quel que soit la commande systme, il faut imprativement appeler DefWindowProc() pour traiter les commandes systmes non prises en charge par l'application. 17. Menus Cours thorique : Il existe deux manires d'insrer des menus dans une application. La premire mthode est de crer un menu grce un diteur de ressources puis de l'insrer dans le programme. La seconde mthode est dynamique, grce aux fonctionnalits de l'API Windows. De manire gnrale, les menus obtenus par des ressources seront utiliss pour les menus principaux des fentres, pour crer des barres de menus. Pour les menus contextuels, botes outils, ..., il est prfrable d'utiliser des menus dynamiques. A chaque clic dans un des menus, la fentre possdant le menu reoit un message WM_COMMAND indiquant l'identifiant de l'option choisie. Pour obtenir une barre de menus partir d'un fichier de ressources, il suffit de crer le menu dans l'diteur de ressources puis de passer son identifiant au membre lpszMenuName de la structure WNDCLASSEX. Le menu sera insr automatiquement dans la fentre. Pour un menu dynamique, on utilisera l'API Windows. Les menus sont identifis par une variable HMENU. Il faut tout d'abord crer le menu. Il sera initialement vide. Pour cela, il faut utiliser la fonction CreatePopupMenu(). Une fois le menu cr, on peut lui ajouter des lments (options, sous menus, sparateurs) avec les fonctions AppendMenu() ou InsertMenuItem(). Une fois le menu cr, il faut le dessiner. Pour cela, on utilise les fonctions TrackPopupMenu() ou TrackPopupMenuEx(). Pour une bote outils, il suffira de dessiner le menu la position courante obtenue par un appel GetCursorPos(). Si le menu est associ une fentre, il sera supprim automatiquement lors de la destruction de la fentre. Dans le cas contraire, l'application doit appeler la fonction DestroyMenu() pour librer les ressources. La fonction suivante dessine un menu contextuel : void PrintMenu(HWND hWnd) { HMENU hMenu; POINT pt; GetCursorPos(&pt); hMenu=CreatePopupMenu(); AppendMenu(hMenu,MF_STRING,1,"Item 1"); AppendMenu(hMenu,MF_STRING,2,"Item 2"); AppendMenu(hMenu,MF_SEPARATOR,NULL,NULL); AppendMenu(hMenu,MF_STRING,3,"Item 3"); TrackPopupMenu(hMenu,NULL,pt.x,pt.y,0,hWnd,NULL); Page 44 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php } Dans cet exemple, les valeurs 1, 2 et 3 passes AppendMenu() reprsentent les identifiants des options. Ces valeurs seront passes en paramtre lors de l'envoi du message WM_COMMAND. Elles n'ont aucune signification particulire, si ce n'est pour le programmeur lui mme. 18. Cration dynamique d'un contrle Cours thorique : La cration dynamique de contrles ne prsente aucune difficult particulire. Elle se fait grce la fonction CreateWindowEx(), en utilisant des classes de fentres prdfinies. Une fois le contrle cr, il faut tout de mme remarquer que son utilisation est quelque peu diffrente. En effet, des fonctions telles que SetDlgItemText() ne fonctionneront plus. Il faudra utiliser la fonction SetWindowText() la place, puisque le contrle est une fentre. La fonction SetDlgItemText() n'est en fait qu'un raccourci qui appelle SetWindowText() aprs avoir utilis GetDlgItem() pour obtenir le 'handle' du contrle correspondant. Les styles utiliss dans l'diteur de ressources sont les mmes. La fonction CreateWindowEx () acceptera donc comme styles l'ensemble des styles utiliss dans l'diteur de ressources. Les classes suivantes sont prdfinies : BUTTON, COMBOBOX, EDIT, LISTBOX, MDICLIENT, RichEdit, RICHEDIT_CLASS, SCROLLBAR, STATIC. Chapitre 4 Le systme de fichier 1. Introduction Cours thorique : L'API Windows fournit une suite de fonctions permettant d'accder au systme de fichier. Bien entendu, il est toujours possible d'utiliser les fonctions classiques du C (fopen(), fprintf()) pour accder au systme de fichier. Cependant, ces fonctions ne supportent pas toutes les possibilits du systme de fichier Windows (ex. : la gestion du partage des ressources dans l'environnement Windows). Il est donc en gnral prfrable d'utiliser les fonctions fournies avec l'API Windows pour manipuler le systme de fichier. Les fonctions de manipulation de fichiers (ouverture, criture, lecture) seront donc abordes dans ce paragraphe. De plus, l'utilisation de fichiers dans un contexte multi-tches impose des prcautions d'emploi supplmentaires. Le non respect de certaines prcautions simples peut entrainer des erreurs ou mme la perte ou l'crasement de donnes. Page 45 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Le problme de la recherche de fichiers dans le systme de fichier sera galement abord. Ce paragraphe a donc pour but de prsenter de manire gnrale les oprations de base lors de la manipulation du systme de fichier Windows grce aux fonctions de l'API Windows. 2. Cration et ouverture d'un fichier Cours thorique : L'API Windows fournit la fonction CreateFile() pour ouvrir ou crer des fichiers. Cette fonction permet de grer le partage du fichier au niveau systme, les attributs de ce fichiers. Il est important de bien comprendre les paramtres principaux de cette fonction. Avant la description plus en dtail de cette fonction, il est important de bien comprendre le fonctionnement du systme de fichiers sous Windows. Comme cet environnement est multi-tches, il est possible qu'un fichier soit accd en mme temps par plusieurs programmes ou mme par le mme programme. Le premier programme demander l'accs au fichier doit prciser si il souhaite ou non le partager. Si le fichier n'est pas partag, le systme refusera tout autre appel CreateFile() pour accder ce fichier. Le fichier ne sera donc plus accessible au reste du systme. Le fichier restera 'rserv' jusqu' ce que le programme se termine ou jusqu' ce que la fonction CloseHandle() soit appele (cette dernire fonction sera explique plus tard). En ne partageant pas le fichier le programme s'assure qu'il est le seul accder au fichier. Un fichier peut galement tre partag en lecture. Dans ce cas, seul le programme ayant accd au fichier en premier aura un accs en lecture/criture. Les appels suivants CreateFile() permetteront seulement d'accder au fichier en lecture. Si le fichier est partag en lecture/criture, alors l'accs simultan en lecture ou criture ce fichier est autoris. Plusieurs programmes peuvent donc lire ou crire des donnes simultanment dans ce fichier. Ce dernier mode de partage est trs dangereux et ne doit tre utilis qu'en connaissance de cause. Lors du premier appel CreateFile(), le mode de partage du fichier est dfini. Il ne pourra pas tre modifi par un autre appel. Mme si le fichier est partag, son mode de partage ne pourra pas tre modifi. Si l'appel la fonction CreateFile() russit, le systme retourne un HANDLE au programme. Ce HANDLE identifie le fichier. La fonction CloseHandle() permet de refermer le fichier. A partir de cet appel, le HANDLE identifiant le fichier n'est plus valide. L'identifiant du fichier stocke galement la position courante du pointeur. Le systme stockera donc la position courante d'criture pour chaque nouvel appel CreateFile(). Si le fichier est ouvert 10 fois simultanment, le systme stockera 10 positions. Le partage du fichier n'entrane donc aucune modification dans les mthodes de lecture/criture sur le fichier (le pointeur courant ne sera pas modifi mme si un autre programme accde le fichier). Cependant, si deux threads utilisent le mme HANDLE pour accder un fichier, alors le pointeur pourra tre modifi par l'un ou par l'autre. Si le mme thread fait deux appels successifs l'API pour lire le fichier, rien ne garantit qu'il Page 46 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php lira deux blocs successifs. En effet, si le deuxime thread a modifi le pointeur entre les deux appels, alors la lecture se poursuivra la nouvelle position. Pour viter des complications, le plus simple est gnralement de ne pas partager les fichiers ou d'autoriser seulement le partage en lecture. Voici un dtail de principaux arguments de la fonction CreateFile() : HANDLE CreateFile( LPCTSTR lpFileName, // pointer to name of the file DWORD dwDesiredAccess, // access (read-write) mode DWORD dwShareMode, // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes DWORD dwCreationDisposition, // how to create DWORD dwFlagsAndAttributes, // file attributes HANDLE hTemplateFile // handle to file with attributes to // copy ); lpFileName est le nom du fichier ouvrir ou crer. DwDesiredAccess est le type d'accs demand : lecture (GENERIC_READ), criture (GENERIC_WRITE) ou les deux. Si le fichier est partag en lecture seulement et qu'un accs en criture est demand, la fonction retournera une erreur. DwShareMode indique le mode de partage demand. Ce mode peut tre non partag (NULL), partag en lecture (FILE_SHARE_READ), partag en criture (FILE_SHARE_WRITE) ou les deux. DwCreationDisposition indique la mthode utilise pour lire ou crer le fichier. CREATE_NEW demande la cration d'un fichier. Si le fichier existe dj, la fonction retournera une erreur. CREATE_ALWAYS demande la cration d'un fichier. Si le fichier existe dj, il est cras. OPEN_EXISTING demande l'ouverture d'un fichier existant. Si le fichier n'existe pas, le fonction retourne une erreur. PEN_ALWAYS demande l'ouverture du fichier. Si le fichier n'existe pas il est cr. dwFlagsAndAttributes indique les attributs utiliss pour crer le fichier. La fonction retournera un HANDLE en cas de succs ou INVALID_HANDLE_VALUE, c'est dire le pointeur 0xFFFFFFFF est cas d'erreur. Ds que le fichier n'est plus utilis, l'application peut utiliser CloseHandle() pour signifier qu'elle n'utilise plus le fichier spcifi. Toute autre application pourra alors avoir accs ce fichier si elle le demande. 3. Lecture/Ecriture dans un fichier Cours thorique : Avant de pouvoir lire ou crire dans un fichier, il faut raliser un appel CreateFile(). Le HANDLE retourn permettra d'identifier le fichier dans lequel on souhaite effectuer les oprations. Page 47 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php La fonction ReadFile() permet la lecture dans un fichier. Elle dplace le pointeur courant la nouvelle position. ReadFile() lit le nombre d'octets spcifis et retourne un indicateur boolen, ainsi que le nombre d'octets lus. ReadFile() retournera toujours TRUE si le pointeur sur le fichier est valide. Pour dtecter la fin du fichier, il faut comparer le nombre d'octets lus au nombre d'octets demands. Si 256 octets sont demands la lecture et que ReadFile() indique que seulement 18 octets ont t lus, alors la fin du ficher est atteinte. La fonction WriteFile() a un mode de fonctionnement similaire ReadFile(). Elle crit le nombre d'octets demand, retourne un indicateur de succs ainsi que le nombre d'octets effectivement crits. Pour dplacer le pointeur courant on utilise la fonction SetFilePointeur(). La nouvelle position peut tre spcifie partir du dbut du fichier, de la position courante du fichier ou de la fin du fichier. Cette fonction retourne la nouvelle position. Pour obtenir la position courante du pointeur, on appelle SetFilePointeur() en demandant un dplacement nul partir de la position courant. On rcupre ainsi la position courante du pointeur. La fonction SetEndOfFile() dtermine la position de la fin du fichier. La fin du fichier est alors place la position courante du pointeur. 4. Manipulations sur les fichiers Cours thorique : D'autres fonctions peuvent tre utilises lors de la manipulations de fichiers. Pour obtenir la taille d'un fichier, il faut utiliser GetFileSize(). Cette fonction retourne sur 32 ou 64 bits la taille du fichier spcifi. Les fonctions MoveFileEx(), CopyFileEx() et DeleteFile() peuvent tre utilises pour les manipulations courantes sur les fichiers. Ces fonctions ne ncessitent pas que le fichier soit prcdemment ouvert par CreateFile(). La fonciton DeleteFile() supprime un fichier sans le faire passer par la corbeille. Il est donc impossible d'annuler la suppression du fichier. Pour envoyer simplement un fichier dans la corbeille, il faut utiliser la fonction SHFileOperation(). Cette fonction permet la fois la suppression et l'envoi la corbeille d'un fichier. Le systme de fichier VFAT (utilis sous Windows 95/98) manipule des noms courts et des noms longs. Les noms de fichiers courts (format 8.3) sont un hritage des prcdents systmes de fichiers utiliss. Pour convertir des nom de fichiers en version courte ou longue on utilise les fonctions GetShortPathName() et GetFullPathName(). 5. Enumration de fichiers Cours thorique : Page 48 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Pour numrer des fichiers, l'API Windows offre une slection de 3 foncitons : FindFirstFile (), FindNextFile() et FindClose(). La fonction FindFirstFile() initialise la recherche et retourne le premier fichier trouv (si il y en a un). Elle retourne un HANDLE sur la recherche en cours, pour permettre de poursuivre la recherche. Le fonction FindNextFile() doit ensuite tre appele pour rcuprer l'ensemble des fichiers trouvs. Gnralement cette fonction sera appele dans une boucle. La fonction FindClose() referme le HANDLE et libre la mmoire occupe. Cette suite de fonctions permet d'effectuer des recherches dans un dossier. Attention, les sous dossiers ne seront pas parcourus. La recherche peut porter sur un listage non exhaustif, par exemple, listage des fichiers '*.txt'. Pour un listage exhaustif, il faudra demander le listage des fichiers '*.*'. Le type de recherche, ainsi que le dossier de recherche sont passs la fonction FindFirstFile() sous forme d'une chane de caractre de type : Chemin\\Masque (ex. c:\*.*). Aucune fonction n'est fournie pour le parcours rcursif des dossiers de manire explorer les dossiers et sous dossiers. Pour effectuer une recherche complte, il faut effectuer des appels successifs ces 3 fonctions de manire parcourir l'ensemble des sous dossiers. Lors d'une recherche, les pseudo-dossiers '.' et '..' sont galement lists. Il peut donc tre utile d'effectuer un test pour supprimer ces dossiers du listage final. Les informations concernant les fichiers et dossiers lists sont places dans une structure WIN32_FIND_DATA (attributs, nom, taille...). 6. Manipulations sur les dossiers Cours thorique : L'API Windows offre peu de manipulations possibles sur les dossiers. La fonction CreateDirectory() permet la cration d'un dossier. Attention toutefois au fait que cette fonction ne cre pas les sous dossiers ncessaires. Si la cration du dossier demand demande la cration implicite de plusieurs dossiers, la fonction retournera une erreur. Pour ex., le dossier c:\a\b ne sera cr que si le dossier c:\a existait dj. Pour crer ce dossier, il faudra faire deux appels CreateDirectory() pour crer respectivement les dossiers c:\a et c:\a\b. Pour supprimer un dossier, on utilisera la fonction RemoveDirectory(). Cette fonction ne supprimera le dossier que si il est vide. Pour supprimer un dossier non vide il faudra donc lister l'ensemble des fichiers et dossiers et les supprimer avant de supprimer le dossier parent. La fonction GetCurrentDirectory() retourne le rpertoire courant. Elle peut tre utilise par exemple pour obtenir le chemin du processus courant. En effet, gnralement, les programmes sont excuts avec pour dossier courant, le dossier contenant l'excutable. Ceci est toujours vrai sauf mention contraire de l'utilisateur. La rcupration du chemin de l'excutable courant par cette mthode n'est donc pas garantie mais elle marchera dans la plupart des cas. La fonction GetTempPath() permet de rcuprer le chemin du dossier temporaire (gnralement Windows\temp). Les fonctions GetWindowsDirectory() et GetSystemDirectory() permettent de rcuprer le chemin du dossier contenant Windows et du dossier systme (system ou Page 49 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php system32). 7. Manipulations des chemins Cours thorique : Windows fournit une collection de fonctions permettant de travailler sur les fichiers, les dossiers ou les chemins. Voici une liste non exhaustive des fonctions les plus utilises : PathIsDirectory() permet de dterminer si le chemin spcifi est un dossier. PathFileExist() dtermine si le fichier spcifi existe. PathCanonicalize() supprimer toutes les rfrences symboliques du chemin et le convertit en chemin absolu. Ex. : (c:\a\.. sera converti en c:\). PathCompactPath() permet de rduire la taille occupe par le chemin. Cette fonction remplace une partie de chemin par des points. PathFindExtension() retourne l'extension du nom de fichier spcifi. PathFindFileName() retourne le nom de fichier dans le chemin spcifi. PathGetArgs() retourne les arguments du chemin spcifi. PathRelativePathTo() permet de relativiser un chemin par rapport un autre chemin. PathRemoveArgs() supprime les arguments du chemin pass en paramtre. PathRemoveExtension() supprime l'extension du chemin, si celle-ci existe. Pour une liste exhaustive, reportez vous l'annexe B. Chapitre 5 Le multithreading 1. Introduction Cours thorique : Commenons tout d'abord par une explication sur le terme de multithreading. Le multithreading est le fait d'excuter des tches simultanment (multitche). Les termes multitche et multithread ne sont pas totalement similaires. Le terme multithread indique une application qui effectue diffrentes taches simultanment. Le terme de multitche est un terme plus gnral et plus commun pour parler d'un systme capable de grer plusieurs applications simultanment. Dans un programme classique, les instructions sont traites de manire linaire, "ligne ligne". En ignorant le reste du systme on pourrait dire que le processeur excute les instructions de la ligne courante, puis passe la suivante. En ralit ce n'est pas tout fait vrai car les autres applications Page 50 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php doivent elles aussi avoir accs au processeur, ainsi que le systme d'exploitation lui mme. Un thread est en fait une unit d'excution. Chaque thread peut avoir accs au processeur et excuter des instructions. Un thread peut se comporter exactement comme si il ne partageait pas le processeur (mais dans ce cas, il faudrait disposer d'autant de processeurs qu'il y a de threads). Pour viter cet inconvnient, le systme donne l'accs au processeur chaque thread durant un temps trs court (quelques millisecondes), et ceci de manire circulaire. Si le nombre de thread augmente, chaque thread devra attendre un dlai plus long avant d'obtenir de nouveau l'accs au processeur. De cette manire, chaque application peut fonctionner comme si elle tait seule utiliser le processeur. La seule diffrence est que l'excution est plus lente. Si le processeur est suffisamment rapide, l'excution d'un nombre modr de threads peut s'effectuer trs rapidement. C'est le cas actuellement. Dans un systme utilisant Windows, le nombre de threads avoisine 50 au repos. Il dpasse rapidement 100. L'utilisation du multithread consiste crer un programme qui comporte plusieurs threads. Ce programme peut donc excuter plusieurs instructions "simultanment". On peut alors se demander l'utilit d'une telle pratique. En effet, puisque le processeur est partag, l'excution du programme ne sera pas plus rapide. Elle sera seulement fragmente. Un exemple simple permet de comprendre l'utilit du multithreading. Considrons une application ralisant un transfert de fichier. Cette application dispose d'une interface graphique prsentant une barre de progression ainsi qu'un bouton 'annuler'. En effet l'utilisateur dsire tre tenu au courant de l'avancement de la copie. De plus il veut pouvoir stopper celle ci tout moment si il le dsire. Le programme doit donc raliser une boucle de manire recevoir les messages. Mais il doit galement s'occuper de la copie des fichiers, ce qui reste son rle principal. Si le programme crit sur le disque, il ne peut plus recevoir les messages. La fentre ne sera donc plus rafrachie. De plus, si l'utilisateur clique 'annuler', l'application ne traitera pas cette demande puisqu'elle ne recevra mme pas le message... Une telle application utilisera donc 2 threads. Le premier thread s'occupera de la rception et du traitement des messages. Le deuxime thread s'occupera de la copie des fichiers. De cette manire, la rception des messages sera effectue mme au cours de la copie. Si l'utilisateur demande l'arrt de la copie, le premier thread devra simplement stopper l'excution du second. La copie sera alors stoppe. 2. Notion de processus et de thread Cours thorique : Il ne faut pas confondre les processus et les threads. Un processus est une application. Tout processus dispose d'au moins un thread. Il peut cependant en possder plusieurs. Pour l'utilisateur moyen, les threads sont invisibles. Ils sont masqus par les processus. La diffrence entre un processus et un thread est qu'un processus ne constitue en rien une unit d'excution. L'excution simultane de plusieurs processus est ralise grce au multithreading. Le processus reprsente Page 51 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php simplement un application vis vis du systme. Lorsque l'utilisateur demande la terminaison d'une application, tous les threads appartenant cette application devront tre termins. Tout processus doit comporter au moins un thread, de manire excuter le point d'entre du programme. Ce thread ne pourra jamais tre termin sans que l'application ne soit elle mme termine. Le processus ne doit en aucun cas tre assimil ce thread. Le thread constitue simplement l'unit d'excution de base du processus. Le systme ne communiquera jamais avec le processus mais toujours avec l'un des threads de ce processus. En effet, le processus n'tant pas une unit d'excution il ne ralise aucune tche. Le premier thread est cr par le systme. C'est pour cette raison que dans un programme comportant un seul thread, aucune rfrence n'est faite aux fonctions de l'API relatives aux threads. La cration de nouveaux threads devra tre explicite. C'est ce qui sera tudi tout au long de ce chapitre. 3. Partage des tches Cours thorique : Avant d'utiliser des threads, il est ncessaire de bien comprendre comment le systme gre ces threads. En effet, un programme multithread mal conu peut conduire une trs forte baisse des performances systme. Il faut bien comprendre que si le systme d'exploitation accorde chaque thread un temps d'accs au processeur, le thread n'est pas oblig d'utiliser totalement ce temps. Le systme accorde simplement un temps maximum d'utilisation. Au del de cette priode, le contrle du processeur sera pass un autre thread. Si le thread n'a plus aucune tche effectuer, il doit repasser lui mme le contrle au systme. De cette manire, une application inactive ne monopolisera pas inutilement le processeur. Si une application inactive n'effectue pas ce passage, elle utilisera totalement son quota d'accs. Le systme affichera alors une charge processeur de 100% et les performances seront fortement ralenties. Le systme d'exploitation dispose galement d'un systme de priorits permettant de grer les quotas d'accs allous aux processus. Voici un rsum rapide du fonctionnement de ce systme de priorit : Time Critical : ce thread requiert l'utilisation totale du processeur. Les quotas allous ce thread sont infinis. Si un tel thread ne repasse pas spontanment le contrle au reste du systme, le systme restera en attente. Le contrle du processeur ne sera accord aucun autre thread. Tout le systme sera alors bloqu jusqu' ce que le thread repasse le contrle. Ce type d'application n'est en ralit quasi jamais utilis. Il doit tre rserv des utilisations trs particulires (ex : un gestionnaire de souris). High : ce thread recevra le contrle du processeur en priorit. Un tel thread ne peut pas tre bloquant pour le systme mais il pourra ralentir normment les performances. Un thread utilisant cette priorit et effectuant un accs permanent au processeur causera un effondrement des Page 52 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php performances gnrales du systme. Cette priorit ne doit gnralement tre accord qu' des applications effectuant des tches trs brves ou tant en premier plan. Les performances de ce thread ne seront quasiment pas affectes par l'excution d'autres applications. Normal : ce thread a une priorit normale. Il recevra le processeur exactement autant de fois que les autres threads de la mme priorit. Si deux threads utilisent cette priorit et utilisent le processeur en permanence, les performances de chacun des thread seront divises par 2. La quasi totalit des threads utilise cette priorit (c'est d'ailleurs la plus recommande hors cas trs particuliers). Idle : ce thread recevra le contrle du processeur seulement si aucune autre application ne demande le contrle. Si ce thread effectue des tches en continu et que toutes les autres applications sont inactives, il disposera de l'accs processeur dans sa quasi totalit. Si un autre thread effectue des tches continues, ce thread n'aura plus aucun accs au processeur. Ce type de priorit peut tre accord des applications effectuant des tches de fond. De cette manire les performances du systme resteront inchanges. La charge du processeur sera alors continuellement de 100%. Le partage sera alors : 0% pour le thread Idle si le systme utilise le processeur. 100% pour le thread Idle si le systme est inactif. De manire gnrale il est inutile de modifier la priorit des threads. La modification des priorits ne doit tre faite que dans les cas particuliers et en connaissance de cause. En particulier, les priorits 'Time Critical' et 'High' ne doivent jamais tre utilises abusivement. De manire conserver des performances systme optimales, un thread doit repasser le contrle au systme ds qu'il devient inactif. Pour cela il dispose de plusieurs mthodes. Prenons l'exemple d'un thread qui dsire attendre 150ms. Si ce thread effectue une boucle en testant l'heure systme, il utilisera totalement son quota et le systme affichera une charge de 100%. Si eu lieu de cela le thread effectue un appel la fonction Sleep(), il repassera le contrle au reste du systme pour une dure de 150ms. Le contrle du processeur ne lui sera plus accord durant cette dure. La charge processeur sera alors de 0%. Le thread sera alors en attente. De cette manire les autres threads auront accs au processeur comme ci ce thread n'tait pas prsent et les performances systme seront conserves. Un autre exemple est la rcupration des messages. La fonction GetMessage() repasse le contrle au systme ds que tous les messages ont t traits. Si l'application est inactive, le contrle est donc repass immdiatement au systme et la charge processeur affiche est de 0%. Si un thread utilise PeekMessage() dans une boucle, le thread conservera l'accs au processeur mme si aucun message n'est prsent dans la file d'attente. La charge processeur sera donc de 100% mme si l'application est inactive. Ce type d'erreurs ne doit donc surtout pas tre commis sous peine de diminuer gravement les performances globales du systme. Il est galement indispensable de comprendre qu'on ne peut jamais prvoir l'ordre dans lequel les threads recevront l'accs au processeur. Une application multithread devra toujours utiliser les fonctions de synchronisation (qui seront dtailles plus tard) et ne devra jamais tenter de prvoir l'ordre d'excution des instructions. De plus, deux lignes conscutives d'une fonction ne seront pas Page 53 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php forcment effectues la suite car le contrle du processeur pourra tre pass d'autres threads entre temps. Il faut donc bien avoir en tte les contraintes imposes par un programme multithread avant de se lancer dans sa ralisation. 4. Synchronisation Cours thorique : Les fonctions de synchronisation sont indispensables dans un programme multithread. La mauvaise synchronisation d'une application peut conduire des plantages ou des performances totalement dsastreuses. Etudions l'exemple d'un processus contenant deux threads, chacun utilisant le mme tableau global. Un tel programme peut donner des rsultats surprenant dans le cas d'une mauvaise synchronisation. Par exemple, deux valuations conscutives de la mme variable pourront retourner des valeurs diffrentes. Ce genre de comportement n'est gnralement pas souhait. Les deux threads devront alors se synchroniser avant d'accder aux ressources partages. Dans ce cas, la synchronisation consistera en une exclusion mutuelle (d'autres types de synchronisation seront tudies plus tard). L'exclusion mutuelle consiste faire patienter tous les threads demandant l'accs aux donnes partages tant que le thread les utilisant n'a pas dclar qu'il avait termin. Chaque thread utilise une fonction pour signaler qu'il entre dans une section protge. Il utilisera ensuite une seconde fonction pour signaler qu'il sort de la section protge. Si un autre thread demande entrer dans un bloc protg, il sera mis en attente. Le thread qui est entr dans la section protge doit imprativement signaler sa sortie de la section protge sinon les autres threads resteront en attente. L'accs aux donnes partages doit donc tre aussi bref que possible. Les fonctions de synchronisation sont un trs bon moyen de mettre des threads en attente tout en conservant une charge processeur nulle. Si un thread entre dans une phase d'attente, son utilisation processeur restera trs faible durant toute la priode d'attente. Le systme lui accordera de nouveau le contrle ds que cela sera possible. La synchronisation des threads devra donc toujours tre effectue par des fonctions de l'API et jamais par des boucles. L'utilisation de boucles ne repassant pas le contrle au systme rendrait les performances dsastreuses et ralentirait l'excution du processus lui mme car l'accs du processeur serait accord des threads en attente, ce qui ne prsente aucun intrt. 5. Premier exemple Cours thorique : Ce premier exemple montre comment crer un thread. Le point d'entre du thread peut tre n'importe quel fonction respectant un certain typage. Ds la fonction constituant le point d'entre retourne, le thread est termin par le systme. Page 54 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php La fonction CreateThread() qui dmarre le thread retourne un HANDLE ainsi qu'un identifiant. L'identifiant est une valeur de type DWORD. Dans cet exemple, le HANDLE et l'identifiant ne nous seront d'aucune utilit car le thread se terminera spontanment. Le HANDLE ne sera donc mme pas rcupr. Il est indispensable de passer un pointeur non nul pour l'identifiant. Dans le cas contraire, la fonction CreateThread() chouera. L'identifiant ou le HANDLE sont utiliss pour communiquer avec le thread. Notons que l'ensemble des variables globales est accessible la procdure du thread. Tlcharger le projet comment : Projet 08 6. Arrt d'un thread Cours thorique : Le problme de l'arrt d'un thread est dlicat. Un thread peut s'arrter spontanment en terminant sa fonction principale. Dans ce cas, on considrera que l'arrt tait prvu. Dans le cas o l'application doit se terminer, l'arrt ne peut pas tre prvu. Dans ce cas, il existe diffrentes mthodes de terminer les threads en cours. Voici tout d'abord la mthode la plus vidente, et aussi la plus mauvaise. La fonction TerminateThread() fournie par l'API permet de terminer un thread. Le thread est alors immdiatement stopp. Bien que cette fonction soit fournie en standard avec l'API Windows, elle ne sera en pratique quasiment jamais utilise. Son utilisation doit tre rserve des cas trs particuliers et en connaissance de cause. Comme ces cas sont gnralement trs rares on peut considrer que dans un programme bien conu, cette fonction ne devra jamais tre utilise. En effet, cette fonction termine le thread courant sans appeler les fonctions de dsallocation de mmoire. Ceci est d au fait qu'il n'existe pas de point de sortie prdfini un thread. La fonction TerminateThread() ne peut donc pas effectuer la dsallocation de mmoire. Toute la mmoire alloue de manire dynamique sera donc perdue. De mme, les dlls charges par le thread ne seront pas libres... Cette fonction ne doit donc tre utilise que dans le cas d'un thread n'utilisant que des allocations de mmoire statique, et aucune ressource extrieure... Pour viter l'utilisation de cette fonction, chaque thread doit tre muni d'un ou de plusieurs points de sortie. Les systmes de communication inter-threads seront utiliss pour mener le thread vers ce point de sortie. De cette manire la dsallocation de la mmoire ainsi que la libration des ressources extrieures pourra tre effectue. La communication inter-threads sera traite plus tard. On peut tout de mme citer les variables globales et les messages qui sont les principaux moyens de communication entre threads. Bien que plus complexes mettre en place, les mthodes de communication inter-threads seront donc toujours prfrer. L'arrt de l'application entrane automatiquement l'arrt de l'ensemble des threads. Le systme appellera alors la fonction TerminateThread() pour arrter les threads encore actifs. Une application doit donc toujours s'assurer de l'arrt de l'ensemble des threads avant de se terminer. Les Page 55 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php fonctions de synchronisation sont trs adaptes ce type de tche, comme la fonction WaitForMultipleObjects() qui sera vue en dtails plus tard. 7. Rcupration des messages Cours thorique : La rcupration des messages peut avoir deux fonctions : rcuprer les messages de communication lis une fentre ou rcuprer les messages de communication inter-thread. La rception des messages doit tre effectue de manire diffrente selon le type de messages. De manire gnrale, les messages associs des fentres doivent tre traits avec la fonction GetMessage() dans une boucle. Cette fonction permet de passer le contrle aux autres threads du systme ds que la file d'attente est vide. De cette manire les performances sont optimales. De plus, les messages associs aux fentres doivent tre traits rapidement. L'utilisation de cette fonction dans une boucle permet donc un traitement rapide tout en conservant de bonnes performances. Le traitement des messages dans le cadre de la communication inter-thread est diffrent (il sera vu plus en dtail plus tard). Il concerne par exemple l'envoi d'un message d'abandon de la tche courante. La fonction GetMessage() est trs peu adapte au traitement de ce type de messages car elle est bloquante. Le thread sera donc suspendu tant que la file d'attente sera vide. Un thread ne s'occupant pas de la rception des messages associs une fentre a gnralement une tche bien prcise excuter. Il serait donc absurde d'utiliser une fonction bloquante pour le traitement des messages. De plus, ces messages ne ncessitent pas un traitement rapide. On prfrera donc utiliser la fonction PeekMessage() qui elle n'est pas bloquante. Il faudra donc utiliser cette fonction dans une boucle. Comme l'utilisation de cette fonction dans une boucle provoque de graves problmes de performances, il ne faudra pas effectuer cette boucle trop frquemment. Prenons l'exemple d'un thread grant un transfert de fichier. Le transfert de fichier est ralis par une boucle qui lit les donnes puis les crit une place diffrente. Selon la dure moyenne d'une boucle, on dterminera un intervalle d'appel de PeekMessage(). Par exemple, toutes les 100 itrations de la boucle principale, la fonction PeekMessage() sera appele dans une boucle jusqu' puisement de la fille d'attente. Ce type de traitement est adapt un faible nombre de messages, de manire ne pas ralentir le travail de la boucle principale. En supposant que la dure moyenne d'une itration de la boucle soit de 5ms, les messages seront donc traits 2 fois par seconde. Si un message demandant l'arrt de la tche en cours est envoy, il sera trait au plus 500ms plus tard, ce qui est trs acceptable. De plus, cette technique ne nuit que trs peu aux performances de la boucle principale puisque seulement une itration sur 100 sera sensiblement ralentie. Si la rapidit du traitement des messages n'a que peu d'importance on pourra espacer les appels PeekMessage() de manire conserver autant que possibles les performances de la boucle principale. Cette fonction sera donc particulirement adapte au traitement de messages de notifications envoys des thread effectuant une tche prcise. Dans le cas d'un thread effectuant des tches 'sur commande' on prfrera bien entendu la Page 56 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php fonction bloquante GetMessage() puisque le thread sera en attente de tche (et ne consommera donc pas de ressources). 8. Communication inter-threads Cours thorique : La communication inter-threads permet aux diffrent threads d'une application de dialoguer entre eux. Comme la rception des messages peut poser problme, on tentera gnralement de rduire au minimum la communication inter-threads et donc de raliser des threads aussi indpendants que possible. Outre les fonctions de synchronisation qui ne sont pas proprement parler des fonctions de communication, il existe deux moyens principaux de communication : les variables globales et les messages. Les variables globales sont un moyen de communication facile mettre en place. Toutefois, leur utilisation devra tre faite avec rigueur pour viter des problmes dus l'accs de variables partages. De plus dans certains cas, l'utilisation de variables globales peut nuire aux performances. Une variable globale ne doit jamais tre utilise comme moyen de synchronisation. En effet, il est possible de placer la valeur d'une variable globale 0 et d'effectuer une boucle avec un test sur cette variable de manire attendre tant que la valeur reste 0. Un second thread peut alors dbloquer l'attente en modifiant la valeur de cette variable globale. Bien que fonctionnant, cette mthode ne devra jamais tre utilise car elle provoque une forte charge processeur inutile. Dans ce cas, les fonctions de synchronisation devront tre utilises car elles permettent de laisser l'accs processeur au reste du systme tant que le thread est en attente. De plus, deux threads ne doivent jamais accder en criture une mme variable globale. Ce type d'accs peut provoquer des plantages dus la manire dont les variables globales sont utilises (ces variables sont parfois places dans des registres pour optimisation). L'utilisation de variables globales pour la communication inter-threads implique une communication sens unique. Un thread accde cette variable en criture et les autres threads ragissent en fonction de cette valeur. Une telle utilisation ne posera pas de problmes. Le partage de donnes mmoire en criture implique des prcautions particulires comme la synchronisation. Les variables globales pourront donc tre utilises comme drapeau. Par exemple pour signifier un thread qu'il doit se terminer. Si le thread effectue une boucle, on peut placer dans cette boucle un test sur la variable globale. Le thread principal place cette variable une valeur principale au moment de quitter, ce qui provoque l'arrt des autres threads. Notons tout de mme que le thread principale devra attendre que l'ensemble des threads ait termin avant de stopper l'application. En effet, l'arrt de l'application provoque une fermeture de tous les threads en cours (ce qui revient un appel de TerminateThread() ). Il faudra donc placer un systme d'attente dans le thread principal (fonctionnant via d'autres variables globales ou par un systme de messages de notification). Page 57 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php L'utilisation de variables globales est donc une mthode rapide de communication. Toutefois cette mthode est soumise un certain nombre de contraintes qui ne doivent surtout pas tre ignores. Les messages constituent la deuxime mthode de communication. La fonction PostThreadMessage() permet d'envoyer des messages un thread, mme si celui-ci ne gre pas de fentres. Le thread pourra alors utiliser les fonctions GetMessage() ou PeekMessage() pour rcuprer les messages. Bien entendu, il sera inutile d'utiliser les fonctions TranslateMessage() et DispatchMessage() puisque ces messages ne sont pas destins des fentres. Il existe plusieurs mthodes pour crer des messages personnaliss. Tout d'abord, il est possible de dfinir une constante au niveau du prprocesseur (#define). Les valeurs valides pour les messages dfinis par l'utilisateur doivent tre compris entre les valeurs WM_USER et 0x7FFF. La constante WM_USER est dfinie dans les headers Windows. La deuxime mthode consiste utiliser la fonction RegisterWindowMessage(). Cette fonction retourne une valeur utilisable pour un message en fonction d'une chane de caractre. Le systme garantit l'unicit du message en fonction de la chane de caractre. Si deux applications distinctes utilisent cette fonction avec la mme chane, elles obtiendront la mme valeur. Cette fonction doit donc seulement tre utilise dans le cadre de la communication inter-applications, c'est dire au niveau systme. Si les messages sont locaux l'application, il est recommand de dfinir des constantes. Si un thread dsire communiquer une plus grande quantit d'information, il peut joindre au message 2 valeurs (lParam et wParam). Deux choix sont alors possibles. Utiliser ces valeurs comme paramtres ou joindre un pointeur. La mthode utiliser pour joindre un pointeur est la suivante : le thread qui poste le message alloue dynamiquement de la mmoire et poste le pointeur. Le thread metteur ne doit pas dsallouer cette mmoire. C'est le thread rcepteur qui s'occupera de la dsallocation de la mmoire. Cette mthode assure que le pointeur sera toujours valide la rception du message. 9. Sections Critiques Cours thorique : Les sections critiques sont utilises dans le cadre de la synchronisation, de manire raliser des exclusions mutuelles. Elles permettent de crer une section de code qui sera protg, c'est dire qu'un seul thread la fois aura accs ce code. Les sections critiques sont trs utiles pour protger des donnes partages. L'utilisation des sections critiques est relativement simple. Pour crer une section critique, l'application doit dclarer une variable de type CRITICAL_SECTION, gnralement globale. Puis au moins un thread doit initialiser la section critique en appelant la fonction InitializeCriticalSection(). Cette fonction peut tre appele un nombre indfini de fois. Par Page 58 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php exemple, chaque thread qui va utiliser la section critique peut s'assurer qu'elle est valide en appelant cette fonction. Deux fonctions seront ensuite utilises pour signifier l'entre et la sortie d'un bloc protg. La fonction EnterCriticalSection() permet de signaler l'entre dans un bloc protg. Si un thread a dj appel cette fonction et n'est pas encore sorti du bloc protg, cette fonction mettra en attente n'importe quel autre thread. Le mme thread peut appeler cette fonction plusieurs fois sans tre bloqu, mais il devra alors appeler la fonction de sortie de bloc un nombre de fois gal pour indiquer sa sortie du bloc protg. Un thread indique qu'il sort d'un bloc protg en appelant la fonction LeaveCriticalSection(). Un des threads en attente sera alors dbloqu et pourra son tour excuter le bloc protg. 10. Fonctions d'attente Cours thorique : Les fonctions d'attente permettent d'attendre de manire explicite qu'un vnement se produise. Les fonctions d'attente sont WaitForSingleObject() et WaitForMultipleObjects(). Ces fonctions permettent d'attendre que l'tat d'un objet change. Le thread appelant une de ces fonctions peut dfinir un temps maximal d'attente (qui sera ventuellement infini). Diffrents objets peuvent tre utiliss. Par exemple, la fonction WaitForSingleObject() peut tre utilise avec le HANDLE d'un thread. La fonction attendra alors que le thread se termine pour retourner. Pour attendre que plusieurs threads se terminent, une application pourra alors appeler la fonction WaitForMultipleObjects() en spcifiant qu'elle dsire attendre que l'ensemble des threads ait termin leur excution. Ces fonctions mettent le thread qui les appelle en attente et sont donc excellentes au niveau performance. Leur utilisation est donc hautement recommande. Un vnement cr explicitement peut galement tre utilis avec ces fonctions. Ce cas sera tudi dans la partie consacre aux vnements. De mme, le HANDLE d'un processus peut tre utilis de manire attendre qu'il se termine. Dans le cas de la communication inter-threads nous avons vu qu'une application pouvait utiliser une variable globale pour spcifier ses threads qu'ils devaient se terminer. L'application doit ensuite attendre que les threads se terminent avant de quitter. Cette attente sera typiquement ralise par un appel la fonction WaitForMultipleObjects(). Lorsque cette fonction retournera, l'application sera alors assure que l'ensemble des ses threads auront termins et pourra quitter sans risque. 11. Evnements Cours thorique : Page 59 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Les vnements peuvent tre utiliss dans les fonctions d'attente. L'API Windows fournit des fonctions permettant de crer des vnements de manire explicite. L'tat de ces vnements pourra ensuite tre modifi de manire explicite. L'tat d'un vnement peut tre signal ou non signal. Lorsqu'un vnement est en tat non signal, l'appel une fonction d'attente provoquera la suspension du thread appelant jusqu' ce que l'vnement passe en tat signal. Les fonctions CreateEvent() et CloseHandle() peuvent tre utilises pour la cration et la destruction d'vnements. Les fonctions SetEvent(), ResetEvent() et PulseEvent() pourront ensuite tre utilises pour modifier l'tat de l'objet. Les vnements peuvent tre configurs de diffrentes manires. La configuration d'un vnement se fait lors de sa cration. Si l'objet est en mode 'rinitialisation manuelle' il restera en tat signal tant que son tat ne sera pas explicitement rinitialis grce la fonction ResetEvent(). Si plusieurs threads taient en attente de cet objet, ils seront alors tous librs au moment ou l'tat de l'objet passera 'signal'. Si l'objet est en mode 'rinitialisation automatique', son tat sera automatiquement replac 'non signal' ds qu'un thread en attente sera libr. Si plusieurs threads sont en attente d'un mme vnement, alors un seul thread sera libr eu moment ou l'tat sera pass 'signal'. Les vnements sont un moyen pratique de synchroniser plusieurs threads de manire explicite. Les vnements peuvent par exemple tre utiliss pour mettre un thread en attente tandis qu'un autre thread initialise des donnes. Ds que les donnes seront initialises, le ou les threads seront dbloqus par le thread ayant initialis les donnes. Tout comme les smaphores, les vnements peuvent tre nomms ou non. Si l'vnement est nomm, son nom doit tre unique dans l'ensemble du systme. Si une autre application tente de crer un vnement du mme nom, la fonction CreateEvent() retournera un HANDLE sur l'vnement prcdemment cr. Ceci peut tre utilis pour synchroniser des applications entre elles. 12. Smaphores Cours thorique : Les smaphores sont utiliss pour limiter le nombre de threads en fonctionnement. Prenons le cas d'un serveur. On dsire limiter le nombre maximal de connections simultanes. Dans ce cas, on utilisera la synchronisation fournie par les smaphores. Pour utiliser un smaphore, une application doit crer une objet de type smaphore en appelant la fonction CreateSemaphore(). Un smaphore est constitu d'un compteur. Le statut d'un smaphore est signal lorsque le compteur est suprieur 0 et non signal lorsque le compteur est 0. Lors de la cration du smaphore, on spcifie la valeur initiale du compteur, ainsi que sa valeur maximale. La valeur maximale du compteur correspond au nombre maximum de threads qui pourront tre excuts simultannment. Page 60 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Chaque thread dsirant se synchroniser doit utiliser une fonction d'attente (WaitForSingleObject() par exemple). Lorsqu'un thread appelant une fonction d'attente est libr, le compteur du smaphore est dcrment de 1. Ds que la valeur du compteur atteint 0, les threads appelant les fonctions d'attente seront bloqus. Lorsqu'un thread sort d'un bloc synchronis, il doit appeler la fonction ReleaseSemaphore(). Cette fonction incrmente de 1 le compteur du smaphore, permettant ainsi un autre thread d'tre dbloqu. Le compteur du smaphore ne pourra jamais dpasser la valeur maximale spcifie au dbut. De cette manire, on peut trs facilement limiter le nombre de threads effectuant une tche spcifie, et conserver ainsi les performances systme. Une augmentation trop importante du nombre de threads en excution simultane poserait en effet des problmes de performances. Il est possible de spcifier un nom lors de la cration du smaphore. De cette manire, des applications diffrentes peuvent se synchroniser. La premire cre un smaphore nomm. La seconde application tente ensuite de crer un smaphore du mme nom. Ceci aura pour effet de lui retourner un HANDLE sur le smaphore dj existant. Le nom d'un smaphore doit donc tre unique dans l'ensemble du systme. Remarquons qu'un smaphore ne doit pas ncessairement tre nomm. 13. Trois projets simples Cours thorique : Voici 3 projets prsentant l'utilisation des fonctions de synchronisation tudies : sections critiques, vnements et smaphores. Ces 3 projets ont pour but d'expliquer l'utilit des diffrents types de synchronisation. De plus, ils pourront aider comprendre la mise en place d'une synchronisation rudimentaire. Il ne faut jamais oublier que les accs la console Windows doivent tre synchroniss de manire viter des rsultats totalement imprvisibles. Une section critique est parfaitement adapte pour rsoudre ce genre de problmes. Tlcharger les projets comments : Projet 09 - Projet 10 - Projet 11. 14. Performances Cours thorique : La cration d'applications multithread est gnralement trs performante. Toutefois, il faudra faire attention prserver les performances systmes. La synchronisation devra toujours tre effectues grce aux objets fournis par l'API comme les sections critiques, les vnements ou encore les smaphores. Il faut toujours penser qu'un thread inactif doit laisser le contrle au systme et non pas effectuer une boucle. Bien qu'elles fonctionnent parfaitement, les boucles sont dsastreuses au niveau performances systme et on perd rapidement l'intrt de l'application multithread. Page 61 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php Il ne faut pas non plus tenter de segmenter tout prix les tches. Un nombre trop important de threads peut nuire aux performances systme. Le travail qui peut tre effectu par un thread ne doit donc pas tre confi 2 threads. Les applications clients rseau sont souvent trs performantes grce au multithreading. En effet, le fait d'tablir plusieurs connections avec la machine hte permet d'amliorer le dbit rseau. Toutefois, un nombre trop important de threads finira par nuire aux performances de l'application car le systme perd du temps en passant le contrle du processeur un nombre trop important de threads. Les applications sont alors considrablement ralenties. De plus, la ralisation d'applications multithread est souvent source d'erreurs. En effet, il ne faut jamais oublier que des instructions situes dans des threads seront excutes dans un ordre quelconque. Il ne faut jamais tenter de prvoir l'ordre d'excution. A partir du moment o l'ordre d'excution n'est pas certain, il faudra imprativement utiliser les fonctions de synchronisation. Il faut galement comprendre que le fait d'utiliser des fonctions de synchronisation nuit aux performances de l'application. Les fonctions de synchronisation ne doivent donc pas tre utilises si elles ne sont pas ncessaires. De plus, si un nombre important de threads partage des donnes, le temps d'attente pour obtenir l'accs ces donnes va rapidement devenir important. Chaque thread passera donc un temps important attendre, ce qui est d'autant plus problmatique si la tche qu'il doit effectuer est courte. Il faut galement viter imprativement des entres - sorties successives dans des sections critiques. En effet, si le temps d'excution entre les sections critiques est trop court, le thread aura trs rapidement un temps d'attente suprieur son temps de calcul. Ce problme se manifestera encore plus avec un nombre important de threads. Si un thread doit faire des accs successifs des donnes partages, il peut copier ces donnes dans un espace non partag ou rester dans la section critique le temps d'excuter sa tche. Si le temps d'excution de la tche est court, un thread aura tout intrt ne pas sortir de la section critique. Il faut donc imprativement prendre en compte les questions de performance lors de la ralisation d'applications multithread. De plus, les plantages dus des problmes de partage de donnes ne surviendront pas forcment lors des tests car les threads ne s'excutent jamais de la mme manire. Il faut donc prvoir les problmes de partage et non pas compter sur les tests pour les dceler. Page 62 de 62 23/11/03 http://bob/php/tutapiwin/cur/full.php