La programmation Windows
Introduction
Windows est un système d'exploitation offrant aux programmeurs des APIs permettant d'intégrer au maximum leurs applications dans l'environnement du système d'exploitation. Ces trois principales APIs sont : user32.dll, kernel32.dll et gdi32.dll. La programmation windows consiste donc simplement à utiliser les APIs Windows pour créer des applications intégrées au systèmes d'application. Il y a deux façons de programmer des applications windows ; la première est d'appeler le fichier windows.h et de gérer toute l'application, la deuxième consiste à utiliser une librairie mise au point par Microsoft, la MFC (Microsoft Foundation Class) qui nécessite de nouveaux DLLs à inclure à votre programme (MFCxxx.dll). L'utilisation de la MFC permet de simplifier le développement des applications windows en offrant aux développeurs un nombre important de composants et de classes prédéfinis.
La programmation classique
le programme principal :
Voici le code permettant de définir le point d'entrée dans le programme, à la différence d'un code en C++ classique, la fonction utilisée est la fonction WinMain:
Comme vous pouvez le constater, la définition de la fonction indique que le programmeur à la possibilité de renvoyer un code d'erreur lors d'une erreur à l'exécution.
- Les premier paramètre correspond à un handle représentant le programme en mémoire.
- Le deuxième vaut NULL pour les applications 32 bits.
- Le troisième correspond aux paramètres passés en arguments de la ligne de commande, c'est une chaîne de caractères contenant la partie située après le nom du programme sur la ligne de commande.
- Le dernier est un paramètre utilisé lors de l'appel à la fonction ShowWindow. Il correspond à la taille de la fenêtre principale lors de son ouverture.
création d'une fenètre :
La première chose à faire lorsque l'on veut créer une fenêtre est de créer une classe pour notre fenêtre. Cette classe n'a rien à voir avec les classes habituelles en C++. Cette classe sert à sauvegarder des informations sur la fenêtre comme ses icônes, le curseur ou la couleur du fond. Cependant, ces attributs pourront être également modifiés ultérieurement.
- Exemple de création de classe de fenêtre :
WNDCLASSEX WndClass; g_hInst = hInstance; // représente l'instance de l'application WndClass.cbSize = sizeof(WNDCLASSEX); WndClass.style = NULL; WndClass.lpfnWndProc = WndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = g_hInst; WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); WndClass.lpszMenuName = NULL; WndClass.lpszClassName = "Nom_de_ma_classe"; WndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
Un des champs le plus important est le champ lpfnWndProc qui est un pointeur sur une fonction qui à pour définition :
Cette fenêtre sert à gérer les messages concernant cette fenêtre. En effet, Windows gère le multitâches gràce à une file de messages. Lorsqu'un événement (clic sur la souris, redimensionnement d'une fenêtre, ...) intervient, un message est généré et ce message doit être récupéré et envoyé à la fenêtre à qui il est adressé. C'est le rôle de cette fonction. Les paramètres de cette fonction sont :
- hwnd : la fenêtre qui est concérnée
- Message : le message reçu
- wParam, lParam : des informations supplémentaires liées au message (position de la souris par exemple)
Une fois notre classe définie, il faut l'enregistrer au moyen de la fonction RegisterClassEx() qui permet de détecter les éventuelles erreurs.
La classe étant enregistrée, nous pouvons créer une fenêtre qui l'utilisera gràce à la fonction CreateWindowEX :
La création de la fenêtre nous permet d'obtenir un handle hwnd qui sera utilisé lors du déroulement de l'application. Les différents paramêtres de cette fonction servent à définir le comportement de la fenêtre dans l'environnement.
Une fois la fenêtre créée, il ne reste plus qu'à l'afficher :
à la dessiner une première fois
et à rentrer dans la boucle permettant de gérer les messages :
La seule façon de quitter sera alors d'envoyer le message PostQuitMessage() à la fenêtre ce qui aura pour effet de renvoyer 0 à GetMessage(...) et de quitter le programme.
La boucle de messages :
Une fois la fenêtre créée, la gestion du bon déroulement du programme revient à la fonction WndProc qui va gérer les messages qui lui sont envoyés. Cette fonction sera calquée sur ce type :
Son fonctionnement est assez simple, on récupère les différents événements qui arrivent (les messages) et suivant le message reçu, on effectue la bonne opération. Il existe un grand nombre de messages, ils possèdent des noms normalisés qui commencent par WM_..., les mots étant écrits en majuscules.
Le fichier de ressources :
La plupart des programmes Windows possèdent des icônes à charger, des menus, ... Afin de hiérarchiser ces données et de simplifier l'utilsation de fichiers externes, nous avons à notre disposition un système de scripts permettant de créer des ressources (*.rc) qui comprend des définitions de constantes, de menus ou d'autres éléments intervenant lors de l'exécution. Voici un exemple de fichier qui peut être utilisé :
-
script.rc:
#include <windows.h> #include "toto.rh" MYICON ICON "my_icon.ico" MYMENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILE_EXIT END END
-
toto.rh:
#define CM_FILE_EXIT 9001
Cependant, la création de fichier de ressources est maintenant grandement simplifiée par les environnements de développement graphique existants sur le marché. Une méthode permettant la simplification de l'utilisation des ressources créées avec l'éditeur dans un programme Windows consiste à utiliser des identifiants de type chaînes de caractères ("ID_TOTO" au lieu de ID_TOTO), en effet, l'utilisation des ressources dans un programme windows classiques utilise des fonctions utilisant des chaînes de caractères en paramètres pour charger les resources.
Création d'un menu (méthode simplifiée) :
Avec l'editeur de ressources, nous avons la possibilité de créer graphiquement un menu. La véritable méthode consiste :
- soit à passer par le fichier de ressources
- soit à utiliser les fonctions CreateMenu() et CreatePopupMenu()
Le résultat est le même. Le but est d'associer des commandes (norme : CM_...) à des éléments du menu. Par exemple, on peut associer la commande CM_QUIT au menu Fichier\Quitter. Ces commandes seront récupérées dans la boucle de messages, lorsque le message WM_COMMAND sera récupéré, avec un test de cette forme :
L'utilisation de ce menu sera faite gràce au champ lpszMenuName de la classe de la fenêtre.
Boîtes de dialogue :
En plus des boîtes de dialogues prédéfinies (utilsées avec l'API MessageBox), nous avons la possibilité de créer nos propres boîtes de dialogue. De la même façon que pour les menus, la méthode consiste à créer la boîte de dialogue dans le fichier de ressources, en plaçant des contrôles (boutons, textfield, ...) et en leur associant des identifiants que l'on pourra ensuite contrôler. Cependant, l'utilisation de l'éditeur de ressources permet d'accélérer leur création. On pourra appeler une boîte de dialogue avec la fonction :
qui renverra les identifiants spécifiez par les boutons. Supposons que nous ayons placés un bouton et que nous lui ayons associé l'identifiant ID_OK, alors l'appel à la fonction DialogBox nous renverra ID_OK. Il est intéressant de noter le paramêtre AboutDlgProc qui correspond à un pointeur sur fonction. Cette fonction nous permettra de gérer les messages destinés à la boîte de dialogue.
Graphisme :
Lorsque vous dessinez ou appelez des fonctions graphiques avec vos fenêtres, vous utilisez GDI (Graphics Device Context). Cela implique l'utilisation de Device Context. Ce sont des handles génériques qui peuvent représenter des fenêtres, des bitmaps, des imprimantes, ... De cette façon, vous pouvez dessiner de la même façon sur une imprimante et sur un écran.
La partie essentielle consiste à créer un DC compatible avec la fenêtre dans laquelle on veut dessiner avec le code suivant par exemple :
de cette façon, nous avons le DC corrspondant à la fenêtre et un DC dans lequel on va pouvoir dessiner. Par la suite, on peut associer un bitmap à ce DC supplémentaire:
et pour finir, nous avons alors deux hdc compatibles et possédant des informations, nous pous pouvons effectuer :
qui effectue une copie de la mémoire du hdcSource (notre bitmap) vers le hdc destination (la fenêtre).
Il ne reste plus qu'à libérer les DC proprement.
Il est important de noter que cette exemple montre comment utiliser deux DC pour afficher un bitmap. Pour des dessins plus simples, nous pouvons directenment dessiner dans le DC de la fenêtre (affichage d'une ligne par exemple).
La programmation MFC :
Il existe un autre style de programmation sous Windows, c'est l'utilisation des MFC. Cela consiste à utiliser un ensemble de classe permettant de simplifier l'utilisation des APIs windows. La principale caractéristique de la MFC est le fait que l'ensemble de votre programme est constitué de classes. Le programme principal est cependant toujours composé de la fonction Winmain, mais la seule chose à faire est d'enregistrer les fenêtres et d'appeler les méthodes InitInstance et Run de la classe représentant l'application. La réalisation d'un programme consiste alors à créer des classes pour les fenêtres et les boîtes de dialogue et de définir des fonctions qui seront appelées lorsque des événements seront détéctés.
L'utilisation de l'environnement graphique permet de placer des composants dans des fenêtres, de leur associer des variables dans la classe de la fenêtre et de définir des fonctions correspondant à une interaction de l'utilisateur avec ce composant.