Programmer en 'C' sous Windows
MinPrg01: programme minimal sous windows.
Ce premier source-code propose un exemple de programme windows minimal. On affiche une fenêtre
principale avec ses composants usuels : Caption bar classique (barre de titre), Menu basique, et
avec ses propriétés standards, par exemple le fait que la fenêtre puisse
être redimensionnée, déplacée, fermée etc... Comme la plupart de ces
fonctions sont gérées par le système, vous allez voir que notre programme ne fait
pratiquement rien.
Que fait ce programme ? : |
Ce programme crée et affiche une fenêtre Windows classique avec un menu défini par
script dans le fichier ressource.
Une chaîne de caractères est affichée dans la partie cliente de la fenêtre.
Le menu utilisateur (Command) permet de choisir 3 différents items :
"Do Something" affiche une autre chaîne de caractères directement dans la partie cliente
"About" Affiche une boite de dialogue système muni d'un bouton OK.
"Quit" Affiche une boite de dialogue système pour confirmer la fermeture de l'application.
|
Les grands principes évoqués : |
Un programme sous Windows (qui est un système d'exploitation événementiel) se
résume dans le principe, à une boucle de messages et une Callback, c'est à dire une fonction
standardisée (appelée par le système), qui va recueillir les messages, et éventuellement les
traiter. Notre programme principale (fonction WinMain), après avoir créé une
fenêtre, entre dans une boucle infinie (jusqu'à la fin du programme) qui va
continuellement :
Attendre un nouveau message (fonction GetMessage).
Traduire et diffuser ce message (fonctions TranslateMessage et DispatchMessage).
Ce message, traduit et diffusé, arrive dans une Callback Windows, celle relative à la
fenêtre concernée par l'événement. Par exemple faire un clic souris sur
un bouton, va déclencher l'événement WM_LBUTTONDOWN qui sera communiqué
à la Callback de ce bouton et qui le traitera comme bon lui semble.
Sous Windows, tout ce que vous voyez à l'écran est dans une fenêtre Windows et
tout programme ayant une partie visible à l'écran est composé de fenêtres.
Je pense que c'est ce qui a inspirer les gens qui ont trouvé le nom du système. Blague
à part, toute action utilisateur (clavier , souris, etc...) est traduite en un événement
qui arrivera dans la Callback de la fenêtre concernée ou de la fenêtre
désignée comme telle. Par exemple pour le clavier, c'est la fenêtre qui a le 'Focus'
(la fenêtre active) qui reçoit les événements clavier.
|
Quelques points du source : |
Dans notre exemple de programme, notre Callback ne répond qu'à très peu de message.
D'ailleurs une Callback ne peut pas répondre à tous les messages, c'est pourquoi on fait
appel à la fonction du système DefWindowProc qui traite tous les messages que nous
ne gérons pas. Par exemple nous ne gérons pas l'affichage des bords et du fond de la
fenêtre, de la barre de titre et du menu. Nous ne gérons pas non plus les actions utilisateurs
qui font bouger ou redimensionner la fenêtre, le déploiement des menus etc...
Notre programme se contente donc de répondre à 5 messages :
WM_CREATE : qui notifie que la fenêtre est en passe d'être créée. Usuellement on
profite de ce message pour faire des initialisations, des allocations mémoire, des créations d'objets...
WM_COMMAND : nous est envoyé par les menus, quand l'utilisateur a sélectionné un item.
WM_PAINT : demande à ce que la partie cliente de la fenêtre soit dessinée. Ici nous ne faisons
qu'afficher une chaîne de caractères. Il faut savoir que la fenêtre est en charge de son aspect
sur toute la partie cliente, et qu'à tout moment elle doit pouvoir être redessinée.
WM_CLOSE : Est reçu quand l'utilisateur veux fermer la fenêtre, soit par un clic sur la croix en
haut à droite, soit par la combinaison de touche ALT+F4, soit par le menu système etc...
WM_DESTROY : Notifie la destruction de la fenêtre. On en profitera pour libérer d'éventuelles
allocations mémoire etc... notons l'appel à la fonction du système PostQuitMessage qui poste
l'événement WM_QUIT dans la 'message queue' qui va entraîner la fin de la boucle (GetMessage
renvoie ZERO dans ce cas).
|
Questions / Réponses: |
A quoi sert le RegisterClass ?
A enregistrer auprès du système une classe de fenêtre (comme une classe d'objet).
Chaque fenêtre est associée à une classe, et l'on peut créer plusieurs fenêtres
d'une même classe (plusieurs boutons poussoirs par exemple). Dans notre programme, on définit une
classe que l'on appelle "MainWindowClass", avec aussi notre Callback (voir structure WNDCLASS). Ensuite on peut
créer notre fenêtre principale, qui est forcément une fenêtre spécifique,
et qui donc nécessite d'être associée à une classe spécifique, avec une Callback spécifique.
Quand le système reçoit un événement (par exemple un clic souris) il retrouve la
classe de la fenêtre concernée et appelle la Callback associée.
Quand est-ce que les messages arrivent dans ma Callback ?
Par nature, le système peut transmettre un message n'importe quand, mais toujours l'un après
l'autre. En effet notre exemple n'est pas MultiThread, et tous les messages qui arrivent à notre Callback passent
par notre boucle de messages au sein du même Thread (le thread dit primaire). Par conséquent, notre
programme ne fait jamais deux choses en même temps et ne peux pas recevoir de nouveaux messages pendant
qu'il en traite un.
Quand on affiche la chaîne avec le Menu " Do Something " cette chaîne ne reste pas affichée,
par exemple elle disparaît quand on redimensionne la fenêtre, pourquoi ?
A chaque fois que nécessaire le système demande à la fenêtre de se redessiner
(via le message WM_PAINT notamment). Donc seules les affichages fait en réponse au message WM_PAINT sont persistants.
|
Source : minprg.c |
/*--------------------------------------------------------------------------------*/
/* 'C' Programming Under WIN32 V.Burel (c)2007*/
/* */
/* WEB : http://pagesperso-orange.fr/vb-audio/fr/pub/programming/index.htm */
/*--------------------------------------------------------------------------------*/
/* MinPrg is a minimal program under windows (like a Hello World) */
/*--------------------------------------------------------------------------------*/
/* To compile With Microsoft VC2005 you need to create an empty Win32 project */
/* with the following options : */
/* - Configuration Properties / General : Char Set = NOT SET */
/* - Configuration Properties / C/C++ / Preprocessor : _CRT_SECURE_NO_DEPRECATE */
/*--------------------------------------------------------------------------------*/
#ifndef __cplusplus
#ifndef STRICT
#define STRICT
#endif
#endif
#include <windows.h>
#include <stdio.h>
#include "minprg.h"
//
//define globals
//
static HWND G_hwnd_MainWindow =NULL;
static HINSTANCE G_hinstance =NULL;
/*******************************************************************************/
/** QUIT & ABOUT **/
/*******************************************************************************/
void ManageCloseMessage(HWND hw)
{
int rep;
char titre[]="Close Application...";
char message[512];
//Get the string from resource, otherwise we use a constant.
if (LoadString(G_hinstance, IDS_CONFIRMCLOSE, message, 512) == 0)
strcpy(message,"Do you Want To Close This Application ?\n
(but there is a problem to load resources)");
//Open System Dialog Box
rep=MessageBox(hw,message,titre,MB_APPLMODAL | MB_YESNO | MB_ICONQUESTION);
if (rep == IDNO) return;
PostMessage(hw,WM_DESTROY,0,0L); //DestroyWindow(G_hwnd_MainWindow);
}
void ManageAboutBox(HWND hw)
{
char titre[]="About...";
char message[512];
strcpy(message,SZPUBLICNAME);
strcat(message,"\nVersion : ");
strcat(message,SZPUBLICVERSION);
strcat(message,"\nStandalone Application\n");
strcat(message,"\nExample of 'C' Source code\n");
MessageBox(hw,message,titre,MB_APPLMODAL | MB_OK | MB_ICONINFORMATION);
}
/*******************************************************************************/
void ManageMenu(HWND hw, WPARAM p,LPARAM w)
{
char sss[256];
HDC dc;
switch(LOWORD(p))
{
case IDT_DOSOMETHING:
dc=GetDC(hw);
strcpy(sss,"I write something in the Client Area");
TextOut(dc,0,0,sss,(int)strlen(sss));
ReleaseDC(hw,dc);
break;
case IDM_QUIT:
ManageCloseMessage(hw);
break;
case IDM_ABOUT:
ManageAboutBox(hw);
break;
}
}
/*******************************************************************************/
BOOL InitSoftware(HWND hw)
{
return TRUE;
}
BOOL EndSoftware(HWND hw)
{
return TRUE;
}
/*******************************************************************************/
/* CALL BACK */
/*******************************************************************************/
LRESULT CALLBACK MainWindowManageEvent(HWND hw, //handle of the window.
UINT msg, //Message Ident.
WPARAM p1, //parameter 1.
LPARAM p2) //parameter 2
{
char sss[256];
HDC dc;
PAINTSTRUCT ps;
switch (msg)
{
case WM_CREATE:
//return -1 here cancel the window creation
if (InitSoftware(hw) == FALSE) return -1;
break;
case WM_COMMAND:
ManageMenu(hw,p1,p2);
break;
case WM_PAINT:
dc=BeginPaint(hw,&ps);
strcpy(sss,"This text is always displayed");
TextOut(dc,0,30,sss,(int)strlen(sss));
EndPaint(hw,&ps);
break;
case WM_CLOSE:
ManageCloseMessage(hw);
break;
case WM_DESTROY:
EndSoftware(hw);
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hw,msg,p1,p2));
}
return (0L);
}
/*******************************************************************************/
/** MAIN PROCDURE **/
/*******************************************************************************/
int APIENTRY WinMain(HINSTANCE handle_app, //Application hinstance.
HINSTANCE handle_prev, //NULL.
LPTSTR param, //Command Line Parameter.
int com_show) //How to display window (optionnal).
{
long wstyle;
MSG msg;
char szWindowClassName[]="MainWindowClass";
char * title="Sorry";
WNDCLASS wc;
//we first store the APP Hinstance
G_hinstance=handle_app;
//here you can make some early initialization and analyze command line if any.
//we define a window class to create a window from this class
wc.style =CS_HREDRAW | CS_VREDRAW; //property.
wc.lpfnWndProc=(WNDPROC)MainWindowManageEvent; //Adresse of our Callback.
wc.cbClsExtra =0; //to store some byte inside a class object.
wc.cbWndExtra =0; //to store some byte inside a window object.
wc.hInstance =handle_app; //handle of the application hinstance.
wc.hIcon =LoadIcon(NULL, IDI_APPLICATION);//handle of icon displayed in the caption.
wc.hCursor =LoadCursor(NULL,IDC_ARROW); //handle of cursor mouse .
wc.hbrBackground=(HBRUSH)(COLOR_MENU+1); //Background color.
wc.lpszMenuName="MyMainAppMenu"; //pointer on menu defined in resource.
wc.lpszClassName=szWindowClassName; //pointer on class name.
//register class.
if (RegisterClass(&wc)==0)
{
MessageBox(NULL,"Failed to register main class...",title,MB_APPLMODAL | MB_OK | MB_ICONERROR);
return 0;
}
//then we can create a windows from this class.
//Classical Window without Sizing border.
//wstyle=WS_DLGFRAME | WS_OVERLAPPED | WS_VISIBLE | WS_MINIMIZEBOX | WS_SYSMENU;
//classical Main Window
wstyle=WS_OVERLAPPEDWINDOW | WS_VISIBLE;
G_hwnd_MainWindow=CreateWindow(szWindowClassName, // address of registered class name.
SZPUBLICNAME, // address of window name string
wstyle, // window style
CW_USEDEFAULT, // horizontal position of window
CW_USEDEFAULT, // vertical position of window
UI_WIN_DX, // window width
UI_WIN_DY, // window height
NULL, // parent handle is NULL for a main window.
NULL, // menu name defined in resource
//(NULL if defined in the Class).
handle_app, // handle of application instance
NULL); // address of window-creation data
if (G_hwnd_MainWindow==NULL)
{
MessageBox(NULL,"Failed to create window...",title,MB_APPLMODAL | MB_OK | MB_ICONERROR);
return 0;
}
ShowWindow(G_hwnd_MainWindow,SW_SHOW); //Display the window.
UpdateWindow(G_hwnd_MainWindow); //Send WM_PAINT.
/*---------------------------------------------------------------------------*/
/* Messages Loop. */
/*---------------------------------------------------------------------------*/
while (GetMessage(&msg,NULL,0,0)) //Get Message if any.
{
TranslateMessage(&msg); //Translate the virtuel keys event.
DispatchMessage(&msg); //DispatchMessage to the related window.
}
//here you can make last uninitialization and release
return (int)(msg.wParam);
}
|
Source : minprg.h |
#ifndef __MINPRG_H__
#define __MINPRG_H__
//version information (for program)
#define SZPUBLICVERSION "1.0.0.0" //displayed version in about box
#define SZPUBLICNAME "Minimal Program" //displayed title in main window
//Information for Main window
#define UI_WIN_DX 600
#define UI_WIN_DY 400
//version information (used in resource file)
#define __FILEVERSION__ 1,0,0,0
#define __PRODUCTVERSION__ 1,0,0,0
#define __SZFILEVERSION__ "1, 0, 0, 0\0"
#define __SZPRODUCTVERSION__ "1, 0, 0, 0\0"
#define __COMMENTS__ "Example of source code"
#define __COMPANYNAME__ "Audio Mechanic & Sound Breeder\0"
#define __FILEDESCRIPTION__ "Minimal Windows Application\0"
#define __INTERNALNAME__ "MinPrg"
#define __LEGALCOPYRIGHT__ "Copyright V.Burel©2007\0"
#define __ORIGINALFILENAME__ "MinPrg.EXE\0"
#define __PRODUCTNAME__ "MinPrg\0"
//definitions for MENU
#define IDT_DOSOMETHING 50
#define IDM_QUIT 100
#define IDM_ABOUT 101
//definitions for STRING-TABLE
#define IDS_CONFIRMCLOSE 100
#endif /*__MINPRG_H__*/
|
Source : minprg.rc |
#include "minprg.h"
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
MyMainAppMenu MENU DISCARDABLE
BEGIN
POPUP "&Command"
BEGIN
MENUITEM "&Do Something", IDT_DOSOMETHING
MENUITEM SEPARATOR
MENUITEM "&About", IDM_ABOUT
MENUITEM "&Quit", IDM_QUIT
END
END
/////////////////////////////////////////////////////////////////////////////
//
// STRING
//
STRINGTABLE
BEGIN
IDS_CONFIRMCLOSE "Do you want to close this application ?"
END
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
1 VERSIONINFO
FILEVERSION __FILEVERSION__
PRODUCTVERSION __PRODUCTVERSION__
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x0L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "Comments", __COMMENTS__
VALUE "CompanyName", __COMPANYNAME__
VALUE "FileDescription", __FILEDESCRIPTION__
VALUE "FileVersion", __SZFILEVERSION__
VALUE "InternalName", __INTERNALNAME__
VALUE "LegalCopyright", __LEGALCOPYRIGHT__
VALUE "OriginalFilename", __ORIGINALFILENAME__
VALUE "ProductName", __PRODUCTNAME__
VALUE "ProductVersion", __SZPRODUCTVERSION__
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END
|
|