Tutorial 16: Event Object

Nous allons apprendre ce qu'est un event object et comment l'employer dans un programme multi-liens.

Downloadez l'exemple ici.

Théorie:

Dans le Tutorial précédent, on a vu comment les liens communiquent avec un message de fenêtre sur mesure (custum). J'ai omis deux autres méthodes : les variables globales et les 'events objets'. On va utiliser les deux dans ce Tutorial.
Un 'event objet', c'est comme un commutateur : il a seulement deux états possibles: on/off. Quand un 'event objet' est allumé, il est dans l'état "signalé" (c'est à dire qu'on l'utilise). Quand il est éteint, il est dans l'état "nonsignalé"(on ne l'utilise pas, on n'y fait référence nulle part). On créer un 'event objet' et on écrit un petit bout de code au niveau des liens appropriés pour observer l'état de l' 'event objet'. Si l' 'event objet' est dans l'état nonsignalé, les liens qui se servent des 'event objet' sont inactifs. C'est bien pratique car lorsque les liens sont en état d'attente, ils consomment peu de temps CPU.
Vous créez 'event object' en appelant la fonction CreateEvent qui a la syntaxe suivante :

CreateEvent proto lpEventAttributes:DWORD,\
                              bManualReset:DWORD,\
                              bInitialState:DWORD,\
                              lpName:DWORD

lpEventAttribute--> Si vous donnez la valeur NULL, l' 'event object' est créé d'une façon par défaut.
bManualReset--> Si vous souhaitez que Windows désactive automatiquement l'event object (off) après l'appel WaitForSingleObject, vous devez taper 'FALSE' pour ce paramètre. Sinon vous devez le désactiver manuellement avec l'appel ResetEvent.
bInitialState--> Au moment où il est créé, si vous souhaitez que l'event object soit dans l'état signalé, mettez TRUE pour ce paramètre, sinon l' event object sera créé avec l'état nonsignalé.
lpName --> C'est le pointer qui repère une chaîne de caractère (ASCIIZ=du texte), laquelle est le nom de l'event object. Ce nom sera employé quand vous voudrez appeler OpenEvent.(pointe sur le nom de l'Event Object)

Si l'appel s'est déroulé avec succès, il renvoie l'handle de l' event object qui vient d'être créé, autrement il renvoie le NULL.
Vous pouvez modifier l'état de l'event object avec deux des appels API : SetEvent et ResetEvent. La fonction SetEvent met l'event object à l'état signalé. ResetEvent fait le contraire.
Après que l'event object soit créé, il vous faut insérez l'appel 'WaitForSingleObject' dans le lien qui dépend de l'état de l'event object. WaitForSingleObject a la syntaxe suivante :

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> L'handle d'un des l'objets de synchronisation. L' event object est un type d'objet de synchronisation.
dwTimeout --> C'est à vous de fixer (en millisecondes) le temps maximal que cette fonction peut mettre pour que l'objet passe à l'état signalé. Si le temps indiqué est passé et que l'event object est toujours à l'état nonsignalé, alors WaitForSingleObject rend la main à l'interlocuteur. Si vous voulez attendre indéfiniment, vous devez mettre la valeur 'INFINITE' pour ce paramètre.(c'est aussi -1 en décimal ou FFFFFFFF en hexadécimal).

Exemple:

L'exemple ci-dessous montre une fenêtre attendant que l'utilisateur choisisse une commande du menu. Si l'utilisateur choisit ''run thread'' (lien en route), le lien commence le calcul sauvage (un calcul immense qui occupe le système). Quand il est fini, une boîte de message apparaît, informant l'utilisateur que le travail est terminé. Mais pendant que le lien était en action, l'utilisateur pouvait toujours cliquer sur "stop thread" pour arrêter ce lien.

.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_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h

.data
ClassName db "Win32ASMEventClass",0
AppName  db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?

.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
    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
    .IF uMsg==WM_CREATE
        invoke CreateEvent,NULL,FALSE,FALSE,NULL
        mov  hEventStart,eax
        mov  eax,OFFSET ThreadProc
        invoke CreateThread,NULL,NULL,eax,\
                             NULL,0,\
                             ADDR ThreadID
        invoke CloseHandle,eax
    .ELSEIF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_START_THREAD
                invoke SetEvent,hEventStart
                invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
            .elseif ax==IDM_STOP_THREAD
                mov  EventStop,TRUE
                invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .ELSEIF uMsg==WM_FINISH
        invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
.ENDIF
    xor    eax,eax
    ret
WndProc endp

ThreadProc PROC USES ecx Param:DWORD
        invoke WaitForSingleObject,hEventStart,INFINITE
        mov  ecx,600000000
        .WHILE ecx!=0
                .if EventStop!=TRUE
                        add  eax,eax
                        dec  ecx
                .else
                        invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
                        mov  EventStop,FALSE
                        jmp ThreadProc
                .endif
        .ENDW
        invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
        invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
        invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
        jmp   ThreadProc
        ret
ThreadProc ENDP
end start

Analyse:

Dans cet exemple, je vous montre une autre technique de lien.

    .IF uMsg==WM_CREATE
        invoke CreateEvent,NULL,FALSE,FALSE,NULL
        mov  hEventStart,eax
        mov  eax,OFFSET ThreadProc
        invoke CreateThread,NULL,NULL,eax,\
                             NULL,0,\
                             ADDR ThreadID
        invoke CloseHandle,eax

Vous pouvez voir que je crée l'event object ainsi que le lien, pendant le traitement du message WM_CREATE. Je crée l'event object à l'état nonsignalé et avec remis automatique. Après que l'event object soit créé, je crée le lien. Cependant le lien ne se met pas immédiatement au travail parce qu'il attend que l'event object soit mis à l'état 'signalé'. Ci-dessous voici comment le mettre à signalé:

ThreadProc PROC USES ecx Param:DWORD
        invoke WaitForSingleObject,hEventStart,INFINITE
        mov  ecx,600000000

La première ligne de la procédure de lien est l'appel WaitForSingleObject. Ici on attend infiniment que l'event object passe à l'état signalé avant qu'il ne retourne. Ça signifie que même après que le lien soit créé, nous le mettons dans un état d'inactivité.
Quand l'utilisateur choisit la commande du menu "run thread", nous mettons l'event object dans l'état signalé, comme indiqué ci-dessous :

            .if ax==IDM_START_THREAD
                invoke SetEvent,hEventStart

L'appel SetEvent met l'event object à l'état signalé, ce qui fait qu'à son tour l'appel WaitForSingleObject nous dirige vers la procédure du lien et le lien commence à travailler. Quand l'utilisateur choisit la commande "stop thread", nous mettons la valeur de la variable globale "EventStop" à TRUE.

                .if EventStop==FALSE
                        add  eax,eax
                        dec  ecx
                .else
                        invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
                        mov  EventStop,FALSE
                        jmp ThreadProc
                .endif

Ceci stop le lien et saute de nouveau à l'appel WaitForSingleObject. Remarquez que nous n'avons pas besoin de remettre manuellement l'event object dans l'état nonsignalé puisque nous avons mis le paramètre bManualReset à FALSE (dans l'appel de CreateEvent).


[Iczelion's Win32 Assembly HomePage]


Traduit par Morgatte