--------------------------------------------------------------------------------------------------- IV. Programmation Win32asm Part1. Disk-Lexic --------------------------------------------------------------------------------------------------- [ Introduction ] Salut à tous ! C'est mon premier article pour IOC Magazine et j'ai choisi de vous parler d'un sujet que l'on ne rencontre pas assez souvent dans les textes français ... Souvenez vous , au départ était DOS, le programmeur en assembleur était heureux, il pouvait écrire partout ou il voulait, en mémoire,sur les ports d'entrée-sorties, sur les périphériques, il était maitre absolu de sa machine et on aurait pu croire que rien ne l'arrêterait. Et c'est alors que Microsoft à crée Windows. Et voilà que tout était changé, sous l'oppression du mode protégé, il ne peut plus faire tout ce qu'il désire, car la firme de Redmond a décidé de mettre des restrictions un peu partout. C'est ainsi que beaucoup se sont mis à programmer dans des langages de haut niveau. Depuis, de nombreux étudiants ne se doutent même plus de ce qui se passe réellement au sein de leurs machines. Et certains osent même dire qu'apprendre l'assembleur ne sert plus à rien. Contre ceux-là je me révolte et préfèrent rejoindre ces qulques irréductibles qui on choisi de ne pas subir cette dictature. Nous continuerons à vouloir tout savoir sur le déroulement des programmes, nous continuerons à vouloir tout apprendre sur la structure de vos fichiers. Nous ne vous laisserons pas nous aveugler derrière vos tonnes de couches logicielles que vous interposez entre l'utilisateur et la machine. J'ai opté pour une démarche progressive et j'essairais tant que possible de la respecter. Cela étant dit , Vous remarquerez que je ne suis pas parti du point zéro. Je n'ai pas l'intention de vous apprendre les bases de la programmation en assembleur, cela a été fait des centaines de fois. Pour cela je vous conseille de lire les e-zines 'reporter' et 'prograzine'. Je vous laisserais aussi le soin de vous renseigner sur les API windows par vous mêmes car je pense qu'il y a déjà suffisamment de documentation sur le sujet. Je vais donc tenter de vous initier à la programmation win32asm à l'aide du compilateur TASM. Parce que j'en ai assez que les informations ne concernent en majorité que MASM. Bonne lecture et j'espère que je permettrais à beaucoup d'apprendre et de progresser . Allez , c'est parti, jetons nous à l'eau ! [ Let's Go ] Vous aurez besoin : -du compilateur TASM 5.0 (trouvable sur internet, essayez www.crackstore.com) -de tasmedit (disponible sur mon site, c'est plus agréable que wordpad ou EDIT) -d'ub déboggueur (Softice ou Turbo debugger ) . Ca sert toujours ! -de la liste des API windows ( trouvable sur internet ) -de rigueur -de discipline -de patience -de sommeil ( à ne pas négliger. Si si J'y tiens) Avant toutes choses, essayez de vous débarasser des concepts que vous appris en assembleur en mode réel. L'asm win32 suit des règles qui lui sont propres. Pour commencer, si on veut faire des programmes win32, il est nécessaire de déclarer et de disposer des fonctions et les strucures spécifiques à l'utilisation des directives Win32 . Ces fonctions sont contenues dans le fichier 'WIN32.INC' . Pour cela, copiez le fichier 'c:\tasm\examples\wap32\win32.inc' vers le répertoire 'c:\tasm\include\' On commence donc par la façon dont on déclare les fonctions. Ceci est exemple : extrn BeginPaint : PROC Faites attention à bien respecter la casse, car les programmes win32 y sont sensibles ! Pour appeler cette fonction , on fait : Call BeginPaint Je dois vous préciser qu'il y a deux types de caractères dans les programmes win32, ANSI et UNICODE . Pour utiliser des chaines du type ANSI , faites suivre les fonctions agissant sur les chaines par un A, et pour UNICODE, par un W . example : extrn TextOutA : PROC extrn GetWindowTextW : PROC Ensuite, il faut définir les types et strctures des données (Il serait nécessaire d'avoir une référence des API windows que vous pourrez trouver dans VisualC++ par exemple). Donc, on définit les strucures de la façon suivante: MSGSTRUCT struc msHWND UINT ? msMESSAGE UINT ? msWPARAM UINT ? msLPARAM UINT ? msTIME ULONG ? msPT ULONG 2 dup(?) MSGSTRUCT ends Il faut à présent définir les types de données: HDC equ
Les fonctions win32 nécessitent pour la majorité d'entre elles de recevoir des arguments . TASM 5.0 possède une fonction qui permet de le faire à la façon des langages de haut niveau : CALL FUNC, Parms Mais on peut qussi le faire manuellement PUSH long 0 PUSH long 0 PUSH long 0 PUSH offset Msg CALL GetMessage Bon c'est pas compliqué, on met les paramètres sur la pile dans l'ordre inverse à leur appel. Pour mieux saisir, regardez la façon dont se fait l'appel de cette fonction en langage C : GetMessage(&Msg, 0, 0, 0) ; Toutes les valeurs de retour des fonctions sont mises dans le registre EAX. Ici, la fonction GetMessage retourne un ZERO lorsqu'elle se termine . MessageLoop: PUSH long 0 PUSH long 0 PUSH long 0 PUSH offset Msg CALL GetMessage test eax,eax ; Doit on quitter la boucle ? jz short EndProgram ; Si oui, on quitte le programme push offset Msg CALL TranslateMessage push offset Msg CALL DispatchMessage jmp short MessageLoop EndProgram: push [Msg.wParam] Call ExitProcess ; Fin du programme Bon , là nous avons vu quelques briques de base en programmation Win32asm. Nous allons désormais voir comment construire nos programmes . La première chose à faire , et que vous devez déjà savoir, c'est de déclarer le type de processeur pour lequel est destiné le programme. .386 ou .486 ou encore .586 La différence est dans le fait que déclarer, par exemple, l'utilisation du processeur 486 vous permettra d'utiliser les instructions spécifiques au 486( et qui sont donc non disponibles sous le 386,. Logique non !! ) ; Les instructions du 386 sont en général largement suffisante. Ensuite , nous définissons le modèle de mémoire que l'on va utiliser. .MODEL FLAT, STDCALL Je vous parlerais des modèles mémoire dans un futur article. Bon maintenant, on peut mettre nos include. include WIN32.INC C'est ce qui permettra à l'éditeur de liens ( TLINK32 dans notre cas ) d'importer les fonctions et les structures qui nous interressent ( Je rappelle que lors d'une compilation, toutes les fonctions externes à notre code source sont 'liées et compilées' lors de l'édition de lien ) . Bon, continuons ! C'est au tour des variables et des constantes de prendre place dans notre programme . .data Titre db "Ceci est le titre de mon beau programme",0 Et le code ! ..code extrn ExitProcess: Proc extrn BeginPaint: Proc DEBUT : ; notre code end DEBUT ; fin du prog Pour les données, voici ce dont nous aurons besoin : Titre db "Titre de la fenetre",0 NonClasse db "Fenetre",0 Msg MSGSTRUCT ; La structure du message WC WNDCLASS ; La classe de la fenêtre hwnd dd ? ; Handle sur une fenêtre hInstance dd ? ; Handle sur une instance On démarre notre code par un appel à GetModuleHandle. Ca retourne un module, qui est l'équivalent de HINSTANCE . push long 0 CALL GetModulehandle mov [hInstance], eax ; On vient de récupérere l' instance. Une fois en possession de notre hInstance, on veut aussi avoir la classe . Un appel à RegClass et hop ! CALL RegClass En faisant une procedure , on aura un code un peu plus propre en ce qui concerne notre bloc WinMain . Regclass PROC mov eax, [hInstance] mov [WC.clsHInstance],eax mov [WC.clsStyle], 0 mov [WC.clsLpfnWndProc], offset WndProc mov [WC.clsCbClsExtra], 0 mov [WC.clsCbWndExtra], 0 push long IDI APPLICATION ; Charge l'icone push eax call LoadIcon mov [WC.clsHIcon], eax mov [WC.hIconSm],eax push long IDC_ARROW ; Charge le curseur push long 0 call LoadCursor mov [WC.clsHCursor], eax push long BLACK_BRUSH ;Un fond d'écran noir call GetStockObject mov [WC.clsHbrBackground],eax mov [WC.clsLpszMenuName], 0 mov [WC.clsLpszClassName], offset ClassName push offset WC call RegisterClass ; On enregistre notre fenêtre RET ENDP Nous devons à présent créer la fenêtre. call CreateWind ; Crée la fenêtre qu'on a précedemment définie. Ici aussi on peut faire une procedure séparée pour avoir un WinMain plus clair. CreateWind PROC push long 0 push [hInst] push long 0 push long 0 push long CW_USEDEFAULT push long CW_USEDEFAULT push long CW_USEDEFAULT push long CW_USEDEFAULT push long WS_OVERLAPPEDWINDOW ;Style de fenêtre se rafraichissant en cas de masquage push offset TitleName push offset ClassName push long 0 ; Les options suivantes concernent CreateWindowEx ; Il s'agit du type fenêtre étendue call CreateWindowEx ; Crée la fenêtre mov [hwnd], eax ; handle de la fenêtre parent retourné par CreateWindowEx RET ENDP Maintenant, on fait appel à ShowWindow et UpdateWindow push long SW_SHOW push [hwnd] call ShowWindow push [hwnd] call UpdateWindow Ceci va donc creer et afficher la fenêtre sur l'écran. N'oublions que nous sommes en programmation évenementielle et que l'on doit gérer la boucle des messages dans WinMain. Je me permet alors de faire un petit rappel sur la programmation evenementielle selon windows. On a une sorte de grosse boucle qui attend qu'on lui demande d'effectuer des actions tant que l'on ne lui ordonne pas de s'arrêter. En fait vous avez probablement déjà fait de la programmation evenementielle sans forcément le savoir. Mais , si, quand vous faites des menus à choix en mode texte. C'est du genre : Tant qu'on ne quitte pas faire : -si 'enregistrer' alors executer la fonction 'Save' -si 'charger' alors executer la fonction 'Load' -si 'imprimer' alors executer la fonction 'Print' -si 'patati patata alors etc, etc, etc............. -si 'quitter' alors executer la fonction 'Exit' Fin de la grosse boucle.(Le programme se termine). Ben pour windows c'est à peu près pareil Les actions sont saisies dans une structure MSG que voici struct MSG { HWND hWnd ; // Handle de la fenêtre UINT message ; // Id du message WPARAM wParam ; // Paramètre du message (32bits) LPARAM lParam ; // Paramètre du message DWORD time ; // Heure de mise en file d'attente POINT pt ; // Position de la souris } alors ça resssemble à ceci : Attent une entrée dans MSG : Si on a une entrée pour MSG, on la met dans la file d'attente Si message est QUIT, alors on sort du programme Sinon On Transforme le message en binaire via la fonction TranslateMessage(&MSG) Puis on execute notre message via la fonction DispatchMessage(&MSG) Fermeture de boucle . Et voilà , rien de bien sorcier ! Et en assembleur , ça nous donne ceci : MessageLoop : push long 0 push long 0 push long 0 push offset Msg call GetMessage ; Appel de la fonction GetMessage test eax,eax ; A t'on le message 'exit' jz short EndProgram ; si oui , on va à la fin du programme push offset Msg call TranslateMessage ; sinon, on traduit le message en binaire push offset Msg call DispatchMessage ; et on execute le message ! jmp short MessageLoop ; On continue notre manège jusqu'à plus soif ! EndProgram: ; Ca , c'est au cas ou on a plus soif justement ;-) push [Msg.wParam] call ExitProcess ; A tchao ! Comme vous avez pu le voir , ça ressemble bien au schéma général que je vais fait juste avant. Donc , au final, notre cher petit programme ressemble à ce qui suit : .486p .MODEL FLAT, STDCALL include WIN32.INC .DATA Msg MSGSTRUCT ; La structure Message WC WNDCLASS ; La classe Windows hwnd dd ? ; Handle de fenêtre hInstance dd ? ; Handle de l'Instance .CODE START : push long 0 call GetModule mov [hInstance], eax ;On prend l'instance call RegClass ; Enregistrement de la Class call CreateWind ; Création de la fenêtre push long SW_SHOWNORMAL push [hwnd] call ShowWindow ; Montre la fenêtre push [hwnd] call UpdateWindow ; Rafraichit la fenêtre MessageLoop: push long 0 pus long 0 push long 0 push offset Msg call GetMessage ; Appel de la fonction GetMessage text eax,eax ; Doit on sortir ? jz short EndProgram ; oui, alors on s'éclipse push offset Msg call TranslateMessage ; Non, on Traduit le message en binaire push offset Msg call DispatchMessage ; et on execute notre action jmp short MessageLoop ; Ce tant qu'on pas recu l'ordre de sortir EndProgram: ; On an choisi d'en finir push [Msg.wParam] call ExitProcess ; On a fini ;On est à la fin de WinMain Regclass PROC mov eax, [hInstance] mov [WC.clsHInstance],eax mov [WC.clsStyle], 0 mov [WC.clsLpfnWndProc], offset WndProc mov [WC.clsCbClsExtra], 0 mov [WC.clsCbWndExtra], 0 push long IDI APPLICATION ; Charge l'icone push eax call LoadIcon mov [WC.clsHIcon], eax mov [WC.hIconSm],eax push long IDC_ARROW ; Charge le curseur push long 0 call LoadCursor mov [WC.clsHCursor], eax push long BLACK_BRUSH ;Un fond d'écran noir call GetStockObject mov [WC.clsHbrBackground],eax mov [WC.clsLpszMenuName], 0 mov [WC.clsLpszClassName], offset ClassName push offset WC call RegisterClass ; On enregistre notre fenêtre RET ENDP CreateWind PROC push long 0 push [hInst] push long 0 push long 0 push long CW_USEDEFAULT push long CW_USEDEFAULT push long CW_USEDEFAULT push long CW_USEDEFAULT push long WS_OVERLAPPEDWINDOW ;Style de fenêtre se rafraichissant en cas de masquage push offset TitleName push offset ClassName push long 0 ; Les options suivantes concernent CreateWindowEx ; Il s'agit du type fenêtre étendue call CreateWindowEx ; Crée la fenêtre mov [hwnd], eax ; handle de la fenêtre parent retourné par CreateWindowEx RET ENDP END START ;Fin du fichier Vous pensez peut être qu'on en a fini , hein , Et bien non ! Il se peut que les appels call RegClass et call CreateWind renvoient une erreur, et ce parce que la fenêtre n'a pas été correctement enregistrée. Et bien il nous suffit de tester si eax est égal à 0, et si c'est le cas, de faire appel à ExitProcess avec 0 en paramètre. call RegClass text eax,eax ; on vérifie qu'il n'y a pas d'erreur jnz short NO_ERROR push long 0 call ExitProcess NO_ERROR: call CreateWind ; Crée la fenêtre test eax,eax ; On vérifie qu'il n'y a pas d'erreur jnz short WINDOW_CREATED push long 0 call ExitProcess WINDOW_CREATED: .... .... .... Et je suppose que vous vous dites , oui, c'est bien beau , on a crée une fenêtre , mais notre programme , il ne fait encore rien ! Ca vient , ça vient . La patience est une vertu qui s'offre à celui qui sait attendre. Et bien les actions que l'on va envoyer à notre fenêtre s'appellent des callbacks . Voici comment cela se fait : WndProc PROC USES ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:DWORD, lparam:DWORD ; vous placez le code ici ENDP Un tout piti exemple de code : mov eax, [Msg] cmp eax, WM_CREATE je W_CREATE push [lParam] push [wParam] push [Msg] push [hwnd] call DefWindowProc ; procedure par défaut RET W_CREATE: ; du code xor eax, eax RET Ah , j'allais oublier, pour compiler tout ça ! Vous trouverez avec TASM un makefile pour WAP32 (c'ad Windows application 32 bits) dans TASM\EXAMPLES\WAP32\MAKEFILE . Copiez le là ou se trouve le source de cotre programme changez la ligne NAME = WAP32 par NAME = Nomdevotreprogramme Plus loin, vous trouverez ceci : OBJS = $ (NAME) .obj DEF = $ (NAME).def et bien sous ces deux lignes vous pouvez ajouter des ressources si vous en disposez par la ligne suivante RES = $ (NAME) .RES A chaque fois , vous remplacer $ (name) par le nom de votre programme. N'oubliez pas que toutes les composantes doivent avoir le même nom. par exemple : toto.asm, toto.res, toto.rc, etc...) Vous copier WAP32.DEF dans le même répertoire que votre code source et vous le renommer en nomdevotreprogramme.def . Vous faites un MAKE -b ou un MAKE -b DEBUG (Pour avoir les informations de déboggage) Pour linker : tlink32 /Tpe /aa /c $ (LINKDEBUG) $ (OBJS), $ (NAME),, $ (IMPORT), $ (DEFS), $ (RES) Attention : ne mettez pas $ (RES) si vous n'avez de ressources [ Conclusion ] Voilà, ça y est, c'est fini ... On poursuivra la prochaine fois ! Disk-Lexic www.disk-lexic.fr.st