API Windows
API Windows
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 2 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 3 de 62 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"
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 4 de 62
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);
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 5 de 62
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().
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 6 de 62 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>
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,
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 7 de 62
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 8 de 62
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>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { 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); HWND hWnd; hWnd=CreateWindowEx( WS_EX_CLIENTEDGE,
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 9 de 62
"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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 10 de 62 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().
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 11 de 62 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:
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 12 de 62 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; } }
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 13 de 62 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.
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)
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 14 de 62 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"
//---------------------------------------------------------------------------
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: 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; } ReadFile(hFile,buf,500,&Read,NULL); CloseHandle(hFile); buf[Read]=''; SetDlgItemText(Dlg,IDC_TEXT,buf); return 0;
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 15 de 62
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>
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
http://bob/php/tutapiwin/cur/full.php
23/11/03
//--------------------------------------------------------------------------USERC("Dialog1.rc"); //---------------------------------------------------------------------------
#pragma argsused //ajout automatiquement mais pas ncssaire. WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { DialogBox(hInstance,(LPCTSTR)DIALOG_1,NULL,(DLGPROC)MainProc); return 0; }
LRESULT CALLBACK MainProc(HWND Dlg,UINT message,WPARAM wParam,LPARAM ) { int Select; char buf[501]; HANDLE hFile; DWORD Read; switch(message) { case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { case IDC_LIRE: 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; } ReadFile(hFile,buf,500,&Read,NULL); CloseHandle(hFile); buf[Read]=''; SetDlgItemText(Dlg,IDC_TEXT,buf); return 0;
case IDOK: EndDialog(Dlg,0); return TRUE; case IDCANCEL: EndDialog(Dlg,0); return TRUE; }
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 17 de 62
default: return FALSE; } }
Fichier "Ressource.h"
#define DIALOG_1 1
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 18 de 62 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"
HINSTANCE hInst;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { hInst=hInstance; DialogBox(hInstance,(LPCTSTR)IDD_MAIN,NULL,(DLGPROC)MainProc); return 0;
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 19 de 62
}
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:
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 20 de 62
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 21 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 22 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 23 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 24 de 62 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).
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 25 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 26 de 62
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 27 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 28 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 29 de 62 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.
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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 30 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 31 de 62 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 }
Chapitre 3
Les fentres 1. Fentres et botes de dialogue
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 32 de 62
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 33 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 34 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 35 de 62
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.
6. Contexte d'affichage
Cours thorique :
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 36 de 62 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...).
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 37 de 62 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().
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 38 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 39 de 62
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); }
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 40 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 41 de 62
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); }
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 42 de 62
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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 43 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 44 de 62 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);
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 45 de 62
}
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.
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 46 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 47 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 48 de 62 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.
5. Enumration de fichiers
Cours thorique :
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 49 de 62 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...).
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 50 de 62 system32).
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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 51 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 52 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 53 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 54 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 55 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 56 de 62 fonctions de synchronisation sont trs adaptes ce type de tche, comme la fonction WaitForMultipleObjects() qui sera vue en dtails plus tard.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 57 de 62 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).
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 58 de 62 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
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 59 de 62 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.
11. Evnements
Cours thorique :
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 60 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 61 de 62 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.
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.
http://bob/php/tutapiwin/cur/full.php
23/11/03
Page 62 de 62 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.
http://bob/php/tutapiwin/cur/full.php
23/11/03