Explication détaillée de la fonction WinMain
Détail des arguments de la fonction WinMain
Le premier argument est le handle de l'instance du programme, bref il s'agit d'un entier qui joue le role d'identificateur. Lorsqu'un programme démarre sous Windows, ce dernier lui attribue un nombre qui va lui permettre d'identifier le programme, c'est ca notre hInstance.
Le second argument ne sert à rien. Eh oui ;p. Il n'est la que par souci de compatibilité avec les précédentes versions de Windows. Sa valeur sera toujours NULL.
Le troisième argument est un pointeur sur une chaine de caractère qui contient la ligne de commande exécutée pour lancer le programme, nom du programme omis. Bref, il n'y aura quelque chose à récupérer là que lorsque vous lancerez votre programme à partir de la ligne de commande, ou dans d'autres cas particuliers que je ne détaillerai pas pour le moment. Notez bien que le nom du programme n'est pas inclus dans cete chaine, si vous voulez tout de même le récupérer, vous pouvez utiliser la fonction GetCommandLine.
Le dernier argument est un flag qui vous permet de connaître l'état initial de votre application, vous n'avez pas à vous en préoccuper pour le moment.
Voila donc pour les arguments de notre fonction WinMain. Nous allons maintenant étudier pas à pas cette même fonction. Pour commencer, nous déclarons et initialisons quelques variables: on déclare une variable de type HWND (comprendre Handle de WiNdoW, bref l'identificateur de notre fenêtre principale), puis une structure de type MSG, qui va nous permettre de récupérer le premier message de notre file de messages pour ensuite l'envoyer à notre fonction de rappel, puis nous déclarons une instance de la structure WNDCLASSEX. Cette structure va nous permettre de décrire la classe de fenêtre de notre fenêtre parent. C'est là que tout se complique un petit peu :).
Les classes de fenêtre
Avant toute chose, il faut savoir que sous Windows TOUT est fenêtre. N'importe quel bouton, menu, barre de défilement, tout est une fenêtre et a à fortiori été créée par la même fonction: CreateWindowEx, que l'on aura appelée explicitement ou non.
En fait, il y a pour chaque classe de fenêtre une fonction de rappel. Ces fonctions de rappel définissant comment la fenêtre doit agir en fonction des entrées de l'utilisateur, le fait d'associer à une fenêtre une fonction CALLBACK revient en fait à lui assigner un comportement par défaut. Je vais essayer d'illustrer cela avec un exemple qui vous permettra, je pense, de mieux saisis la chose.
Prenons l'exemple du bête bouton. Un bouton, que ca soit dans Word, Paint, ou n'importe quel autre programme Windows, garde toujours certaines caractéristiques: il est gris, donne une impression de relief, s'enfonce quand on clicke dessus... Bref, tout cela est géré par une classe qui est prédéfinie dans Windows. En gros, quand on crée un bouton, on va créer une fenêtre selon la classe fenêtre bouton. C'est valable également pour les barres de défilement ou n'importe quel autre élément de ce genre.
Bon, cela dit, pour la fenêtre principale de notre application, il n'existe pas de classe prédéfinie. Le tout va donc être d'en créer une nouvelle. Cela se fait en deux temps: tout d'abord on remplit les champs de notre structure WNDCLASSEX, et ensuite on fait appel à la fonction RegisterClassEx. Je vais donc décrire les différents champs de cette structure:
- cbSize : ce champ contient la taille, en bytes, de la structure WNDCLASSEX. Il faudra toujours l'initialiser à sizeof(WNDCLASSEX), même si cela peut paraître inutile.
- style : Spécifie le(s) style(s) de la classe. Ca peut prendre un paquet de valeurs, que l'on peut combiner, je ne vais pas toutes les énoncer ici.
- lpfnWndProc : Pointeur sur la fonction CALLBACK de la fonction.
- cbClsExtra : initialiser ce champ à 0.
- cbWndExtra : initialiser ce champ à 0
- hInstance : Placer dans ce champ l'instance de l'application
- hIcon : Placer dans ce champ un handle d'icone, si l'on désire assigner une icône à sa fenêtre. Nous verrons par la suite comment récupérer des handles d'icônes.
- hIconsm : Placer ici un handle d'icone, pour les icones de petite taille.
- hCursor : Handle sur un curseur pour cette classe. C'est le curseur qui sera utilisé lorsque la souris passera au dessus dela fenetre. On obtient ce handle grâce à la fonction LoadCursor, dont le premier argument est l'instance du programme et le second un identificateur pour désigner quel curseur utiliser, IDC_ARROW correspond à la flèche standard. Lorsque l'on désire charger un curseur "standard" (cad non personnalisé) il faut placer le premier paramètre à NULL.
- hbrBackground : Handle pour le pinceau qui sera utilisé pour effacer la fenetre. En l'occurence, notre arrière plan doit être blanc, on va donc récupérer un handle sur un pinceau blanc avec la fonction GetStockObject, dont le seul argument est l'objet que l'on désire récupérer, ici le pinceau blanc (WHITE_BRUSH).
- lpszMenuName : Chaine spécifiant le nom d'un menu déclaré dans le fichier de resources. En l'occurence, on n'utilise pas de menu, on initialise donc ce membre à NULL.
- lpszClassName : Nom de la classe. Chaque classe est repérée par son nom; c'est d'ailleurs ce nom que l'on va utiliser lors de la création de notre fenêtre.
Pfiou, ca y est!. Bref, y'a plein de champs, qui peuvent prendre un tas de valeurs. Je me contente pour le moment d'expliquer notre programme avant de détailler chacune de ces valeurs, sinon on en finira jamais :). Bref, maintenant que cette structure est initialisée, il faut faire appel à la fonction RegisterClassEx afin de l'enregistrer. Attention, n'oubliez pas d'enregistrer vos classes, sinon le programme ne fonctionnera pas et le compilateur ne détectera pas d'erreur! RegisterClassEx renvoie 0 si l'enregistrement échoue. On teste donc cette valeur de retour, et si c'est le cas (si l'enregistrement échoute), on affiche une boite de dialogue affichant un message d'erreur, et on renvoie 0 à la fonction WinMain pour terminer le programme.
Attention: Il faut bien renvoyer 0 si une erreur survient avant la boucle de messages... J'ai un livre devant moi où ils renvoient 1, mais c'est bien zéro qu'il faut renvoyer, j'ai MSDN avec moi :)
On crée ensuite notre fenêtre, j'ai détaillé les arguments dans la source. Attention: la fenêtre est créée, mais elle n'est pas affichée tout de suite. Pour l'afficher, on utilise la fonction ShowWindow, dont le premier argument est le handle de la fenêtre dont on veut modifier l'état, et le deuxième l'état où on veut mettre la fenêtre: SW_SHOW c'est pour la mettre en état "visible". Notez que c'est la même méthode pour cacher une fenêtre sans la détruire, au lieu de mettre SW_SHOW, on mettra SW_HIDE :). De plus, comme on est pressé, on appelle la fonction UpdateWindow, qui va dire de poster un message WM_PAINT à la fenêtre dont le handle est spécifié comme argument, mais sans passer par la file de messages =).
Pour plus d'informations sur les classes de message n'hésitez pas à aller sur: http://msdn.microsoft.com/ pour consulter les détails de la fonction WNDCLASSEX.
La boucle de messages
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Voila, maintenant on retrouve notre boucle qui va récupérer les messages de la file de messages :) La fonction GetMessage nous permet de récupérer le message qui est en tête de cette file, et va le placer dans la structure MSG dont l'adresse lui est passé comme premier argument. Le deuxième argument spécifie la fenêtre dont on veut récupérer les messages. En l'occurence, si on met le paramètre à NULL, GetMessage va renvoyer tous les messages envoyés à n'importe quelle fenêtre du thread.
Les deux derniers paramètres permettent de spécifier des valeurs minimales et maximales pour les messages que l'on veut récupérer, retenez pour le moment que si on les met tous les deux à 0 on récupère tous les messages.
GetMessage renverra toujours true, quel que soit le message lu, jusqu'à ce qu'elle recoive le message WM_QUIT, auquel cas elle renverra 0, et la boucle de message se terminera. On bouclera donc jusqu'a ce que notre application recoive le message WM_QUIT :).
La fonction TranslateMessage: c'est compliqué :). L'explication de cette fonction ne rentre pas dans le cadre de notre introduction. Mettez-le, point.
DispatchMessage: cette fonction va envoyer le message lu par GetMessage à notre fonction de rappel. Elle va envoyer les champs hwnd, message, wParam et lParam de notre structure MSG comme paramètres à notre fonction CALLBACK.
Voilà, lorsque notre boucle se termine, on renvoie le champ wParam de notre struture MSG et on rend la main à windows, comme spécifié dans msdn :).