---------------------------------------------------------------------------------------------------
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