ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Coding in asm : les boites de dialogues º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ by Androgyne for hccc#6 Intro ~~~~~ Cet article traite des boites de dialogue en asm. Les outils necessaires pour cet article sont TASM32, TLINK32, IMPORT32.LIB pour la compilation, ainsi que RLINK32.DLL pour pouvoir linker vos ressources. Il vous faudra egalement un editeur de ressources, j'utilise personnellement Borland Ressource Workshop 4.5 (si quelqu'un en connait un autre plus recent, faite moi signe :) ). Les boites de dialogues ~~~~~~~~~~~~~~~~~~~~~~~ Une boite de dialogue, c'est quoi ? En fait, c'est simplement des objets qui servent à communiquer avec l'utilisateur, reunis dans une boite. Ces objets peuvent etre de nature diverse : boite de texte, boutons, checkbox (les options avec les croix), listbox (listes deroulantes...), editbox (les zones ou vous pouvez mettre du texte), etc. Comment fait-on pour faire une de ces boites ? On utilise un editeur de ressources. En fait, ces boites sont stockees dans votre exécutable (generalement dans la section .rsrc) avec toutes les autres ressources de votres programmes : icônes, curseurs, images, version (les informations que vous avez de temps en temps quand vous faites 'Proprietes') et donc boites de dialogues. Fabriquer une boite n'est pas dur en soi, surtout avec un éditeur de ressources. Cet editeur va vous creer un .rs qui sera linke avec votre executable... Vous allez ajouter divers element sur votre boite et l'editeur de ressources va vous demandez de les nommer. Choisissez des nombres et soyez coherent : par exemple, partez de 100 pour tous les elements de la boites, partez de 200 pour les icônes et partez de 300 pour les boites elle-memes... Ces nombres serviront à identifier vos elements. Gerer vos boîtes de dialogue ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Les boites de dialogues sont faites comme des procédures appelées par Windows. Dès que vous faites quelquechose sur la boite de dialogue, Windows appelle cette procédure avec 4 arguments : l'handle de votre boîte, le message, le premier parametre du message, le deuxieme parametre du message. Ce systeme de message est tres important a comprendre : toutes les communications entre les elements de votre boite, l'utilisateur, Windows, etc se font par ces messages. Generalement, ces messages ont deux arguments. Il y en a beaucoup alors je ne vais pas en faire la liste ici (vous pouvez la trouver dans win32.hlp). La principale API utilisee est DialogBoxParam. On va détailler un peu cette API : --- DialogBoxParam ----------------------------------------------------------- int DialogBoxParam(hinst, lpszTemplate, hwndOwner, dlgprc, lParamInit) HINSTANCE hinst; /* handle of application instance */ LPCTSTR lpszTemplate; /* identifies dialog box template */ HWND hwndOwner; /* handle of owner window */ DLGPROC dlgprc; /* address of dialog box procedure */ LPARAM lParamInit; /* initialization value */ --- DialogBoxParam ----------------------------------------------------------- -> hinst : c'est l'handle du module qui contient les ressources pour la boîte de dialogue ; généralement, c'est votre programme. Pour obtenir cet handle, vous devez alors utilisez l'API GetModuleHandleA (0 en argument). -> lpszTemplate : il s'agit de l'identifiant de votre boîte tel que vous l'avez défini dans l'éditeur de ressources. Vous pouvez utilisez des EQU pour faciliter la lecture (ex : MAIN_BOX equ 301). -> hwndOwner : c'est l'handle de celui à qui appartient cette boite, si c'est votre programme, c'est 0 ; si c'est une boite de dialogue, c'est l'handle de votre boîte de dialogue (il est dans les parametres de votre boîte de dialogue). -> dlgprc : c'est un pointeur sur la procédure de gestion de votre boite. On va en repaler tout de suite parce que c'est là que tout se trouve. -> lParamInit : c'est le parametre du message d'initialisation de votre boite. (voir tout de suite apres). Concrètement, notre programme principal va être très court... --- Main Procedure ----------------------------------------------------------- call GetModuleHandleA, 0 mov h_module,eax call DialogBoxParamA, h_module, MAIN_BOX, 0, offset MyBox, 0 call ExitProcess, 0 --- Main Procedure ----------------------------------------------------------- Voilà, c'est tout... Enfin, presque. Maintenant, il va falloir faire la procédure de gestion de la boite (ici, elle s'appelle MyBox). C'est cette procedure que Windows va appeler chaque fois qu'il va y avoir quelque chose sur notre boite. On doit donc faire comme si c'était une procedure comme une autre. --- MyBox(1) ----------------------------------------------------------------- MyBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD xor eax,eax ret MyBox endp --- MyBox(1) ----------------------------------------------------------------- C'est le plus simple que l'on puisse faire. Mais là, vous allez avoir un petit problème, votre boîte de dialogue ne va rien faire, vous n'allez meme pas pouvoir la fermer... Le paramètre 'wmsg' contient les messages que nous envoie Windows. Le premier de ces messages est le message d'initialisation WM_INITDIALOG. Ce message n'a qu'un seul parametre, celui que vous avez envoyé en appelant la boite de dialogue. A ce moment, vous pouvez initialiser certains elements de votre boite de dialogue. Par exemple, on va mettre l'icone que l'on a fabriqué dans la barre des taches a la place de l'icone Windows par defaut. Tout d'abord, on modifie la procédure MainBox : --- MyBox(2) ----------------------------------------------------------------- MyBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD cmp [wmsg],WM_INITDIALOG je init_box xor eax,eax ret MyBox endp --- MyBox(2) ----------------------------------------------------------------- Et on fait une routine init_box... On va utiliser deux API : tout d'abord l'API LoadIcon qui va nous permettre de charger la jolie icône que l'on a faite. Et on va recuperer l'handle de l'icône chargee qui va ensuite nous servir dans le message que l'on va envoyer à notre boite... --- LoadIcon ----------------------------------------------------------------- HICON LoadIcon(hinst, lpszIcon) HINSTANCE hinst; /* handle of application instance */ LPCTSTR lpszIcon; /* icon-name string or icon resource identifier */ --- LoadIcon ----------------------------------------------------------------- -> hinst : c'est la meme chose que pour DialogBoxParam -> lpszIcon : c'est l'identifiant de votre icône tel que vous l'avez défini dans l'editeur de ressources. Vous pouvez utilisez des EQU pour faciliter la lecture (ex : MY_ICONE equ 201). Ensuite, on envoie un message à notre boîte. Pour cela, on utilise SendMessage : --- SendMessage -------------------------------------------------------------- LRESULT SendMessage(hwnd, uMsg, wParam, lParam) HWND hwnd; /* handle of destination window */ UINT uMsg; /* message to send */ WPARAM wParam; /* first message parameter */ LPARAM lParam; /* second message parameter */ --- SendMessage -------------------------------------------------------------- -> hwnd : c'est l'handle de celui qui reçoit le message, dans notre cas, ça va être notre boîte de dialogue donc, on renvoie _hwind. -> uMsg : c'est le message que l'on envoie ; chaque message correspond à un nombre, vous pouvez trouver l'ensemble des EQU utiles pour gerer les boîtes de dialogues avec cet article ; intégrer cette liste dans win32.inc et faite un include au debut de votre programme, ca vous aidera. Dans notre cas, on va envoyer le message WM_SETICON (voir ci-après). -> wParam, lParam : c'est le premier et le deuxieme parametres de notre message... Pour chaque message, les parametres ont une signification differentes. Je n'ai pas trouve les parametres exacts du message WM_SETICON mais je pense que le premier represente le style de l'icone et le deuxieme l'handle de l'icone que l'on a obtenu avec LoadIcon. Voilà donc la procédure init_box : --- init_box ----------------------------------------------------------------- init_box: call LoadIconA, h_module, MY_ICONE call SendMessageA, _hwind, WM_SETICON, 0, eax ret --- init_box ----------------------------------------------------------------- Le dernier ret sert à rendre le controle a Windows et pas a revenir dans notre procedure principale (on n'a pas fait de call). En rendant le controle a Windows, on n'arrete pas le programme, on sort simplement de la procedure de gestion des evenements de notre boîte de dialogue. Mais elle existe toujours. Là, c'est pas mal, on a une jolie icone mais on ne peut toujours rien faire, même pas sortir de cette boite. On va régler ce probleme maintenant. On peut recevoir deux messages pour lesquels on doit fermer la boîte : WM_CLOSE et WM_DESTROY. Les subtilites entre les deux messages ne nous intéressent pas vraiment. Ces deux messages n'ont aucun paramètres. Que doit-on faire quand on reçoit ce message ? On doit dire à notre boître de s'autodétruire... Notre procédure doit alors appeler l'API EndDialog : --- EndDialog ---------------------------------------------------------------- BOOL EndDialog(hwndDlg, nResult) HWND hwndDlg; /* handle of dialog box */ int nResult; /* value to return */ --- EndDialog ---------------------------------------------------------------- -> hwndDlg : c'est l'handle de notre boîte de dialogue. -> nResult : c'est la valeur qu'on veut renvoyer, ca peut servir pour les grosses applications mais pour nous, ça sert a rien. On modifie alors une nouvelle fois la procedure MyBox : --- MyBox(3) ----------------------------------------------------------------- MyBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD cmp [wmsg],WM_INITDIALOG je init_box cmp [wmsg],WM_CLOSE je kill_box cmp [wmsg],WM_DESTROY je kill_box xor eax,eax ret MyBox endp --- MyBox(3) ----------------------------------------------------------------- Et on fait la procédure kill_box. --- kill_box ----------------------------------------------------------------- kill_box: call EndDialog, _hwind, 0 --- kill_box ----------------------------------------------------------------- A ce moment, la boite va se fermer et Windows va revenir à notre programme principal pour exécuter ExitProcess. Un peu plus loin dans les boites de dialogue ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On va faire un petit programme de démonstration, un programme qui ne sert à rien mais c'est juste pour montrer ce qu'on peut faire... Ce programme va afficher les noms du repertoire Windows, du repertoire Systeme et du repertoire courant. Je vais utiliser la trame deja definie et je vais rajouter quelques petites choses. Par exemple, un bouton. Les boutons sont tres faciles a gerer. En fait, chaque fois qu'un bouton est presse, Windows envoie le message WM_COMMAND (c'est pareil pour tous les elements de contrôles). Ensuite, pour savoir d'ou vient le message, on regarde le premier parametre qui contient l'identifiant du bouton. Et là, on agit en fonction. Par exemple, on fait un bouton 'Quit' qui sert à quitter. Voila toutes les nouveautes dans le code. --- MyBox(4) ----------------------------------------------------------------- MyBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD cmp [wmsg],WM_INITDIALOG je init_box cmp [wmsg],WM_CLOSE je kill_box cmp [wmsg],WM_DESTROY je kill_box cmp [wmsg],WM_COMMAND je events xor eax,eax ret kill_box: call EndDialog, _hwind, 0 init_box: call LoadIconA, h_module, MY_ICONE call SendMessageA, _hwind, WM_SETICON, 0, eax ret events: cmp [wparama], BUTTON_QUIT je kill_box ret MyBox endp --- MyBox(4) ----------------------------------------------------------------- Jusque là, ca va... Maintenant, on peut essayer de faire un peu plus compliqué. On va essayer d'appeler une boîte de dialogue a partir de cette boite de dialogue, et plus precisement d'un bouton 'About'. Pour cela, on procede de la meme facon sauf qu'on appelle DialogBoxParam avec hwndOwner egal à l'handle de la fenêtre. Voilà ce que ca donne. --- MyBox + AboutBox --------------------------------------------------------- MyBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD cmp [wmsg],WM_INITDIALOG je init_box cmp [wmsg],WM_CLOSE je kill_box cmp [wmsg],WM_DESTROY je kill_box cmp [wmsg],WM_COMMAND je events xor eax,eax ret kill_box: call EndDialog, _hwind, 0 init_box: call LoadIconA, h_module, MY_ICONE call SendMessageA, _hwind, WM_SETICON, 0, eax ret events: cmp [wparama], BUTTON_ABOUT je event_about cmp [wparama], BUTTON_QUIT je kill_box ret event_about: call DialogBoxParamA, h_module, ABOUT_BOX, _hwind, offset AboutBox, 0 ret MyBox endp AboutBox proc _hwind:DWORD, wmsg:DWORD, wparama:DWORD, lparama:DWORD cmp [wmsg],WM_INITDIALOG je init_about cmp [wmsg],WM_CLOSE je kill_about cmp [wmsg],WM_DESTROY je kill_about xor eax,eax ret kill_about: call EndDialog, _hwind, 0 init_about: call LoadIconA, h_module, MY_ICONE call SendMessageA, _hwind, WM_SETICON, 0, eax ret AboutBox endp --- MyBox + AboutBox --------------------------------------------------------- On peut enchaîner les boîtes de dialogues de cette maniere. Que se passe-t-il si on appelle notre nouvelle boite de dialogue avec 0 a la place de l'handle de notre premiere boite ? Windows va bien creer la boite mais elle ne va pas etre chainee avec la premiere donc, vous pourrez supprimer la premiere et la deuxieme sera toujours la... ca peut poser des petits problemes. :) Maintenant, on peut egalement communiquer avec certains elements, notamment pour les initaliser. Pour cela, on va utiliser SendDlgItemMessage : --- SendDlgItemMessage ------------------------------------------------------- LONG SendDlgItemMessage(hwndDlg, idControl, uMsg, wParam, lParam) HWND hwndDlg; /* handle of dialog box */ int idControl; /* identifier of control */ UINT uMsg; /* message to send */ WPARAM wParam; /* first message parameter */ LPARAM lParam; /* second message parameter */ --- SendDlgItemMessage ------------------------------------------------------- -> hwndDlg : c'est l'handle de la boite à laquelle appartient le contrôle. -> idControl : c'est l'identifiant du controle tel que vous l'avez défini dans l'editeur de ressources. -> uMsg, wParam, lParam : c'est la meme chose que pour SendMessage. Par exemple, on a défini un contrôle EditText que l'on veut initialiser avec la chaine placé dans le buffer my_string. Il faut envoyer le message WM_SETTEXT à ce contrôle. Le premier parametre est 0 et le second paramètre est un pointeur vers la chaîne en question. Voilà par exemple ce que ca pourrait donner : --- SendDlgItemMessage, exemple ---------------------------------------------- call SendDlgItemMessageA, _hwind, MY_EDITTEXT, ET_WIN, WM_SETTEXT, 0, offset my_string --- SendDlgItemMessage, exemple ---------------------------------------------- Outro ~~~~~ Voila, je pense qu'on arrive a la fin. Maintenant, on peut faire notre programme. La ressource se trouve avec le mag ainsi que le code source complet. La seule difficulte maintenant sera à chaque fois de trouver comment gerer telle ou telle chose, c'est parfois long, il faut parcourir win32.hlp de nombreuses fois avant de trouver la bonne rubrique. Je vous souhaite un bon coding. Et si vous avez des difficultes, n'hesitez pas à m'ecrire, je me ferai un plaisir de vous aider dans la mesure de mes moyens :) J'ai peut etre fait quelques erreurs dans cet article, n'hesitez pas a m'envoyer des correctifs.