WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA
; Initialise les DATA (ceux qui font déjà parti du programme)
ClassName db "SimpleWinClass",0
; le nom de Class de votre fenêtre
AppName db "Our First Window",0
; Le nom de votre fenêtre (c'est son titre)
.DATA?
; Initialise les DATA (ceux qui ne font pas encores parti du programme. EX: les futures saisies au clavier).
hInstance HINSTANCE ?
; l'Instance handle de votre programme
CommandLine LPSTR ?
.CODE
; C'est ici que commence votre code.
start:
invoke GetModuleHandle, NULL
; Récupère l'Instance handle de votre programme.
; Sous Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine
; Obtient la command line. vous n'avez besoin d'appeler cette fonction QUE SI
; votre programme n'a pas encore de command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine,
SW_SHOWDEFAULT
; Appelle la fonction principale
invoke ExitProcess, eax
; Sort de votre programme. Le code de sortie est retourné dans eax.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
; Créez des variables locales sur la pile
LOCAL msg:MSG
LOCAL hwnd:HWND
mov
wc.cbSize,SIZEOF WNDCLASSEX
; Place les valeurs dans les membres de wc
mov
wc.style, CS_HREDRAW or CS_VREDRAW
mov
wc.lpfnWndProc, OFFSET WndProc
mov
wc.cbClsExtra,NULL
mov
wc.cbWndExtra,NULL
push
hInstance
pop
wc.hInstance
mov
wc.hbrBackground,COLOR_WINDOW+1
mov
wc.lpszMenuName,NULL
mov
wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov
wc.hIcon,eax
mov
wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov
wc.hCursor,eax
invoke RegisterClassEx,
addr wc
; register our window class
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov
hwnd,eax
invoke ShowWindow,
hwnd,CmdShow
; Affiche notre fenêtre sur le bureau
invoke UpdateWindow,
hwnd
; Régénérez le client area
.WHILE TRUE
; Création d'une boucle
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov
eax,msg.wParam
; Retoune le code de sortie dans eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT,
wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
; si l'utilisateur ferme notre fenêtre...alors
invoke PostQuitMessage,NULL
; on quitte notre application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
La susdite ligne est la déclaration de la fonction WinMain. Notez l'ensemble des paramètres qui suivent la directive PROC. Ce sont les paramètres que WinMain reçoit de la fonction qui l'appelle. Vous pouvez utiliser les noms de ces paramètres au lieu de vous servir de la pile pour pousser la valeurs de vos paramètres.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
La directive LOCAL alloue de la mémoire sur la pile pour des variables locales employées par la fonction. La totalité des directives LOCALES doivent être placées juste au-dessous de la directive PROC. La directive LOCAL est immédiatement suivie par " le nom de la variable locale " ; " le type de variable ". Et donc 'LOCAL wc:WNDCLASSEX' dit à MASM de réserver de la mémoire sur la pile. Nous pourrons ensuite nous référer à wc dans notre code sans aucune difficulté engendrée par la manipulation de la pile. C'est vraiment une aubaine, je pense. le revers est le fait que les variables locales ne peuvent pas être employées à l'extérieur de la fonction, elles sont créés et seront automatiquement détruites quand la fonction retourne à l'appel. Un autre inconvénient est que vous ne pouvez pas initialiser des variables locales automatiquement parce qu'elles sont uniquement allouées dans la mémoire de la pile au moment seulement où la fonction est entrée. Vous devez les assigner manuellement avec les valeurs désirées après les directives 'LOCAL'.
mov
wc.cbSize,SIZEOF WNDCLASSEX
mov
wc.style, CS_HREDRAW or CS_VREDRAW
mov
wc.lpfnWndProc, OFFSET WndProc
mov
wc.cbClsExtra,NULL
mov
wc.cbWndExtra,NULL
push
hInstance
pop
wc.hInstance
mov
wc.hbrBackground,COLOR_WINDOW+1
mov
wc.lpszMenuName,NULL
mov
wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov
wc.hIcon,eax
mov
wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov
wc.hCursor,eax
invoke RegisterClassEx,
addr wc
Les lignes ci-dessus sont vraiment simples par leur concept. Il s'agit juste de plusieurs lignes d'instruction regroupées. Le concept qui se cache derrière toutes ces lignes c'est d'initialiser la Windows class. Une 'Windows Class' n'est rien de plus que la première ébauche d'une fenêtre. On définit donc plusieurs caractéristiques importantes d'une fenêtre comme son icône, son curseur, la fonction qui la contrôle, sa couleur etc. Vous définisez la fenêtre grâce à sa 'Windows Class'. C'est en quelque sorte le but de ce concept. Si vous voulez créer plusieurs fenêtres avec les mêmes caractéristiques, il serait temps de penser à stocker toutes ces caractéristiques dans un unique endroit (donc dans une procédure par exemple) et de s'y référer quand c'est nécessaire. Cet arrangement économisera des tonnes de la mémoire en évitant la duplication d'informations. Windows doit être très efficaces dans l'utilisation des ressources mémoires, qui restent encore rares.
Faisons le point : Dès que vous définissez votre propre fenêtre, vous devez définir ses caractéristiques grâce à l'instruction WNDCLASS ou WNDCLASSEX et ensuite appeler RegisterClass ou RegisterClassEx avant que vous ne soyez capables de créer votre fenêtre. Vous devrez donner une 'Windows Class' pour chaque type de fenêtre que vous voulez créer.
Windows possède plusieurs 'Windows Class' déjà prédéterminées, comme les boutons et la boîte d'édition (pour entrer un texte). Pour ces fenêtres (ou ces commandes), vous n'avez pas besoin de déclarer une 'Windows Class', appelez juste CreateWindowEx avec le nom de classe prédéterminée.
L'élément le plus important de WNDCLASSEX est lpfnWndProc. Lpfn attend un 'Pointeur Long' pour fonctionner. Sous Win32, il n'y a pas de Pointeur(proche) ou de Pointeur(lointain), mais un simple Pointeur grâce au nouveau modèle de mémoire Flat(PLAT). Mais c'est encore un reste des vieux jours de Win16. Chaque 'Windows Class' doit être associée à une procédure appelée par Windows. La procédure qui sert à créer vos fenêtres sera responsable du traitement de chaque message associée la à Windows Class. Les fenêtres enverront des messages vers la procédure qui les gère pour lui notifier quels événements importants sont utilisés par ces fenêtres, comme l'utilisation du clavier ou de la souris. Il ne reste plus qu'à la procédure (qui s'occupe de la gestion des fenêtres) à répondre intelligemment à chaque message qu'il reçoit envoyé par une fenêtre. La plupart du temps vous écrirez des enchaînements d'événements dans cette procédure.
Ci-dessous, Je vous détaille la construction de la fonction WNDCLASSEX:
WNDCLASSEX STRUCT DWORD
cbSize
DWORD ?
style
DWORD ?
lpfnWndProc
DWORD ?
cbClsExtra
DWORD ?
cbWndExtra
DWORD ?
hInstance
DWORD ?
hIcon
DWORD ?
hCursor
DWORD ?
hbrBackground
DWORD ?
lpszMenuName
DWORD ?
lpszClassName
DWORD ?
hIconSm
DWORD ?
WNDCLASSEX ENDS
cbSize: La taille de la structure WNDCLASSEX en octets. Nous pouvons employer l'opérateur SIZEOF pour obtenir (initialiser) cette valeur.
style: Le style (ou le type) de fenêtre créée pour cette 'Class'. Vous pouvez combiner plusieurs types ensembles en employant l'opérateur "or".
lpfnWndProc: L'adresse de la procédure responsable de la gestion des fenêtres créées dans cette 'Class'.
cbClsExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la structure(ses éléments) de la Windows Class choisie. Le système d'exploitation initialise les octets à zéro. Vous pouvez stocker des données spécifiques à la Windows Class ici.
cbWndExtra: Indique le nombre d'octets supplémentaires à allouer en fonction de la 'Windows Instance'. Le système d'exploitation initialise les octets à zéro.
hInstance: l'Instance Handle de ce module (de ce WNDCLASSEX).
hIcon: Handle de l'icône. Obtenez-le après l'appel de 'LoadIcon' (ou en retour de l'appel de 'LoadIcon').
hCursor: Handle du curseur. Obtenez-le près l'appel de 'LoadCursor'.
hbrBackground: Couleur du fond de toutes les fenêtres créées dans cette 'Class'.
lpszMenuName: L'Handle du menu des fenêtres créées par cette 'Class'. C'est une valeur par défaut.
lpszClassName: Le nom de cette 'Class'.
hIconSm: L'Handle d'une petite icône associée à cette 'Class' de fenêtres. Si cette valeur est NULLE, le système recherche dans la 'ressource d'icône' indiquée par le membre hIcon, une icône de la taille appropriée sera employée en tant que petite icône.
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
Après l'enregistrement de la Window Class, nous pouvons appeler CreateWindowEx pour créer notre fenêtre basée sur la Window Class ainsi définie. Remarquez qu'il y a 12 paramètres à cette fonction, beuurr ! ! !
CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD
,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD
On va voir la description détaillée de chaque paramètre :
dwExStyle: Styles de fenêtre supplémentaires. C'est le nouveau paramètre qui est ajouté à la vieille fonction 'CreateWindow'. Vous pouvez mettre des nouveaux styles de fenêtre pour Windows 95 & NT ici. En temps normal, vous pouvez spécifier votre style de fenêtre ordinaire dans dwStyle, mais si vous voulez certains styles spéciaux pour une fenêtre 'tip top', vous devez les spécifier ici. Vous devez employer un NULL(un 0) si vous ne voulez pas de styles de fenêtre supplémentaires.
lpClassName: (obligatoire).L'adresse de la suite de caractères ASCII contenant le nom de la Window Class que vous voulez employer en tant que gabarit pour cette fenêtre. La Class peut être votre propre Class que vous venez de créer ou bien une Window Class prédéterminée. Comme exposé ci-dessus, chaque fenêtre que vous avez créée doit être basée sur une Window Class. (Normal ! , puisque la Window Class sert justement à définir les propriétés de la fenêtre à créer)
lpWindowName: Adresse de la suite de caractères ASCII contenant le nom de la fenêtre. Sera en fait la titre de la fenêtre (en haut à gauche). Si ce paramètre est NUL, la barre de titre de la fenêtre sera vide.
dwStyle: C'est le style de la fenêtre. Vous pouvez spécifier ici l'apparition de la fenêtre. Mettre une valeur NULLE pour ce paramètre dwSTYLE est possible mais la fenêtre n'aurait aucune boîte de menu, n'aurait ni les boutons de réduction-agrandissement ni aucun autre bouton. La fenêtre n'aurait pas beaucoup d'intérêt. Vous devrez même appuyer sur Alt+F4 pour la refermer. Le style de fenêtre le plus commun est le paramètre 'WS_OVERLAPPEDWINDOW' (et non pas le paramètre NUL). Ainsi vous pouvez combiner plusieurs styles de fenêtre grâce à l'opérateur "ou" pour faire apparaître la fenêtre que vous désirez. Le style WS_OVERLAPPEDWINDOW est en réalité une combinaison de styles des fenêtres les plus communes.
X,Y: C'est la coordonnée du coin supérieur gauche de la fenêtre. Normalement on prend le paramètre CW_USEDEFAULT comme valeur, comme ça Windows décidera pour vous où placer la fenêtre sur le bureau.
nWidth, nHeight: Ce sont les largeur et hauteur de la fenêtre en pixels. Vous pouvez aussi employer CW_USEDEFAULT pour laisser Windows choisir la largeur appropriée et la hauteur à votre place.
hWndParent: C'est l'Handle de la fenêtre parente de votre fenêtre (si celle-ci existe). Ce paramètre indique à Windows si cette fenêtre est un enfant (Window Child) d'une autre fenêtre et, si elle l'est, qui est la fenêtre parente. Notez que ce n'est pas la même relation Parent-Enfant que les 'multiple document interface' ou (MDI). Les 'Window Child' n'ont pas forcément les mêmes caractéristiques que leur 'Window Parent'. Ces caractéristiques sont uniquement spécifiques à l'utilisation interne de chaque fenêtre. Si la fenêtre parente est détruite, toutes ses fenêtres enfant seront détruites automatiquement. C'est vraiment très simple. Dans notre exemple, il y a seulement une fenêtre, donc ce paramètre reste NULL.
hMenu: C'est l'Handle du menu de la fenêtre. Il est NULLsi la Class Menu est utilisée. Revenez en arrière à un des membres de l'instruction WNDCLASSEX, soit 'lpszMenuName'. lpszMenuName indique le menu par *default* pour la 'Window Class'. Chaque fenêtre de cette Window Class aura le même menu par défaut. À moins que vous ne spécifiiez un menu *overriding* pour une fenêtre spécifique via son paramètre hMenu. hMenu est en réalité un paramètre à double usage. Si la fenêtre que vous souhaitez créer utilise un 'Type de Fenêtre' (Style) prédéterminé (c'est-à-dire un contrôle), un tel contrôle ne peut pas posséder de menu. hMenu est employé comme ID de ce contrôle au lieu de cela. Windows peut décider si hMenu est vraiment un Handle de menu ou un contrôle ID en regardant le paramètre lpClassName. Si c'est le nom d'une Window Class prédéterminée, hMenu est un contrôle ID. Si ce n'est pas, ce sera alors l' Handle du menu de votre fenêtre.
hInstance: l'Instance Handle pour le module (la partie) du programme créant la fenêtre.
lpParam: Le pointeur facultatif pour des données a passer à la fenêtre. Il est employé par la fenêtre MDI pour passer les données CLIENTCREATESTRUCT. Normalement, cette valeur est mise au NUL, signifiant que l'on ne passe aucunes données via CreateWindow (). La fenêtre peut prendre la valeur de ce paramètre après l'appel à la fonction GetWindowLong.
mov hwnd,eax
invoke ShowWindow,
hwnd,CmdShow
invoke UpdateWindow,
hwnd
En retour couronné de succès de CreateWindowEx, l'Handle de la fenêtre est placée dans eax. Nous devons garder cette valeur pour une utilisation future. La fenêtre que nous venons de créer n'est pas automatiquement affichée. Vous devez appeler 'ShowWindow' avec l'Handle de fenêtre et la *résolution* désirée de la fenêtre pour la faire apparaître à l'écran. Ensuite vous pourrez appeler 'UpdateWindow' pour ordonner à votre fenêtre de repeindre son secteur de client (d'afficher son contenu). Cette fonction est utile à chaque fois que vous voulez mettre à jour le contenu du secteur de client. Vous pouvez omettre cet appel quoique.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
Cette fois-ci, notre fenêtre est sur l'écran. Mais elle ne peut pas recevoir d'informations extérieur (comme une saisie de texte ou bien la détection de la pression d'un bouton). Donc nous devons l'informer de ce qui se passe. Nous faisons ça avec une boucle de message. Il y a seulement une boucle de message pour chaque module. Cette boucle de message vérifie continuellement les messages de Windows grâce à un 'Call GetMessage'. GetMessage passe une donnée à une structure de MESSAGE de Windows. Cette structure de MESSAGE sera remplie de l'information du message que Windows veut envoyer à une fenêtre dans le module. La fonction GetMessage ne retournera pas (ne reviendra pas à votre programme mais au contraire restera dans Kernel ou User) tant qu'un message (ex : appui sur un bouton) ne sera pas transmis pour une fenêtre dans le module. Pendant ce temps-là, Windows peut donner le contrôle à d'autres programmes. C'est en quelque sorte le fonctionnement en Multigestion de la plate-forme Win16. GetMessage renverra une erreur si le Message WM_QUIT est reçu dans la boucle de message, et ainsi nous termineront la boucle et irons vers la sortie du programme.
TranslateMessage est une fonction utile qui saisie les entrées de clavier (à la volée) et produit un nouveau Message WM_CHAR qui est placé sur la file d'attente des messages. Le message avec WM_CHAR contient la valeur ASCII pour la touche pressée, c'est bien plus facile de procéder ainsi. Vous pouvez omettre cet appel si votre programme ne traite pas de frappes.
'DispatchMessage' redirige les données d'un message( ex : envoi : on vient d'appuyer sur le bouton) à la procédure responsable de la fenêtre spécifique.
mov eax,msg.wParam
ret
WinMain endp
Si la boucle de message se termine, le code de sortie est stocké dans le membre wParam de la structure de MESSAGE. Vous pouvez stocker ce code de sortie dans eax pour le rendre à Windows. Actuellement, Windows ne se sert pas de la valeur de retour, mais c'est mieux de respecter les règles.
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
C'est notre procédure Win. Vous ne devez pas la nommer WndProc. Le premier paramètre, hWnd, est l' Handle de la fenêtre pour laquelle le message est destiné. uMsg est le message. Notez qu'uMsg n'est pas une structure de MESSAGE. C'est juste un numéro(nombre). Windows définit des centaines de messages, dont la plupart ne seront pas intéressants pour vos programmes. Windows enverra un message approprié à une fenêtre seulement si quelque chose d'intéressant arrive à cette fenêtre. La procédure qui traite vos fenêtres reçoit le message et y réagit intelligemment. wParam et lParam sont juste des paramètres supplémentaires pour l'utilisation de certains messages. Certains messages envoient des données d'accompagnement en plus du message lui-même. On passe ces données à la procédure de fenêtre au moyen de lParam et wParam.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Vient ici la partie cruciale. C'est l'endroit où la plupart de l'intelligence de votre programme réside. Les codes qui répondent à chaque message de Windows sont dans la procédure de fenêtre (la procédure qui s'occupe du contrôle de votre fenêtre). Votre code doit vérifier le message de Windows pour voir si c'est un message intéressant. S'il l'est, faites tout ce que vous voulez faire en réponse à ce message et retourner ensuite la valeur zéro dans eax. S'il ne l'est pas, vous DEVEZ appeler l'API 'DefWindowProc', pour lui passer tous les paramètres que vous y avez reçu pour les traiter par défaut .'DefWindowProc' est une fonction API qui traite les messages qui ne sont pas intéressant pour votre programme.
Le seul message que vous pouvez et DEVEZ envoyer vers votre procédure qui traite les messages est WM_DESTROY. Ce message est envoyé à votre procédure de fenêtre pour la refermée. Au moment où votre procédure de fenêtre reçoit ce message, votre fenêtre est déjà enlevée de l'écran. C'est juste un renseignement comme quoi votre fenêtre a bien été détruite, vous devez vous préparer à retourner à Windows. Si vous utilisez cela, vous n'avez d'autres choix que de quitter. Si vous voulez avoir une chance d'arrêter la fermeture de votre fenêtre, vous devez utiliser le message WM_CLOSE. Maintenant revenons à WM_DESTROY, après cette fermeture radicale, vous devez appeler PostQuitMessage qui postera WM_QUIT en retour à votre module(traitement des message). WM_QUIT fera le retour de GetMessage avec la valeur zéro dans eax, qui à son tour, terminera la boucle de message et quittera Windows. Vous pouvez envoyer le message WM_DESTROY à votre propre procédure de fenêtre en appelant la fonction DestroyWindow.