Tutorial 14: Process

Nous allons voir ce qu'est un process et comment faire pour le créer et le terminer.

Downloadez l'exemple ici. Attention en fait cet exemple est dans un *.ZIP et il ne marchera pas si vous le laissez dedans, donc dézipez-le.

Préliminaire:

Qu'est ce qu'un 'process' ? Je cite cette définition de la référence Win32 API:
"Un 'process' est une application (un programme) qui sert à exécuter un autre programme. C'est un espace d'adresses virtuel privé, de code, de données et d'autres ressources du système d'exploitation, comme les fichiers, ou la synchronisation d'objets qui sont visibles pour le process.."
Comme vous pouvez le voir dans la définition ci-dessus, process "possède" plusieurs objets : l'espace d'adresse, le(s) module(s) (les autres programmes) à exécuter et tout ce que ces programmes créent ou ouvrent. Au minimum, process doit consister en l'exécution d'un seul module, avec un espace d'adresse privé et un lien. Chaque process doit avoir au moins un lien. Qu'est ce qu'un lien ? Un lien est en réalité une suite d'instructions d'exécution en attente. Lorsque Windows crée d'abord un process, il crée seulement un lien de process. D'habitude, ce lien fait en sorte d'exécuter la première instruction dans le module (dans le programme ciblé). Si le process à besoins de plusieurs autres liens par la suite, il peut les créer.
Quand Windows reçoit une commande pour créer un process, il alloue un espace d'adresses mémoire privé et ensuite il Mappe (recopie) le fichier exécutable dans cet espace. Après cela il crée le premier lien du process.
Sous Win32, vous pouvez aussi créer des process de vos propres programmes en appelant la fonction CreateProcess. CreateProcess a la syntaxe suivante :

CreateProcess proto lpApplicationName:DWORD,\
                                 lpCommandLine:DWORD,\
                                 lpProcessAttributes:DWORD,\

                                 lpThreadAttributes:DWORD,\
                                 bInheritHandles:DWORD,\
                                 dwCreationFlags:DWORD,\
                                 lpEnvironment:DWORD,\
                                 lpCurrentDirectory:DWORD,\
                                 lpStartupInfo:DWORD,\
                                 lpProcessInformation:DWORD

Ne soyez pas effrayez par le nombre de paramètres. Nous pouvons ignorer la plupart d'entre eux.

LpApplicationName est le nom du fichier exécutable avec ou sans nom de chemin que vous voulez exécuter. Si ce paramètre est nul, vous devez mettre le nom du fichier exécutable dans le paramètre lpCommandLine.
LpCommandLine sont les arguments de la ligne de commande du programme que vous voulez exécuter. Notez que si lpApplicationName est NUL, ce paramètre doit en plus contenir le nom du fichier exécutable. Comme cela : "notepad.exe readme.txt". (LpCommandLine c'est aussi l'argument qu'on écrit dans W32Dasm quand on fait 'DebugàLoad Process [argument]àLoad' , mais c'est très rare qu'on écrive quelque chose en tant qu'argument!)
LpProcessAttributes et lpthreadAttributes Spécifient les attributs de sécurité pour process et le lien primaire. S'ils sont NULS, les attributs de sécurité par défaut sont employés.
BInheritHandles est un flag qui indique si vous voulez qu'un autre process hérite du contrôle de toutes les manipulations ouvertes par votre process.
DwCreationFlags représente Plusieurs flags qui déterminent le comportement du process que vous voulez créé, comme : voulez-vous qu'un process soit créés, mais suspendu immédiatement pour que vous puissiez l'examiner ou le modifier avant qu'il ne reparte ? Vous pouvez aussi spécifier la classe de priorité du lien(s) dans un nouveau process. Cette classe de priorité est employée pour déterminer la priorité de planification des liens du process. Normalement nous employons le flag NORMAL_PRIORITY_CLASS.
LpEnvironment est le pointer du bloc d'environnement qui contient plusieurs instructions pour un nouveau process. Si ce paramètre est NUL, le nouveau process hérite de l'environnement du bloc process précédent.
LpCurrentDirectory est le pointer qui est sur la chaîne de caractères lequel indique l'actuel 'Lecteur ' (Drive) ainsi que le répertoire (Directory) du Child Process. Il est NULL si vous voulez que le 'Child Process' succède au 'Parent Process'.
LpStartupInfo pointe sur une structure STARTUPINFO laquelle dit comment doit apparaître la fenêtre principale du nouveau process. La structure STARTUPINFO contient beaucoup de membres servants à définir l'affichage de la fenêtre principale du 'Child Process'. Si vous ne voulez rien de spécial, vous pouvez remplir la structure STARTUPINFO avec les valeurs du 'Parent Process' en appelant la fonction GetStartupInfo.
LpProcessInformation pointe sur la structure PROCESS_INFORMATION laquelle reçoit l'information d'identification du nouveau process. La structure de PROCESS_INFORMATION est la suivante :

PROCESS_INFORMATION STRUCT
    hProcess          HANDLE ?             ; Handle du 'Child Process'
    hThread            HANDLE ?             ; Handle du premier lien du 'Child Process'
    dwProcessId     DWORD ?             ; ID du 'Child Process'
    dwThreadId      DWORD ?            ; ID du premier lien du 'Child Process'
PROCESS_INFORMATION ENDS
L'handle du process et l'ID du process sont deux choses différentes. L'ID est un identificateur unique du process dans le système. L'handle est une valeur renvoyée par Windows pour qu'on puisse l'utiliser avec d'autres process rapportés de fonctions API. L'handle du process ne peut pas être employé pour identifier un process puisque il n'est pas unique.

Après l'appel à CreateProcess, un nouveau process est créé et on retourne (au prog principa) immédiatement après cet appel à CreateProcess. Vous pouvez vérifier si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess qui a la syntaxe suivante :

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si cet appel est couronné de succès, lpExitCode contient le statut de terminaison du process en question. Si la valeur dans lpExitCode est égale à STILL_ACTIVE, alors ce process est toujours en cours.

Vous pouvez force le process à se terminer en appelant la fonction TerminateProcess. Voici sa syntaxe :

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Vous pouvez spécifier le code de sortie désiré pour votre process, n'importe quelle valeur que vous souhaitez. TerminateProcess n'est pas une façon très propre de terminaison d'un process puisque chaque dll rattaché à ce process ne sera pas informé que le process a été fermé.
 

Exemple:

L'exemple suivant crée un nouveau process lorsque l'utilisateur choisit le sous-menu (le menu item) "Create Process". Il essayera d'exécuter "msgbox.exe". Si l'utilisateur souhaite terminer le nouveau process, il peut choisir le menu item "Terminate Process". Le programme vérifiera d'abord si le nouveau process est déjà fermé, si ce n'est pas le cas, le programme appellera la fonction TerminateProcess pour détruire le nouveau process.

.386
.model flat,stdcall
option casemap:none
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

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ?                    ; Contient le statut 'exitcode du process' servant à l'appel de GetExitCodeProcess.

.code
start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax
        invoke GetCommandLine
        mov CommandLine,eax
        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
        invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    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  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    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
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,300,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke GetMenu,hwnd
    mov  hMenu,eax
    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL startInfo:STARTUPINFO
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_CREATE_PROCESS
                .if processInfo.hProcess!=0
                    invoke CloseHandle,processInfo.hProcess
                    mov processInfo.hProcess,0
                .endif
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo
                invoke CloseHandle,processInfo.hThread
            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif
                invoke CloseHandle,processInfo.hProcess
                mov processInfo.hProcess,0
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp
end start

Analyse:

Ce programme crée la fenêtre principale et renvoie l'handle de son menu pour une future utilisation. Il attend alors que l'utilisateur choisisse une commande du menu. Lorsque l'utilisateur choisit le sous-menu "Process" du menu principal (de la barre de menu), nous déployons le menu grâce au message WM_INITMENUPOPUP pour modifier les sous-menus à l'intérieur du 'menu popup' (menu principal) avant qu'il ne soit affichés.

    .ELSEIF uMsg==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif

Pourquoi utilisons-nous ce message dans notre process? Parce que nous souhaitons préparer les sous-menus dans le menu popup avant que l'utilisateur ne puisse les voir. Dans notre exemple, lorsque le process n'est pas encore lancé, nous activons le sous-menu "Start Process" et désactivons "Terminate Process" (il est grisé). On fait le contraire quand le nouveau process est déjà actif.
Nous vérifions d'abord si le nouveau process dirige (a encore la main sur MsgBox.exe) toujours en appelant la fonction GetExitCodeProcess avec l'handle du process qui a été renvoyé par la fonction CreateProcess. Si les retours de GetExitCodeProcess sont FALSE(FAUX), alos ça signifie que notre process n'est pas encore commencé, donc nous grisons (désactivons) le sous-menu "Terminate Process". Si les retours de GetExitCodeProcess sont TRUE(VRAIS), nous savons que notre nouveau process a été lancé, mais nous devons vérifier plus loin s'il est toujours en court. Donc nous comparons la valeur dans ExitCode à la valeur STILL_ACTIVE. S'ils sont égaux,c'est que notre process en encore en court : nous devons alors désactiver la sous-menu "Start Process" puisque nous ne souhaitons pas lancer plusieurs processus en même temps.

            .if ax==IDM_CREATE_PROCESS
                .if processInfo.hProcess!=0
                    invoke CloseHandle,processInfo.hProcess
                    mov processInfo.hProcess,0
                .endif
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo
                invoke CloseHandle,processInfo.hThread
 
Quand l'utilisateur choisit le menu item "Start Process", nous vérifions d'abord si le membre hProcess de la structure PROCESS_INFORMATION est déjà fermé. Si c'est la première fois, la valeur d'hProcess sera toujours zéro puisque nous définissons la structure de PROCESS_INFORMATION dans la section '.data'. Si la valeur du membre hProcess n'est pas 0, ça signifie que le Child Process est sorti mais nous n'avons pas fermé son handle process encore. Donc c'est maintenant qu'il le faire.
Nous appelons la fonction GetStartupInfo pour récupérer la valeur startupinfo pour pouvoir ensuite la passer à la fonction CreateProcess. Après ça nous appelons la fonction CreateProcess pour commencer nouveau process. Notez que je n'ai pas vérifié la valeur de retour de CreateProcess parce que ça aurait encore compliqué cet exemple. En réalité, vous devriez vérifier cette valeur de retour (de CreateProcess). Immédiatement après CreateProcess, nous fermons l'handle du premier lien, lequel est renvoyé par la structure processInfo. La fermeture du handle ne veut pas dire que est le lien terminé, mais seulement que nous ne voulons pas employer cet handle pour nous référer au lien de notre programme. Si nous ne le fermons pas, il provoquera une fuite des ressources.

            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif
                invoke CloseHandle,processInfo.hProcess
                mov processInfo.hProcess,0

Quand l'utilisateur choisit le sous-menu (le menu item) "Terminate Process", nous vérifions si le nouveau process est toujours actif en appelant la fonction GetExitCodeProcess. Si il est toujours actif, nous appelons la fonction TerminateProcess pour détruire notre process. Aussi nous fermons l'handle du Child Process puisque désormais nous n'en avons plus besoin.


[Iczelion's Win32 Assembly HomePage]


Traduit par Morgatte