Téléchargez le fichier d'exemple ici.
.386
.model flat, stdcall
.data
.code
start:
end start
L'exécution commence à la première instruction
immédiatement au-dessous de l'étiquette Start:
Le programme s'exécutera instruction par l'instruction jusqu'à ce qu'une instruction de contrôle de
direction comme jmp,
jne, je
,ret Etc..soit trouvée.
Ces instructions dirigent l'exécution vers d'autres lignes d'instructions. Quand le programme a besoin de
sortir de Windows pour se terminer, il doit appeler la fonction ExitProcess qui est une API de la DLL
Kernel32.dll.
ExitProcess
proto uExitCode:DWORD
Ci dessus, ce type d'expression est appelé un
prototype de fonction. Un prototype de fonction définit les attributs d'une fonction pour ensuite
transformer cette expression (pseudo-assembler) en véritable assembler. Le format d'un prototype de
fonction est le suivant :
FunctionName
PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
En résumé, le mot-clé PROTO est précédé du nom de
la fonction puis suivi de la liste des paramètres, séparés par des virgules. Dans l'exemple ExitProcess ci-dessus,
il définit ExitProcess comme une fonction qui prend seulement un seul paramètre de type DWORD. Les
prototypes de fonctions sont très utiles quand vous employez la syntaxe d'appel de niveau haut :INVOKE. En plus,
le Linker vérifiera si vous avez correctement utilisé la fonction que vous invokez. En outre, il vérifiie
que ses paramètres ne sont pas oubliés. call ExitProcess
Sans pousser un dword sur la pile (Push 0, avant
d'appeler la fonction ExitProcess), l'assembler ne sera pas capable de comprendre cette erreur à votre
place. Vous vous en apecevrez plus tard quand votre programme plantera. Mais si vous employez :
invoke ExitProcess
Le linker vous informera que vous avez oublié de
pousser un dword sur la pile évitant ainsi l'erreur. Je vous recommande donc d'employer.
invoke
au lieu d'un simple 'Call'. La syntaxe d''Invoke' est la suivante :
INVOKE
expression [,arguments]
L'expression peut être le nom d'une fonction ou
bien un pointeur de fonction. Les paramètres sont séparés par des virgules.
La plupart des prototypes de fonction pour des
fonctions API sont tenus dans les fichiers nommés : INCLUDE. Si vous employez MASM32, ils seront dans le
dossier MASM32/INCLUDE. Les fichiers INCLUDE ont l'extension .inc et les prototypes de fonction pour un DLL
sont stockés dans le fichier .inc avec le même nom que le DLL. Par exemple, ExitProcess est exporté par
kernel32.lib donc le prototype de fonction pour ExitProcess est stocké dans kernel32.inc.
Maintenant revenons à ExitProcess, uExitCode est
la valeur du paramètre que vous voulez que votre programme renvoie à Windows après que celui-ci se termine.
Vous pouvez appeler ExitProcess comme cela :
invoke ExitProcess, 0
Si vous placez cette ligne immédiatement après l'
étiquette de commencement (ici on l'a appelée arbitrairement 'Start :', vous obtiendrez un programme win32
qui sortira immédiatement de Windows (pour revenir à ce que vous aviez à l'écran juste avant d'exécuter
votre programme), mais c'est néanmoins un programme valable.
.386
L'option casemap:none indique à MASM de ne pas faire de différence entre les expressions ExitProcess et exiteprocess par exemple.
Notez la nouvelle directive,
include. Cette directive est suivie par le nom du
fichier que vous voulez insérer à sa place. Dans cet exemple, quand MASM traite la ligne
include \masm32\include\windows.inc, Il ouvrira windows.inc
qui est dans le dossier \MASM32\include et fera en sorte que son contenu (celui de windows.inc) soit collé
dans votre programme win32. Il ne contient pas de prototype de fonction. windows.inc n'est en aucun cas
complet(c'est compréhensif car on peut toujours y rajouter de nouvelles choses). Ce fichier 'windows.inc'
qui regroupe tous mes fichiers.inc je l'appelle le fichier 'HUTCH'. Il sera constamment remis à jour.
Vérifiez donc HUTCH de temps en temps sur ma page d'accueil pour des mises à jour.
Par exemple, si vous faites :
Vous pouvez aussi créer des prototypes de fonction
pour vos propres fonctions. (HéHé ! ! !)
Partout dans mes exemples, j'emploierai windows.inc
que vous pouvez télécharger à http://win32asm.cjb.net
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke
ExitProcess,0
end
start
Dans notre exemple ci-dessus,
nous appelons une fonction exportée par kernel32.dll, donc nous avons besoin d'inclure le prototype de fonction
de kernel32.dll. Ce fichier est kernel32.inc. Si vous l'ouvrez avec un éditeur de texte, vous
verrez que c'est plein de prototypes de fonction pour kernel32.dll. Si vous n'incluez pas kernel32.inc,
vous pouvez toujours appeler ExitProcess, mais seulement avec la syntaxe d'appel simple (Push 0 puis Call
ExitProcess). Vous ne serez pas capables d'invoker
la function (ExitProcess).
*** Faisons le point : pour invoquer une fonction, vous devez mettre son prototype
de fonction (qui est le contenu d'un des fichier *.inc) quelque part dans le code source. Dans notre exemple, si vous n'incluez pas kernel32.inc,
vous pouvez définir le prototype de fonction pour ExitProcess n'importe où dans le code source au-dessus
de la commande invokée et ça marchera. Les fichiers invokés doivent ici vous faciliter le travail en vous
épargnant de taper les prototypes vous-même, donc employez-les chaque fois que vous le pouvez.
Maintenant nous rencontrons une nouvelle directive,
includelib.
includelib ne fonctionne pas comme include.
C'est seulement une façon de dire à l'assembleur quelles bibliothèques d'importation sont employées par vos
programmes. Quand l'assembleur voit une directive includelib, il met une commande de linker dans le
fichier d'objet pour que le linker sache avec quelles bibliothèques d'importation votre programme a
besoin de se lier. Vous n'êtes pas forcé d'employer includelib quoique. Vous pouvez spécifier les noms
des bibliothèques d'importation dans la ligne de commande du linker, mais croyez-moi, c'est ennuyeux et
la ligne de commande ne peut contenir que 128 caractères.
Sauvegardez maintenant cet exemple sous le nom de msgbox.asm. Vérifiez le chemin d'accès du fichier ml.exe, et assemblez msgbox.asm avec:
Alors allons y avec link:
/SUBSYSTEM:WINDOWS Ça informe 'Link' de quel sorte d'exécutable est votre programme.Link lit dans le fichier *.obj et pose les adresses des bibliothèques d'importation. Quand le processus est fini vous obtenez msgbox.exe.
/LIBPATH:<path to import library> Dit à Link où sont les bibliothèques d'importation. Si vous utilisez MASM32, ils seront dans le dossier MASM32\LIB.
Maintenant vous arrivez à msgbox.exe. Continuez, exécutez-le. Vous constaterez qu'il ne fait rien. Bien, nous n'y avons rien mis d'intéressant encore. Mais c'est un programme de Windows néanmoins. Et regardez sa taille! Dans mon PC, il prend 1,536 octets.
Maintenant, nous allons créer une boîte de message. Son prototype de fonction est :
MessageBox PROTO hwnd:DWORD, lpText:DWORD, lpCaption:DWORD, uType:DWORD
hwnd est le 'handle' ou la poignée de la fenêtre parente. Vous pouvez vous imaginer qu'un 'Handle' est un numéro qui représente la fenêtre à laquelle vous faîtes référence. Sa valeur n'a pas d'importante pour vous. Vous vous rappelez seulement qu'il représente la fenêtre. Quand vous voulez faire quelque chose avec la fenêtre, vous devez vous y référer par son 'handle'.Modifions msgbox.asm pour inclure une MessageBox.
lpText est un pointeur sur le texte que vous voulez montrer en tant que Contenu de la MessageBox (boîte de message). Cet pointeur représente réellement l'adresse de quelque chose. Cette adresse sera quelque chose du genre 00602154 et elle sinifie que le contenu qui sert à votre boite de message est en fait écrit à partir de la mémoire 00602154 (Cette adresse fait toujours parti des DATA) = Le Contenu De La MessageBox
lpCaption est l'pointeur de texte qui sert cette fois à accèder à l'adresse du texte qui est le titre de la message box. = Le Titre De La MessageBox
uType spécifie le type de message box utilisée. Par exemple quels sont les boutons quelle doit avoir, est-ce une MessageBox simple ou bien une avec un signe particulier comme le Point d'Interrogation...
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data
MsgBoxCaption
db "Iczelion Tutorial No.2",0
MsgBoxText
db "Win32 Assembly is Great!",0
.code
start:
invoke MessageBox,
NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess,
NULL
end start
Assemblez le fichier msgbox.asm et exécutez le nouveau prog msgbox.exe. Vous verrez une boîte de message montrant le texte"Win32 Assembly is Great!".
Regardons de nouveau le code source.
Nous avons définis deux données terminées par un
zéro dans la section .data. Rappelez-vous que chaque données ANSI dans Windows doit être terminée par le
caractère NULL (0 hexadécimal).
Nous avons aussi employé deux constantes, le NULL
et MB_OK. Ces constantes sont définies dans windows.inc. Donc vous pouvez faire référence à leurs
noms à la place de leurs valeurs. Cela améliore la lisibilité De votre code source.
L'expression ADDR
est employée pour passer l'adresse d'une données ou d'un label à la fonction.
C'est valable seulement dans le contexte de la directive 'invoke'. Vous ne
pouvez pas l'employer pour assigner l'adresse d'une donnée à un registre / variable, par exemple. Vous
devez employer offset
au lieu de 'ADDR' dans ce cas. Cependant, il y a quelques différences entre les deux :
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OKMASM indiquera une erreur. Si vous utilisez offset au lieu de addr dans ce petit bout de code, alors MASM l'assemblera avec succès.
......
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
lea eax, LocalVar
push eax
Puisque 'lea' peut déterminer l'adresse d'un
label pendant l'exécution, ça fonctionne tès bien.