- Chap.1: Création d'une fenêtre -


Ce chapitre risque de vous rebuter quelque peu, mais il faut savoir qu'en fait, il est le plus difficile à assimiler. Cependant je vais vous faciliter la tâche. En effet seront présentées ici des fonctions comportant parfois une douzaine d'arguments. Je prendrai alors le parti suivant: celui de ne pas documenter les arguments qui servent rarement. Si le coeur vous en dit, vous pourrez toujours vous y intéresser à travers la documentation Microsoft.

De même, il faut savoir que le squelette présenté ici sert quasiment pour 90 % des applications. Il semble donc plutôt évident que s'en servir comme base d'un programme est la meilleure façon de commencer un projet.




- 1.1: Fonctionnement d'une application -

Avant toute chose, il faut connaître la manière dont se comporte une application Windows. Celle-ci est identifiée par une variable nommée "variable d'instance". Le type donné par Windows est HINSTANCE. Nous pourrons par exemple déclarer une variable d'instance par :




  hInstance HINSTANCE ?          
  

Où 'hInstance' est le nom qu'on aurra choisi pour identifié cet instance plus tard, alors que 'HINSTANCE' est le type de cette variable. On peut donc choisir un autre nom que 'hInstance'. Tel que:
hInstanceProgPrincipal HINSTANCE ?

Une application est composée d'une fenêtre principale, laquelle contient des fenêtre filles, lesquelles peuvent elles-même contenir des fenêtres filles, etc ... La dépendance mère/fille se matérialise par la propagation de la prise de focus, de la destruction des fenêtres filles quand la fenêtre mère est détruite, etc ... (il serait en effet bizarre par exemple que Notepad soit quitté et que le contrôle d'édition de texte reste flottant). Chacune de ces fenêtres sont-elles aussi identifiées par une variable nommée "handle". Le type donné par Windows est HWND. Nous pourons par exemple récupérer le handle d'une fenêtre à l'aide d'une variable déclarée comme suit :


  LOCAL hwnd:HWND   

Ici encore c'est bien 'hwnd' qui est la variable alors que 'HWND' est le type de variable. Ici on déclara donc la variable 'hwnd' (qu'on aurait pu appeler 'HandleFenetrePrincipale' par exemple) en tant que variable de type: handle. On aurrait donc tout aussi bien pu mettre,
LOCAL HandleFenetrePrincipale:HWND

Voilà tout pour la manière dont Windows identifie tout son beau monde. Plus tard nous apprendrons que Windows identifie le reste de son environnement à l'aide de contexte de périphérique (imprimante, clavier, affichage zone écran, etc ...).

C'est bien beau de savoir comment Windows identifie les fenêtres, mais comment permettre à l'application de dialoguer avec l'OS et les autres fenêtres ? Et bien tout simplement à l'aide d'envoi de messages. Ceux-ci sont destinés soit à Windows, soit à une autre fenêtre. Toute fenêtre a alors un comportement défini face à la réception de ceux-ci.
Par exemple, prenons une fenêtre texte d'handle hWndText affichant le résultat d'une compilation. Celui-ci devient visible par clik sur un bouton "Afficher Resultat" d'handle hWndButton. Il faut donc que lorqu'un clik se produise sur le bouton, un message soit envoyé à la fenêtre texte pour qu'elle affiche le résultat ! Ces envois se font à l'aide de la fonction SendMessage() que nous détaillerons plus loin. Pour info, nous aurons dans notre cas :




  invoke SendMessage, hWndText, WM_SETTEXT,IDM_GETTEXT,0  

Reste à savoir que nombre de messages sont générés par Windows lui-même, ou situés dans le code des contrôle communs comme le simple clik d'un bouton qui envoi le message WM_CLICK.


- 1.2: Inclusions et prototypes -

Première chose à savoir, les inclusions nécessaires. Déclarez toujours 'windows.inc'. Par contre vous n'aurez besoin de déclarer 'user32.inc' uniquement si vous faites appel à une de ses fonctions API (tel que SetMessage...) c'est la même chose pour Kernel (ex: ExitProcess) et les autres...




  include \masm32\include\windows.inc  
  
  include \masm32\include\user32.inc  
  include \masm32\include\kernel32.inc  
  includelib \masm32\lib\user32.lib  
  includelib \masm32\lib\kernel32.lib  
  ...

C'est quand même beau la vie ! Vous avez alors à disposition tout le nécessaire pour programmer. Bien qu'il soit possible d'utiliser les fonctions C-ANSI, il est préférable de s'adapter à Windows, à l'aide des fonctions propres à Windows (comme "lstrcmp()" au lieu de "strcmp()", ou encore "GlobalAlloc(GPTR, ...)" au lieu de "malloc(...)").

Ensuite le prototype principal. Pas de "int main(int argc, char **argv);", mais celui-ci :


  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD  


Où HINSTANCE, LPSTR, et DWORD représentent les types de ces données.
hInst et hPrevInst seront dont deux handles d'instances
CmdLine sera donc une chaîne de caractères (du texte en fait)
et CndShow un nombre de dimension DWORD donc sur 32bit (4 octets)

Voici le détail des arguments passés par windows au lancement de l'application :

- hInst : la variable d'instance de l'application (Windows nous le donne gentillement)
- hPrevInst : c'est rigolo, çà sert à rien en fait :) (toujours 0)
- CmdLine : Une chaine représentant les arguments en ligne (utile pour récupérer le nom d'un fichier lors d'un double-clik sur celui-ci lorsque son extension est associée à notre programme).
- cmdShow : La manière dont notre application doit être affichée (minimisée, normale, agrandie ...)

Il est utile de récupérer la variable d'instance. Notre programme basé sur nos connaissances actuelles a donc cette forme :


  ...
  ...
  ...
  include \masm32\include\windows.inc  
  
  include \masm32\include\user32.inc  
  include \masm32\include\kernel32.inc  
  includelib \masm32\lib\user32.lib  
  includelib \masm32\lib\kernel32.lib  
  
  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD  
  
  
  .DATA
   ...
   ...
   
  .DATA? 
  hInstance HINSTANCE ? 
  CommandLine LPSTR ?
   ...
   ...
   
   
  .CODE    
   ...
   ...
   ...            
  invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT          
   ...
   ...
   


- 1.3: Classes de fenêtres -

Nous allons maintenant créer notre fenêtre. Une fenêtre est un objet, et comme tout objet (pour peu que vous connaissiez la programmation objet), celui-ci est instancié à partir d'une classe. Pour ceux qui ne maitrisent pas ce genre de concept, il s'agit en fait d'un "moule" à partir duquel notre fenêtre saura prendre forme (si vous êtes rebutez, partez du principe qu'une classe est une sorte de structure dont les valeurs définnisent la "forme" des objets qui s'y réfèrent).
Pour cela nous vient en aide une classe de fenêtre. Il nous faut donc initialiser certaines de ses variables (je réserve le terme de "propriété" aux experts). Il est préférable de le faire à l'aide d'une fonction pour la lisibilité du code. Sachez, avant la présentation du programme, que le type "classe de fenêtre" sous Windows est WNDCLASS.



Voici notre fonction d'initialisation d'une classe de fenêtre :


 WNDCLASSEX STRUCT DWORD 
  cbSize            DWORD      ?	   ;size = SIZEOF WNDCLASSEX
  style             DWORD      ?	   ;style = 0 ou CS_HREDRAW 
  lpfnWndProc       DWORD      ?	   ;lpfnWndProc = WindowProcedure (OFFSET WndProc)  
  cbClsExtra        DWORD      ?	   ;cbClsExtra = 0
  cbWndExtra        DWORD      ?	   ;cbWndExtra = 0
  hInstance         DWORD      ?	   ;hInstance = hInst
  hIcon             DWORD      ?	   ;hIcon = invoke LoadIcon....
  hCursor           DWORD      ?	   ;hCursor = invoke LoadCursor....
  hbrBackground     DWORD      ?	   ;hbrBackground = COLOR_3DFACE +  1
  lpszMenuName      DWORD      ?	   ;lpszMenuName = 0
  lpszClassName     DWORD      ?	   ;lpszClassName = MonNomDeClasse
  hIconSm           DWORD      ?	   ;hIconSm = NULL
 WNDCLASSEX ENDS 	


Voici le détail des champs que nous modifions :

- style : le style de notre fenêtre. Positionnez à zéro.
- lpfnWndProc : il s'agit ici de passer le nom d'une fonction très utile, celle qui va gérer la réception des évènements !!! J'en présente une de base à la fin de ce chapitre.
- cbClsExtra et cbWndExtra : servent à l'allocation d'octets supplémentaires. Mettez à zéro.
- hInstance : Fournir l'instance de l'application.
- hCursor : Quel pointeur voulez-vous ? ben celui par défaut ! Cool, la fonction LoadCursor() nour le permet !
- hbrBackground : Si on ne modifie pas ce champou qu'on met (COLOR_WINDOW + 1), le fond de notre fenêtre sera blanc. Ici, on affecte la couleur système de fond de fenêtre.(COLOR_3DFACE + 1)
- lpszMenuName : Mettez zéro. On préfèrera placer un menu d'une autre manière.
- lpszClassName : Le nom que vous donnez à la classe. Préférez utiliser une variable globale de pointeur sur char, le nom de la classe sera réutiliser.
- hIcon : Nous chargerons ici une icône dans le 2ème chapitre.

Ainsi, notre WinMain() s'étoffe :


  								
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
	{  
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
    	hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe
    
	}

Nous avons une instance d'application et une classe de fenêtre bien définie, ce n'est pas si mal ! C'est même dejà beaucoup !!!


- 1.4: Phase de création principale -

En effet, tout va maintenant très vite ! Il nous reste à créer notre fenêtre à l'aide de CreateWindow(), qui retourne le handle de la fenêtre créée (lequel doit être récupéré dans une variable globale). Voici comment cela se passe :




  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
	{  
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
    	hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe
    
	

    	invoke CreateWindowEx("MonNomDeClasse",	  	 	  ; Structure 
                             // le nom de la classe de fenêtre	  ; CreateWindow  
                        "Programmation API Win32",
                             // le titre de la fenêtre
                        WS_OVERLAPPEDWINDOW,
                             // le style de la fenêtre
                        CW_USEDEFAULT, CW_USEDEFAULT,
                             // ses positions x et y au début
                        300, 200,
                             // la taille dx et dy de la fenêtre 
                        NULL,                 
                             // le handle de sa fenêtre parent
                        NULL,
                             // réservé au chargement d'un menu
                        hInst,     
                             // l'instance de l'appli
                        NULL   
                             // mettre toujours à NULL
                       );

    	invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre
	}

Rien de bien compliqué je pense. Sinon, regardez ce que dit la doc Microsoft sur cette fonction. Pour les styles de fenêtres, ils sont innombrables. Ils se combinent à l'aide de l'opérateur OR. En général WS_OVERLAPPEDWINDOW convient dans la plupart des cas. Sinon, voici une liste peu exhaustive (le site proposera par la suite un ensemble de références plus consistant) :

- WS_BORDER : crée une fenêtre avec bordure.
- WS_CAPTION : crée une fenêtre avec barre de titre.
- WS_OVERLAPPED : combine les 2 précédents.
- WS_SYSMENU : ajoute le menu système en haut à gauche dans la barre de titre.
- WS_THICKFRAME : Possibilité de changer la taille de la fenêtre à la souris.
- WS_MINIMIZEBOX : Bouton "réduire" en haut à droite.
- WS_MINIMIZEBOX : Bouton "agrandir" en haut à droite.
- WS_OVERLAPPEDWINDOW : Combinaison de tous les styles précédents.
- WS_MAXIMIZE : La fenêtre est créée avec la taille maximum.
- et bien d'autres encore ...

Pour le dernier paramètre, sachez qu'il sert à joindre une chaine de caractère au message WM_CREATE que recevra notre fenêtre lors de sa création. Personnellement, il faudra m'expliquer l'utilité de ce système.


- 1.5: Boucle évènementielle -

C'est bien beau, mais pour l'instant, notre programme quitte directement. En fait, il ne reçoit même pas les messages de l'OS. Pour cela, nous nous servons d'une boucle classique, que nous ne modifierons jamais :




  HWND hWnd;

  WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
	{  
        Structure WNDCLASS wndClass	; pour initialiser la Window Class
    	hInst = hInstance  		; en retour
        invoke RegisterClassEx, addr wc ; Pour enregistrer cette classe
        	Strucure CreateWindowEx	   		;pour initialiser la fenêtre
    	invoke ShowWindow, hwnd,CmdShow		; on affichee notre fenêtre
	

    // on reçoit les messages en boucle à l'aide de GetMessage()
    .WHILE TRUE                                                         
                invoke GetMessage, ADDR msg,NULL,0,0	; // msg reçoit les messages   
                .BREAK .IF (!eax)  			; // Sort de la boucle si eax=-1  
                invoke TranslateMessage, ADDR msg 	; // on traduit le message reçu
                invoke DispatchMessage, ADDR msg 	; // et on l'envoie vers notre 
					   		; procédure de gestion de msgs
   .ENDW 


   mov     eax,msg.wParam		  		; // Retoune le code de sortie dans eax 
        
	}
ret							; // on termine la procédure 
WinMain endp  						; WinMain
    // on renvoie désormais le code d'arrêt contenu dans l'objet msg 
    
  

Je crois ne pas pouvoir être plus explicite. C'est de toute façon une méthode standard. Inutile de s'intéresser ici à la composition de l'objet MSG, ou au fonctionnement exacte de la réception des messages. Pour plus de compléments, référez-vous à la documentation. Pour l'heure, nous nous contenterons de prendre la boucle telle qu'elle.


- 1.6: Fonctions évènementielles -

Je vous ai parlé d'une fonction évènementielle dans l'initialisation de la classe de fenêtre sans vraiment la définir. Celle-ci repond aussi à un prototype, et permet de lancer différents traitements selon le type de message reçu. La voici incluse dans la version finale de notre programme :




Tout WinMain


WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    .IF uMsg==WM_DESTROY                           ; si l'utilisateur ferme notre fenêtre (parce qu'on vient de recevoir un message WM_DESTROY)...alors 
        invoke PostQuitMessage,NULL             ; on quitte notre application 
    .ELSE 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam     ; Traitement par défaut de tous les autres messages qui ne nous intéressent pas. 
        ret 
    .ENDIF 
    xor eax,eax 
    ret 
WndProc endp 


  }

Le prototype d'une fonction évènementielle est toujours le même. Les arguments reçus sont :

- • hwnd : Le handle de la fenêtre concernée par le message
- • uMsg : La référence du message
- • wParam : le 1er paramètre du message
- • lParam : le second

Pour les différents messages reçus, je n'en ferai pas la liste ici. Nous aurons l'occasion de les rencontrer tout au long de ce tutorial (WM_CLOSE, WM_CREATE, WM_SIZE, WM_MOVE, etc ...). En ce qui concerne le traitement particulier réservé à WM_DESTROY, il permet de fermer l'application à l'aide de la fonction PostQuitMessage() (qui en fait envoie le message WM_QUIT à notre fenêtre (et oui, nombre de fonctions remplacent l'utilisation de SendMessage() pour faciliter la programmation)). En effet, WM_DESTROY est un message sans traitement par défaut qui est envoyé lors d'un clik sur la croix en haut à droite, ou lors du choix "quitter" dans le menu système en haut à gauche. Il nous faut donc notifier la fermeture de l'application nous même.