Nous voici arrivé au début du cinquième chapitre de cette série de tutoriaux sur la programmation
sous Windows. Nous avancons petit à petit vers des sujets un peu plus avancés et vers des notions
un peu plus complexes. Nous allons aujourd'hui aborder deux sujets particuliers de la programmation
sous Windows : le sous-classement et quelques petits problemes liés au log d'informations.
Nous avons vu que les controles possèdent un comportement par défaut. Ainsi, un controle
de type EditBox par exemple est prevu pour accepter tous les types de caracteres, etc ...
Ce comportement va se caracteriser en pratique comme nous l'avons deja vu
par une DialogProc qui va repondre de maniere "standard"
aux differents messages adresses a ce controle. Quand on y pense un peu, chaque controle
repond a un besoin specifique d'organisation dans l'espace (votre ecran ou votre GUI)
d'un certain type d'information. A ce controle est associe un ensemble d'action permettant
d'interagir avec ce dialogue. Pour prendre un exemple plus
concret et intuitif, une maison est une idee generale a laquelle sont associes une
occupation spatiale particuliere (sa forme) et un certain nombre d'actions de base
qui sont la par defaut (y entrer, en sortir, ... ). Une maison par defaut vient
avec des portes encastrees dans ses murs externes permettant d'y entrer
et d'en sortir. Bon imaginons maintenant que vous vouliez condamner les portes de votre maison
et juste permettre l'entree par la cheminee. Bon, nous sommes en presence d'une sorte
de maison speciale. D'une maison dont les caracteristiques different des maisons
dites traditionnelles. Vous allez donc devoir modifier le comportement (les plans) par defaut
de votre maison pur l'adapter au besoin specifique que vous avez ... et bien voila en
gros le sous-classement !
Le sous classement est donc, en somme, une technique permettant de specialiser le comportement
par defaut de n'importe quel controle. Quand on y pense cette notion correspond a peu
pres a de l'heritage (cf. C++ ou n'importe quel autre langage objet).
En effet, en C++, par exemple, l'heritage correspond entre autre a une specialisation
de la classe mere. Or, nous sommes ici
justement confronte a un desir de specialisation d'un controle. Nous voulons lui faire
effectuer une operation particuliere qui sort du type d'operation qu'il a l'habitude
de traiter. Le sous classement est une technique simple à utiliser et je pense simple à comprendre.
Nous avons vu dans les chapitres precedents que à chaque boite de dialogue était
associé une "Dialog Procedure" (DlgProc). Cette procédure
est appelée par Windows à chaque fois qu'un message concernant notre boite de dialogue
a été recu et doit etre traité. Pour chaque boite de dialogue que nous créons nous devons lui spécifier
une DlgProc afin de lui associer un comportement utile. Nous avons déjà vu qu'une boite de dialogue
n'est en fait qu'une fenetre un peu particulière composée de sous-fenetres filles (les controles).
Si tel est le cas, ces sous-fenetres filles doivent disposer d'une procedure de type
DlgProc par défaut qui va décrire son comportement "standard". Etant donne que nous
voulons outrepasser ce comportement standard, on peut imaginer que le fait de substituer notre
DlgProc à la DlgProc par défaut pour répondre aux messages qui nous intéressent pourrait
résoudre efficacement notre probleme.
Et bien, c'est exactement à quoi correspond
la technique de sous-classement : remplacer la dlgProc par défaut par la notre
dans laquelle nous filtrerons les messages recu par notre controle et nous
traiterons les messages pour lesquels nous voulons avoir un comportement spécifique
et nous laisserons l'ancienne DlgProc traiter les autres messages avec un comportement
par défaut.
2. La spécialisation du comportement d'un controle.
Les techniques :
nous avons 2 techniques qui nous permettent d'obtenir facilement le resultat de
specialisation voulu : le sous-classement et le super-classement.
2.1. Le sous classement.
Voici un diagramme mettant un peu de clareté dans tout ca (j'espère) :
D'un point de vue pratique, une fonction va nous permettre de réaliser ce
sous-classement : SetWindowLong().
Elle nous permet, entre autres, de substituer une DlgProc quelconque par une autre. Nous lui
passons l'adresse de cette DlgProc et elle nous retourne l'adresse de la vieille
DlgProc. Le dernier point auquel nous devons bien penser est d'appeler cette
ancienne DlgProc pour que les autres comportement par defaut soient concerves.
Cela va se faire depuis la nouvelle DlgProc grace a la fonction CallWindowProc() qui prend comme premier
parametre l'adresse de la vieille DlgProc et ensuite les parametres classiques associes.
Donc si nous resumons un peu le tout, pour sous classer un controle quelconque, il nous
faut :
Cela donne dons au niveau code source quelquechose comme:
DLGPROC g_oldDlgProc;
BOOL CALLBACK NewDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
[...]
return CallWindowProc( g_oldDlgProc, hwndDlg, uMsg, wParam, lParam );
}
BOOL CALLBACK MainGUIDlgProc(HWND hwndDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch( uMsg )
{
[...]
case WM_INITDIALOG:
{
// on specialise le controle qui nous interesse
g_oldDlgProc = (WNDPROC) SetWindowLong( MY_CONTROL_ID, GWL_WNDPROC, (long)NewDlgProc);
[...]
}
[...]
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
[...]
DialogBox( hInstance, MAKEINTRESOURCE(NOTRE_ID), NULL, MainGUIDlgProc );
[...]
}
Voila en gros pour ce qui est du sous-classement. C'est une technique qu'il est bon
de connaitre car elle est extremement utile et utilisee sous Windows mais aussi
parce la notion de sous-classement est aussi utilise dans d'autres cadre que celui de
Windows et on retrouve frequement le terme.
Un exemple simple de ce type d'utilisation est celui d'un controle de type
Edit dont on veut qu'il ne recoive que des entrees de type numeriques et non
des caracteres de l'alphabet. Ce type de comportement n'est pas prevu par avance
pour ce controle nous devons donc specialiser son comportement pour lui faire accepter
que des chiffres. Voila une situation ou le sous-classement est typiquement une
technique ideale. Voici le source :
ProjetWindows_chap6_sous_class1.zip
Voici un autre exemple ou je sous-classe un edit-control pour lui faire afficher
une chose differente quand la souris passe par dessus :
ProjetWindows_chap6_sous_class2.zip
2.2. La technique de super-classement.
Je passerai rapidement sur cette technique car une fois que le sous-classement
a été compris, il n'y a plus grand chose à expliquer. Contrairement au sous-classement,
elle permet de specialiser une classe et non pas seulement une procedure de callback.
Les possibilités sont plus grandes qu'avec le sous-classement puisqu'il nous est
possible de modifier plusieurs caractéristiques du controle en agissant directement
au niveau de sa WNDCLASS. Nous allons donc
, dans ce cas, recuperer la WNDCLASS de notre controle, changer le nom de
cette WNDCLASS et modifier les champs qui nous interessent pour finir par
ré-enregistrer cette classe. Ce que nous faisons effectivement c'est créer une nouvelle
classe qui va avoir les memes caractéristiques que la classe du controle que nous
voulons spécialiser, remplacer les caracteristiques de cette classe par celles
que nous voulons, et enregistrer cette classe. Mais attention, TOUTES les fenetres que nous pourrons
creer avec le nom de cette classe auront ainsi le meme comportement que le controle
visé mais avec une specialisation voulue. Il faut bien comprendre que le sous-classement
ne s'attaque qu'a un controle en particulier lui en lui associant une nouvelle WNDPROC.
Je vais maintenant aborder le sujet du debuggage et du log d'information sous Windows.
Loin d'etre une presentation exhaustive, je vais plutot voir
quelques points qui sont plus apparentés à des trucs intéressant à connaitre
qu'à des éléments essentiels dans la programmation sous Windows.
Quelques une de ces informations seront spécifiques a VC++, je le signalerai
dans ce cas de figure.
3.1. Les macros d'environnement.
Sous windows, il existe un certain nombre de macros specifiques à cet environnement
qui nous permettent d'avoir acces a une foule d'informations utiles. Par exemple,
la macro _WIN32 nous permet de savoir si nous sommes dans un programme win32 ou pas.
La macro __cplusplus nous permet de savoir que nous sommes dans un source c++ et
_MSC_VER (specifique a VC++) permet d'avoir acces a un element d'information concernant la version
du compilateur utilise,
Voici une table resumant les valeurs possibles de _MSC_VER :
Version du compilateur | Valeur de _MSC_VER |
C Compiler version 6.0 | 600 |
C/C++ compiler version 7.0 | 700 |
Visual C++, Windows, version 1.0 | 800 |
Visual C++, 32-bit, version 1.0 | 800 |
Visual C++, Windows, version 2.0 | 900 |
Visual C++, 32-bit, version 2.x | 900 |
Visual C++, 32-bit, version 4.0 | 1000 |
Visual C++, 32-bit, version 5.0 | 1100 |
Visual C++, 32-bit, version 6.0 | 1200 |
Dans un projet logiciel, que ce soit en phase de debuggage pure ou en phase de test,
il est souvent utile d'avoir acces a des informations de type texte pouvant, par exemple,
renseigner sur l'etat de l'application en cours ou bien fournir des informations
specifiques sur une erreur qui vient de survenir. Pour un projet Win32, alors que logger
des informations dans un fichier ne
pose aucun probleme, il est moins evident de logger simplement des informations a
l'ecran comme nous pouvons avoir l'habitude de le faire avec des printf (ou cout) sous DOS, Linux, etc ...
Et bien, nous allons etudier a travers quelques exemples des solutions simples permettant d'afficher du texte
dans une boite de dialogue, dans la fenetre de debug, et pour finir dans une console DOS !
NOTE: j'utilise le terme 'log'
comme un equivalent de "isoler des informations pertinentes au qu'un
programme tourne" et logger comme un equivalent de "mettre de cote
ces informations pertinentes".
3.2.1. Un meilleur assert().
La macro assert() est une macro que tout programmeur devrait utiliser.
Pour ceux pour qui elle n'évoque rien, elle permet de vérifier la validité d'une
expression booléenne passée en paramètre. Si cette expression est fausse, assert
provoque la fin du programme et affiche un message d'erreur. Chose importante
a savoir c'est que cette macro n'agit qu'en mode Debug et ne fait rien quand un programme est compile en
version Release. Nous pouvons donc en truffer notre source sans aucune penalite
pour la version finale de notre application
mais avec une verification maximum en version Debug.
NOTE: pour ceux qui ne sont pas a l'aise avec ces notions de Release et de Debug,
voici quelques explications supplementaires. Un compilateur propose souvent
une option Debug ou Release. Pour une application compilee en Debug, le compilateur
va rajouter une foule d'information a notre executable devant par la suite faciliter le tracage du code
pas a pas, etc ... Parmis ces informations, on a la table des symboles qui va contenir
la liste des symboles de votre application (variables, ...) ainsi qu'un ensemble
d'information les concernant, etc ... En mode de compilation Debug, le compilateur
ne va pas chercher a optimiser votre code et va ainsi fournir un code machine
brute "de base". En mode release, tout ceci est oublie bien entendu. Il est bon
dans un projet de rester en mode Debug jusqu'au dernier moment mais attention cette
regle peut parfois reserver des surprises. Il n'est pas rare qu'un programme semblant
fonctionner en Debug crash violement en Release. La raison est souvent un probleme
de violation de memoire qui n'avait pas ete detecte. En mode Debug, la gestion memoire
est, en effet, plus "assistee" et peut masquer des erreurs qui resortent en mode
Release ou aucune verification n'est faite. C'est a vous de regarder avec le compilateur
que vous utilisez la maniere de voir comment passer du mode Release au mode Debug.
Je vous propose deux possibilites simple pour notre version de assert().
L'une va utiliser un debugger et l'autre va utiliser une boite de dialogue.
3.2.1.a. Le debugger.
Un debugger de maniere generale est une application qui va assurer un certain
nombre de services minimum vous permettant de verifier l'etat de fonctionnement
de votre application. Parmi ces services, on trouve des possibilites d'execution
pas a pas, de mettre en place des breakpoint ( ou "point d'arret", qui va vous
permettre de lancer votre programme et de le laisser tourner jusqu'a ce
qu'il atteigne une certaine ligne de votre code source, ligne a laquelle
il s'arretera vous permettant d'inspecter l'etat de vos variables, etc ...), etc ...
Ce debugger peut prendre plusieurs forme, sous VC++ il est integre a l'EDI
et vous pouvez y avoir acces directement dans le menu "Build>Start debug",
sous gcc le debugger prend la forme d'une application a part : gdb, etc ...
#include <stdio.h> #include <assert.h> int main(void) { assert(0); return 0; }
#define MAX_MESSAGE 256 #if defined(_DEBUG) #define MY_ASSERT(condition) { \ char szMessage[MAX_MESSAGE]; \ _snprintf(szMessage, sizeof(szMessage), "ERREUR D'ASSERTION : fichier %s, ligne %d, condition : %s.\n", __FILE__, __LINE__, #condition); \ if( !(condition) ) { \ if( IsDebuggerPresent() ) \ OutputDebugString(szMessage); \ } \ } #else #define MY_ASSERT(condition) #endif
Une autre solution pouvant nous venir a l'esprit est d'utiliser un boite de dialogue
pour obtenir une sortie d'assertion formatte comme on le desire. Cette boite de dialogue
peut etre une simple message box ou bien une boite un peu plus developpee avec des
commentaires eventuels sur le type de problemes rencontres, etc ...
Je ne vais pas m'ettendre la dessus etant donne que vous avez deja toutes
les clefs en main pour vous permettre de le faire vous meme.
3.2.2. Logger des informations.
Au cours du developpement d'une application, il est parfois necessaire d'avoir une sortie
de texte permettant d'afficher des informations pertinente en cours d'execution. Afin de
parvenir a ce comportement plusieurs solutions s'offrent a nous :
Une console sous Windows est un moyen physique pour une application d'interagir avec le monde exterieur (usage, ecran ...) a travers des flots entrant ou sortant de caracteres. Elle va ainsi servir d'interface permettant de transmettre un flot de donnees de type caracteres de l'utilisateur vers l'application (grace au clavier) et de l'application vers l'utilisateur grace a l'ecran.
1. Associer une console a notre application, 2. Recuperer les handles associes a cette console devant nous permettre d'y ecrire et d'y lire, 3. (optionnel) Formatter l'apparence de la console a notre bon gre, 4. Utiliser la console.D'un point de vue pratique, il va nous falloir d'abord attacher une console au process de notre application. La fonction AllocConsole() est la pour nous aider dans cette demarche.
HANDLE hConsoleStdout = GetStdHandle( STD_OUTPUT_HANDLE ); HANDLE hConsoleStdin = GetStdHandle( STD_INPUT_HANDLE ); HANDLE hConsoleStderr = GetStdHandle( STD_ERROR_HANDLE );Il nous est alors possible d'ecrire dans la console avec quelques fonctions specifiques du type WriteConsole(), ReadConsole(), etc ... On peut change la couleur, la taille des caracteres et de la console elle meme, ce qui pourrait nous permettre de faire une console qui affiche les messages de log en vert, les warning en bleu et les messages d'erreur en rouge par exemple ! ...
freopen("CONIN$", "rb", stdin); freopen("CONOUT$", "rb", stdout); freopen("CONOUT$", "rb", stderr);Facile hein ? :) On peut imaginer enormement d'applications a ce types de solutions. Voici un exemple simple de ce type de console en action.
L'API win32 de Windows offre un grand nombre de fonctions facilitant le debuggage des applications. Nous n'avons qu'effleure les possibilites offertes et il faudrait au moins 1 ou 2 tutorial en plus pour en explorer les possibilites (peut etre que je m'y mettrais si je recois des demandes). Mais il faut bien que je me force a m'arreter un jour parce que deja la j'ai tellement rajoute de choses non prevues initialialement que si je continue non seulement ce tut ne sortira jamais mais en plus il fera 10 pages de plus. Je vais quand meme me faire un petit plaisir :) en rajoutant une fois de plus une choses non prevue. Je voudrais en effet dire 2 mots sur les problemes de memory leaks et sur la maniere dont Windows peut nous aider a y trouver une solution. Ces problemes ne sont pas si eloignes que ca du sujet de debug puisqu'ils sont symptomatiques de bugs existant dans l'application.
Tout d'abord, qu'est ce qu'un memory leak ?
Et bien comme son nom l'indique, il s'agit d'une fuite de memoire incontrolee
dans votre application. La petite experience que je peux avoir dans le monde professionel en tant qu'ingenieur
en informatique montre que les risques de memory leaks augmentent exponentiellement
au fur et a mesure que la taille du projet augmente et que le nombre de personnes
concernees augmente aussi. Il s'agit vraiment la d'une plaie dont il est preferable
d'etre conscient afin de pouvoir des le debut d'un projet prendre des bonnes habitudes.
Un memory leak intervient lorsque vous avez allouer dynamiquement de la memoire
dans votre code et que vous n'avez pas de liberation associee a cett eportion de memoire
allouee. Etant donne qu'en C ou C++ il n'y pas de garbage collector comme en Java ou en
Smalltalk par exemple c'est a vous de prendre en charge la gestion de memoire.
Les memory leaks peuvent provoquer des maux bien ennuyeux a l'environnement dans
lequel tourne votre application et donnent une impression de non professionel
et de non-fini a votre application. Elle peuvent ainsi etre responsable de nombreux
symptomes bien ennuyeux comme une augmentation dramatique du swap sur disque et donc
un ralentissemnt de votre environnement, etc ...
Voici un exemple bien bete de memory leak :
class CMemoryLeakSource { public: CMemoryLeakSource(); ~CMemoryLeakSource(); private: char * m_lpDummy; } CMemoryLeakSource::CMemoryLeakSource() { m_lpDummy = new char[10]; } CMemoryLeakSource::~CMemoryLeakSource() { // la memoire allouee devrait etre liberee } int main(void) { CMemoryLeakSource test; return 0; }Cet exemple peut paraitre stupide (et il l'est) mais ce genre d'erreur est frequente dans un gros projet quand on en vient, presse par les deadlines, a patcher violement le code pour faire en sorte que ca marche a grand coup de cut-and-paste, etc ...
#define _CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h>En utilisant ces fonctions de diagnostic memoire, il vous est possible de maniere tres simple d'avoir des rapports relativements detailles sur les memory leaks que peut creer votre application.
Voici une serie d'utilitaires commerciaux ou non parmi les plus connu permettant
une gestion/detection des memory leaks travaillant au niveau du binaire directement ou
du code source :
BoundsChecker | http://www.compuware.com/products/numega/bounds/ |
Purify de Rational | http://www.parasoft.com/products/insure/ |
Electric Fence | http://perens.com/FreeSoftware/ |
ZeroFault | http://www.tkg.com |
Fortify | http://www.geocities.com/SiliconValley/Horizon/8596/fortify.html |
Nous voila enfin au terme de ce tutorial qui aura finalement balaye plusieurs sujets plus generaux
concernant la programmation sous Windows. J'espere que vous commencez a etre un peu a l'aise avec
la programmation sous Windows. Si ce n'est pas le cas n'hesitez pas a m'ecrire, je reponds a tous
les emails que je recois plus ou moins rapidement en fonction de mon temps libre. N'hesitez pas
aussi a me faire parvenir des critiques, remarques, et des idees de sujets que vous voudriez voir aborde.
Pour vous donner une idee sur ce qui va venir dans les mois prochains, voici ce que je prevois d'aborder
(dans l'ordre) :
- Les problemes d'ajustement de taille de fenetre, - Les precompiled headers, - Le format PE, - Le multi-thread en profondeur (3 tuts), - Les "common controls" (? tuts), - Les DLLs et autres libs (3 tuts),Voila ca en fait des choses pour cette annee prochaine, on verra la ou j'en serais en debut d'annee prochaine. Je suis ouvert au fait d'aborder des sujets autres biensur.
http://www.cs.colorado.edu/homes/zorn/public_html/MallocDebug.html http://www.codeguru.com/cpp_mfc/MemoryTracking.shtml http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/memleaks.asp http://tehran.stanford.edu/Iran_Lib/doreh/Events/1995/april.2.1995.html http://www.rational.com/products/whitepapers/319.jsp http://personal.cfw.com/~tkprit/page/leak.html http://www.edm2.com/0508/membug.html--