C/c++ sous windows sans mfc tutorial 1

Description

J'ai pas mal galéré pour comprendre le fonctionnement de C ou C++ sous Windows sans faire usage de MFC. Ce premier tutorial explique les bases de la programmation Windows avec C ou C++ . Il contient une introduction et une partie explicative générale de la programmation windows, suivi d'un exemple simple commenté. Il contient également quelques annotations pour ceux qui viennent de Visual Basic 5 ou 6.

Source / Exemple :


/*TUTORIAL C sous WINDOWS
Leçon 1

                Pour bien comprendre ce Tutorial il est utile de connaitre le C sous DOS au niveau des appels de fonction, et des pointeurs. Plusieurs rapprochements avec Visual Basic sont également effectués, mais ce tutorial ne nécessite en aucun cas l'apprentissage de ce langage.

Introduction
	La Programmation C ou C++ sous windows semble relativement complexe lorsque l'on vient de la programmation C sous MS DOS, ou d'autres langages évolués tels que Visual Basic. 
	Pour ma part, j'ai débuté la programmation PC avec Visual Basic. Ce langage permet de créer des application répidement sous Windows, car tous les éléments de feuille, de contrôle et autre peuvent se créer en un click de souris. Ensuite, il ne reste plus qu'à rajouter à ces éléments des valeur de propriété (taille de feuille, texte, couleur, aspect,...) et des méthodes d'évènement (Click, Change, GetFocus...)
	Sous Windows, la programmation en C ressemble à Visual Basic, seulement nos éléments ne seront plus créés en cliquant dans une boite à outils et en rajoutant du code, mais en codant la totalité de notre programme qui sera composé au moins de deux grandes parties : La partie Création et Réception des Messages et la partie Traitement des Messages.
	Tout cela va vous sembler nouveau, mais vous allez normalement très vite vous y faire.

I) Structure d'un Programme Windows.
	Windows est un système d'exploitation multitache (c'est à dire qu'il permet de faire tourner plusieurs applications en même temps) et qui réagit à des évènements causés par l'utilisateur  (Click de la souris, appuis d'une touche du clavier, relachement d'une touche, mouvement de la souris...), ou par l'ordinateur lui même (temps écoulé, fin d'impression de l'imprimante...)

	Lorsqu'on programme en C sous Windows, il faut créer une application dans laquelle les évènements seront attendus, et aussi dans laquelle ces évènements seront traduit pour donner lieu à des actions que vous souhaitez. Il faut donc pour cela créer deux Procédures qui sont les suivantes : 

Procédure de Réception des Messages
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
}

Procédure de Traitement des Messages
LRESULT CALLBACK WinProc (HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
}

	Lorsque vous lisez ces deux procédures si vous n'avez fait jusqu'à présent que du C ou C++ sous Dos, vous allez dire Qu'est ce que c'est que ce M.......

	Ne nous affolons pas, j'ai oublié de vous parler d'une chose assez importante. Dans Windows, la programmation se fait à l'aide de l'API. l'API est une bibliothèque de fonctions servant à communiquer avec Windows. Seulement, on ne communique pas avec l'API en utilisant des fonctions simples telles que int Resultat(int NombreA, facteur, int  NombreB). l'API le plus souvent possède ses propres type de variables. Par exemple, le type LPSTR de la fonction WinMain est en fait un type de chaine de caractère. Il est pour certaines et peut être la plupart des fonctions de l'API totalement compatible avec une chaine de caractère déclarée en char *. Si vous créez par exemple une chaine de caractère en utilisant char*, elle pourra être passée en paramètre dans une fonction de l'API demandant comme paramètre une chaine de type LPSTR. Il existe comme vous pouvez le voir dans les deux fonctions WinMain et WinProc d'autres types inconnus du monde MS DOS comme HINSTANCE, WPARAM, LPARAM, UINT) , nous les commenterons au fur et à mesure.

Donc, pour en revenir à nos deux fonctions WinMain et WinProc, ce sont elles qui vont gérer tout notre programme.

 WinMain va contenir au moins deux choses : 
	_ La Création de l'application à son démarrage
	_ Une boucle d'attente des Messages

La Création de l'application à son démarrage, c'est généralement la création d'une feuille avec dessus des controles tels que des boutons, des zones de texte ...
Nous nous contenterons pour cette première leçon d'une simple feuille vide.

La Boucle d'attente des Messages c'est tout simplement une boucle du style while.
Dans cette boucle, on va demander via une fonction de l'API, que Windows envoie tous les évènements vers la procédure WinProc qui va les traiter.

WinProc va contenir la chose suivante : 
	_ Un ensemble de branchement du type Switch Case

Cet ensemble va permettre à l'utilisateur de dire ce qu'il veut voir se passer lorsque tel ou tel évènement est envoyé. Pour les arrivant de Visual Basic, cela correspond aux méthodes d'évènements.
Une différence est à noter, c'est qu'il n'y a pas à la base de méthode d'évènement pour chaque contrôle d'une feuille. Il y a un évènement global qui envoie un message contenant le nom du controle concerné.
Nous verrons cela en détail un peu plus loin.

II) Notre premier Programme Windows
	Pour parler avec l'API de Windows et donc utiliser ses fonctions un peu tordues, et ses types de variables sorties d'on ne sait où il faut inclure deux fichiers d'En-tête

  • /
#include <windows.h> #include <windowsx.h> /* Puis, nous allons déclarer les prototype de nos deux fonctions principales. Oui, on le fait, même si ces fonctions sont contenues dans l'API. C'est un peut comme lorsque on utilise directement l'API depuis Visual Basic, on Déclare quand même les fonctions à utiliser. Je vais les commenter une par une.
  • /
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); /* Cette fonction est la fonction principale de Windows. C'est la même que la fonction main pour un programme DOS. Elle attend un int en retour comme pour les programmes console, on retournera 0 à la fin de l'application pour indiquer à Windows qu'on est sorti du programme sans encombre. Vient ensuite le mot WINAPI . Alors, là, j'ai pas trop d'explications à donner si ce n'est une sorte de protocole à indiquer à Windows. Sachez pour l'instant uniquement qu'il faut le mettre dans la fonction principale. WinMain est donc le nom de la fonction (à bien respecter). Ensuite viennent des paramètres un peu bizarre. hInstance de type HINSTANCE correspond à l'application elle même. Si hInstance était égal à 0 cela voudrait dire que l'application ne fonctionne pas. C'est une sorte de pointeur indiquant combien de fois le programme est chargé (Rappelons le on est en multitache). hPrevInstance de type HINSTANCE également est un peu la même chose, mais est actuellement reclus car il était utilisé sous les anciens systèmes en 16 bits. LPSTR lpCmdLine est comme nous l'avons vu une chaine de caractère. elle correspond à la chaine de caractère qu'on peut entrer à la suite d'un lancement de programme. Si vous éxécutiez Word par exemple et que vous le lanciez sans utiliser l'icone mais en tapant dans Programme/Exécuter word.exe fichier.doc, word s'éxécuterait et fichier.doc serait chargé dans word. lpCmdLine contiendrait alors "fichier.doc". Le dernier paramètre nCmdShow est de type int. Je n'ai pas de précisions sur lui pour l'instant, mais ce paramètre nous le verrons est utilisé lors de la création des fenêtres et contrôles. Déclarons maintenant le prototype de la fonction de traitement des messages
  • /
LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); /* Comme pour la précédente, cette fonction attend un retour. Le type de ce retour est ici LRESULT, qui est apparement spécifique au retour d'une procédure de traitement de messages Windows. CALLBACK est comme WINAPI une sorte de protocole. WinProc est le nom de la procédure qui va traiter les messages envoyés par WinMain. Voici ses paramètres : _HWND hWnd : HWND est une sorte de pointeur sur un objet. Dans Visual Basic on appelle une fenetre par son nom, un bouton par son nom... En C sous Windows, on appelle généralement les choses par leur Handle. Il s'agit d'un pointeur désignant un objet. _ UINT uMsg : C'est le message envoyé par WinMain. La valeur contenue par ce message sera de type UINT qui est en fait une appellation de l'API pour le type unsigned int. La valeur de uMsg va désigner l'évènement qui vient de se produire. _ wParam est un paramètre lié au Message envoyé. Il est de type WPARAM c'est une sorte d'entier long. _lParam est aussi lié au Message envoyé . Ils vont contenir par exemple les positions de la souris lors d'un évènement MouseMove, le code de la touche enfoncée lors d'un évènement KeyDown... Cette Procédure WinProc va en somme recevoir de WinMain l'objet concerné par l'évènement (hWnd), le nom de l'évènement (uMsg), deux paramètres précisant l'évènement (wParam et lParam). Voici maintenant la suite du programme avec le contenu de la Procédure WinMain. Nous allons créer une fenêtre, puis la charger et enfin la faire apparaitre à l'écran. Nous placerons ensuite la boucle d'attente de messages.
  • /
bool stop = false; /* Je place cette variable globale ici qui indique si le programme se termine ou non, nous verrons son utilité plus tard*/ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { /*On créé d'abord les variables dont nous aurons besoin et pour la fenêtre et pour la boucle des messages après.*/ HWND hFenetre; /* Il va s'agir du Handle qui contiendra l'objet Fenetre que nous créérons.*/ MSG MessagesAEnvoyer; /*Il s'agit de la variable de type MSG qui sera envoyée à WinProc lorsque un évènement arrivera. */ /* Pour créer une fenêtre dans une application Windows, il faut trois phases: _ La déclaration avec implémentation de certaines propriétés _ L'Enregistrement en mémoire en utilisant une fonction de l'API _ La Création finale en utilisant une fonction de l'API. Ne vous inquiétez pas, on va décortiquer. La création de la fenêtre avec implémentation des propriétés donne ce qui suit :
  • /
WNDCLASS Fenetre; /* Déclaration de Fenetre, objet de la Structure WNDCLASS.*/ Fenetre.lpszMenuName =NULL; /* Il s'agit d'une propriété indiquant le nom du menu que la fenêtre doit contenir. Ici, il n'y en aura pas.*/ Fenetre.cbClsExtra =0; /* Je laisse ce paramètre propre à Windows à 0*/ Fenetre.cbWndExtra =0; /* Je laisse ce paramètre propre à Windows à 0*/ Fenetre.hInstance = hInstance; /* J'indique que la fenêtre va fonctionner dans l'actuel programme.*/ Fenetre.lpfnWndProc = WinProc; /* J'indique le nom de la procédure vers laquelle seront envoyés les messages Windows propres à cette fenêtre.*/ Fenetre.lpszClassName = "FENETRE DE TYPE A MOI"; /* J'indique le nom que va porter le type de Fenêtre que je suis en train de créer actuellement. */ Fenetre.style = CS_VREDRAW | CS_HREDRAW; /* J'indique que la fenêtre devra être redessinée à chaque modification de la fenêtre.*/ Fenetre.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); /*hBackGround est la couleur de fond de la fenêtre. On utilise la couleur WHITE_BRUSH. WHITE_BRUSH on ne sait pas ce que c'est vu comme ça, mais en utilisant la fonction GetStockObject de l'API, le retour va correspondre à une donnée. Cette donnée castée en (HBRUSH) v a être compatible pour cette propriété de hBackGround ce qui donnera à notre fenêtre la couleur blanche. Pour désigner une couleur sinon, il aurait fallut caster en HBRUSH un nombre Hexadécimal correspondant à la couleur demandée, mais les héxa, c'est un peu galère à se rappeler. (En Visual Basic, je préfère utiliser RGB(a,b,c) plutôt que &HF00...)*/ Fenetre.hIcon =(HICON) LoadIcon(hInstance, IDI_APPLICATION); /*hIcon est la forme de l'icone qui va se trouver dans le bandeau de notre fenêtre. En utilisant la fonction LoadIcon de l'API, à laquelle on envoie l'instance du programme et la valeur IDI_APPLICATION, on obtiendra une donnée qui castée sous la forme (HICON) sera compatible avec Fenetre.hIcon.*/ Fenetre.hCursor =(HCURSOR) LoadCursor(hInstance, IDC_ARROW); /*Ici, c'est la même chose que pour hIcon, on indique que l'on veut IDC_ARROW comme pointeur de souris soit une flèche. Il existe également IDC_WAIT, IDC_CROSS...*/ /* Voilà, nous venons de créer un type de fenêtre propre à nous qui s'appelle Fenetre. Mais Fenetre n'est pas une fenêtre à part entière. Il s'agit en fait d'une sorte de modèle dont le nom usuel est "FENETRE DE TYPE A MOI". Pour créer une fenêtre prête à être affichée, il faut en premier lieu l'enregistrer en mémoire*/ RegisterClass(&Fenetre); /* Ne me demandez pas pourquoi il faut faire cela, je n'en sais rien, je sais uniquement qu'il s'agit d'une étape obligatoire avant de vraiment créer la fenêtre. l'API a apparement besoin d'enregistrer la fenêtre via la fonction RegisterClass pour la continuation de notre création. Vient ensuite l'étape finale : la création finale de notre fenêtre. Dans cette partie, nous allons attribuer un handle à notre fenêtre, en la créant à l'aide de la fonction CreateWindow de l'API, et en passant comme paramètre pour cette création non pas l'objet Fenetre, mais son nom de baptème FENETRE DE TYPE A MOI. C'est peut être pour cela qu'il fallait l'enregistrer dans l'étape deux avec RegisterClass.*/ hFenetre = CreateWindow("FENETRE DE TYPE A MOI", "Voici la fenêtre", WS_OVERLAPPEDWINDOW, 200, 200, 300, 250, NULL, NULL, hInstance, NULL); /*Je commente : hFenetre c'est le Handle désignant notre fenêtre qui va se créer. Ce Handle comme je l'ai déjà dit va servir à nommer notre fenêtre dans la plupart des fonctions de l'API. CreateWindow : C'est la fonction qui va créer notre fenêtre. "FENETRE DE TYPE A MOI" : C'est le nom que l'on a donné à notre type de Fenetre dans l'étape 1. CreateWindow se sert de cela pour savoir quel type de fenêtre créer. "Voici la fenêtre": C'est la chaine de caractère que l'on veut voir apparaitre dans le bandeau WS_OVERLAPPEDWINDOW : Il s'agit d'un paramètre qui va indiquer comment notre fenêtre doit apparaitre. (Avec ou sans bandeau, avec ou sans bouton de controle...). Ici, elle sera complète (bandeau, redimensionnable, avec boutons d'agrandissement, de réduction et de fermeture) 200,200,300,250 : Il s'agit des coordonnées de notre fenêtre à l'écran. 200,200 : c'est le point en haut à gauche par rapport au bord gauche et haut de l'écran. 300 est la longueur de la fenêtre et ,250 la hauteur. NULL,NULL, hInstance, NULL : Cela correspond à : FenêtreParent de type HWND, hMenu de type HMENU, Instance de l'appplication, Paramètre Windows de type LP VOID. la Fenêtre Parent correspond à la fenêtre qui contiendrait la fenêtre que nous créons. hMenu est le handle du menu de la fenêtre. Instance est l'instance du programme Paramètre Windows est utile apparement dans le cadre de la création d'une application en multidocuments nous ne nous y attarderons pas ici. Pour notre exemple, vu que nous voulons une fenêtre toute simple, nous utiliserons donc les paramètres tels qu'ils sont dans l'exemple. Maintenant que notre fenêtre est crée, il faut la faire apparaître à l'écran. On va utiliser pour cela la fonction de l'API ShowWindow. Il faut passer à cette fonction deux paramètres : le Handle de l'objet que l'on veut faire apparaître (ici hFenetre), et le paramètre de WinMain nShowCmd.*/ ShowWindow(hFenetre,nShowCmd); /*Voilà, notre fenêtre est créée et peut apparaitre. Notre programme est cependant incomplet, voici la suite, la boucle d'attente des messages. Si nous laissions tourner notre programme comme cela, la fenêtre apparaitrait à l'écran en un milliardième de seconde, et notre programme se terminerait. Il faut donc une boucle qui ne se terminera que si l'utilisateur clique par exemple sur la fermeture de la fenêtre. Dans cette boucle, il faut insérer une procédure qui va capturer les évènements et les envoyer dans WinProc pour y être traités.*/ while (!stop) { GetMessage(&MessagesAEnvoyer, hFenetre, 0, 0); /* Cherche et lit les évènements liés à la fenêtre*/ TranslateMessage (&MessagesAEnvoyer); /* Traduit ces évènements*/ DispatchMessage (&MessagesAEnvoyer); /* Envoie ces évènements sous entendu dans WinProc)*/ } return 0; /*fin du programme*/ } /*Voilà, cette boucle va empêcher au programme de se terminer tant que la variable stop est à false. Le Programme est donc comme cela condamné à attendre les message d'évènement de la fenêtre et à les envoyer à WinProc Nous allons donc maintenant voir la fonction WinProc
  • /
LRESULT CALLBACK WinProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) /* On s'interroge sur le message envoyé par Window.*/ { /* A partir de maintenant, on va voir à quoi correspond ce message*/ case WM_DESTROY: /* Message de destruction de la fenêtre. Est envoyé lorsque la fenêtre se ferme (si l'utilisateur a cliqué sur la croix de fermeture par exemple)*/ { stop = true ;/* On met stop = true pour terminer la boucle d'attente de message de WinMain*/ PostQuitMessage(0); /*Fonction de l'API indiquant à Windows que le programme va se terminer.*/ return 0; /*On renvoie 0 pour dire que le traitement du message a été effectué. */ } } return DefWindowProc(hWnd, uMsg, wParam, lParam); /*dans tout autre cas on renvoie le message tel quel*/ } /* Nous venons de voir la procédure WM_DESTROY qui est un message de fermeture de notre fenêtre. Il s'agit d'un des messages les plus simples car nous n'avons utilisé aucun paramètre wParam ni lParam. Il existe plusieurs autres évènements qui aurait pu nous faire quitter l'appplication si nous l'aviosn voulu tel qu'un clique de souris dans la fenêtre, ou un mouvement de souris comme dans les écrans de veille. Nous verrons cela dans un prochain cours.*/ /*LISTING COMPLET DU PROGRAMME*/ #include <windows.h> #include <windowsx.h> int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd); LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); bool stop = false; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { HWND hFenetre; MSG MessagesAEnvoyer; WNDCLASS Fenetre; Fenetre.lpszMenuName =NULL; Fenetre.cbClsExtra =0; Fenetre.cbWndExtra =0; Fenetre.hInstance = hInstance; Fenetre.lpfnWndProc = WinProc; Fenetre.lpszClassName = "FENETRE DE TYPE A MOI"; Fenetre.style = CS_VREDRAW | CS_HREDRAW; Fenetre.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); Fenetre.hIcon =(HICON) LoadIcon(hInstance, IDI_APPLICATION); Fenetre.hCursor =(HCURSOR) LoadCursor(hInstance, IDC_ARROW); RegisterClass(&Fenetre); hFenetre = CreateWindow("FENETRE DE TYPE A MOI", "Voici la fenêtre", WS_OVERLAPPEDWINDOW, 200, 200, 300, 250, NULL, NULL, hInstance, NULL); ShowWindow(hFenetre,nShowCmd); while (!stop) { GetMessage(&MessagesAEnvoyer, hFenetre, 0, 0); TranslateMessage (&MessagesAEnvoyer); DispatchMessage (&MessagesAEnvoyer); } return 0; } LRESULT CALLBACK WinProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: { stop = true ; PostQuitMessage(0); return 0; } } return DefWindowProc(hWnd, uMsg, wParam, lParam); } /*Copiez ou faites un copier coller du programme dans un nouveau fichier C/C++ d'un Projet Window Application sous un IDE tel que Visual C++, Borland C++, DevC++.... Vous devez obtenir une fenêtre dont le bandeau affiche 'voici la fenêtre'. Cette fenêtre doit être de taille 300 / 250 et doit comporter les boutons de maximisation, minimisation et de fermeture. Elle doit également comporter une icone. Son fond doit être blanc.
  • /

Conclusion :


Ce Tutorial contient le même programme deux fois, une fois commenté pas à pas, et au final vous pouvez trouver le listing complet. vous pouvez copier coller soit l'un soit l'autre, le premier ayant ses commentaire sous forme de commentaire C (/*.... */). En revanche bien faire attention de ne pas copier coller dans votre IDE C/C++ les deux programmes, sinon, BUG.
Voilà, n'hésitez pas à commenter, poser des questions, je m'éforcerai d'y répondre dans la limite de mes possibilités.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.