Tutorial 16: Event Object

We will learn what an event object is and how to use it in a multithreaded program.
 

Theory:

From the previous tutorial, I demonstrated how threads communicate with a custom window message. I left out two other methods: global variable and event object. We will use both of them in this tutorial.
An event object is like a switch: it has only two states: on or off. When an event object is turned on, it's in the "signalled" state. When it is turned off, it's in the "nonsignalled" state. You create an event object and put in a code snippet in the relevant threads to watch for the state of the event object. If the event object is in the nonsignalled state, the threads that wait for it will be asleep.When the threads are in wait state, they consume little CPU time.
You create an event object by calling CreateEvent function which has the following syntax:

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

lpEventAttribute--> If you specify NULL value, the event object is created with default security descriptor.
bManualReset--> If you want Windows to automatically reset the event object to nonsignalled state after WaitForSingleObject call, you must specify FALSE as this parameter. Else you must manually reset the event object with the call to ResetEvent.
bInitialState--> If you want the event object to be created in the signalled state, specify TRUE as this parameter else the event object will be created in the nonsignalled state.
lpName--> Pointer to an ASCIIZ string that is the name of the event object. This name is used when you want to call OpenEvent.

If the call is successful, it returns the handle to the newly created event object else it returns NULL.
You can modify the state of an event object with two API calls: SetEvent and ResetEvent. SetEvent function sets the event object into signalled state. ResetEvent does the reverse.
When the event object is created, you must put the call to WaitForSingleObject in the thread that wants to watch for the state of the event object. WaitForSingleObject has the following syntax:

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

hObject --> A handle to one of the synchronization object. Event object is a type of synchronization object.
dwTimeout --> specify the time in milliseconds that this function will wait for the object to be in signalled state. If the specified time has passed and the event object is still in nonsignalled state, WaitForSingleObject returns the the caller. If you want to wait for the object indefinitely, you must specify the value INFINITE as this parameter.

Example:

The example below displays a window waiting for the user to select a command from the menu. If the user selects "run thread", the thread starts the savage calculation. When it's finished, a message box appears informing the user that the job is done. During the time that the thread is running, the user can select "stop thread" to stop the thread.

___________________________________________________________________________________
; Equates:
 

[M00_Menu  2000                  M00_Thread  2001                M00_Run_Thread  2002
 M00_Stop_Thread  2003           M00_Exit  2004]
 

[WM_FINISH 0500]     ;  WM_USER + 0100
_____________________________________________________________________________________
; Data:

[WindowHandle: 0  ExitCode: 0  MenuHandle: 0  ThreadID: 0  EventStartHandle: 0
 ThreadHandle: 0  EventStop: B§ 0
 ClassName: 'Win32ASMThreadClass' 0   AppName: 'Win32 ASM Event Example' 0
 SuccessString: 'The calculation is completed!' 0  StopString: 'The thread is stopped' 0]

______________________________________________________________________________________

; Window Class Structure:

[WindowClassEx: wc_Size: len          wc_style: &CS_HREDRAW+&CS_VREDRAW WndProc: MainWindowProc
                wc_ClsExtra: 0        wc_WndExtra: 0                  hInstance: 0
                wc_hIcon: 0           wc_hCursor: 0                   wc_hbrBackground: 6
                wc_MenuName: M00_Menu wc_ClassName: ClassName         wc_hIconSm: 0]
 

[FirstMessage: 0 #7]

Main:
    call 'Kernel32.GetModuleHandleA' &NULL   | mov D§hInstance eax
    call 'User32.LoadIconA'  0  &IDI_WINLOGO | mov D§wc_hIcon eax  D§wc_hIconSm eax
    call 'User32.LoadCursorA' 0  &IDC_ARROW  | mov D§wc_hCursor eax
    call 'User32.RegisterClassExA'  WindowClassEX
    call 'User32.CreateWindowExA' &NULL ClassName AppName,
                                 &WS_OVERLAPPEDWINDOW,
                                 &CW_USEDEFAULT  &CW_USEDEFAULT 300 200,
                                 &NULL &NULL D§hInstance &NULL
        mov D§WindowHandle eax

    call 'User32.ShowWindow'  D§WindowHandle &SW_SHOWNORMAL
    call 'User32.UpdateWindow'  D§WindowHandle
    call 'User32.GetMenu' D§WindowHandle
        mov D§MenuHandle eax

L1: call 'User32.GetMessageA' FirstMessage 0 0 0 | cmp eax 0 | je L9>
        call 'User32.TranslateMessage'  FirstMessage
        call 'User32.DispatchMessageA'  FirstMessage
    jmp L1<

L9: call 'Kernel32.ExitProcess' 0

____________________________________________________________________________________________
____________________________________________________________________________________________
 

Proc MainWindowProc:
    Arguments @Adressee, @Message, @wParam, @lParam
    pushad
 
    .If D@Message = &WM_DESTROY
         call 'User32.PostQuitMessage' &NULL

    .Else_If D@Message = &WM_CREATE
        call 'Kernel32.CreateEventA' &NULL &FALSE &FALSE &NULL
        mov D§EventStartHandle eax
        call 'Kernel32.CreateThread' &NULL &NULL ThreadProc  &NULL,
                                    &NORMAL_PRIORITY_CLASS  ThreadID
          mov D§ThreadHandle eax

    .Else_If D@Message = &WM_COMMAND
        mov eax D@wParam
        ..If D@lParam = 0
            ...If ax = M00_Run_Thread
                 call 'Kernel32.SetEvent' D§EventStartHandle
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Run_Thread  &MF_GRAYED
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Stop_Thread &MF_ENABLED

            ...Else_If ax = M00_Stop_Thread
                 mov B§EventStop &TRUE
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Run_Thread  &MF_ENABLED
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Stop_Thread &MF_GRAYED
            ...Else
                 call 'User32.DestroyWindow' D@Adressee
 
            ...End_If
         ..End_If

     .Else_If D@Message = WM_FINISH
         call 'User32.MessageBoxA' &NULL SuccessString  AppName &MB_OK

      .Else
        popad
        call 'User32.DefWindowProcA' D@Adressee D@Message D@wParam D@lParam
        Exit
 
      .End_If
 
      popad | mov eax &FALSE
EndP

____________________________________________________________________________________________
____________________________________________________________________________________________
 

ThreadProc:
    call 'Kernel32.WaitForSingleObject' D§EventStartHandle &INFINITE
    mov ecx 600000000
 
   .While ecx a 0
       If B§EventStop = &FALSE
          add eax eax
          dec ecx
       Else
          call 'User32.MessageBoxA' D§WindowHandle StopString  AppName &MB_OK
          mov B§EventStop &FALSE
          jmp ThreadProc
       End_If
   .End_While
 
    call 'User32.PostMessageA'  D§WindowHandle  WM_FINISH  &NULL  &NULL
    call 'User32.EnableMenuItem' D§MenuHandle  M00_Run_Thread  &MF_ENABLED
    call 'User32.EnableMenuItem' D§MenuHandle  M00_Stop_Thread  &MF_GRAYED
 
  jmp   ThreadProc

Analysis:

In this example, I demonstrate another thread technique.
 

    .Else_If D@Message = &WM_CREATE
        call 'Kernel32.CreateEventA' &NULL &FALSE &FALSE &NULL
        mov D§EventStartHandle eax
        call 'Kernel32.CreateThread' &NULL &NULL ThreadProc  &NULL,
                                    &NORMAL_PRIORITY_CLASS  ThreadID
          mov D§ThreadHandle eax

You can see that I create the event object and the thread during the processing of WM_CREATE message. I create the event object in the nonsignalled state with automatic reset. After the event object is created, I create the thread. However the thread doesn't run immediately because it waits for the event object to be in the signalled state as the code below:
 

ThreadProc:
    call 'Kernel32.WaitForSingleObject' D§EventStartHandle &INFINITE
    mov ecx 600000000

The first line of the thread procedure is the call to WaitForSingleObject. It waits infinitely for the signalled state of the event object before it returns. This means that even when the thread is created, we put it into a dormant state.
When the user selects "run thread" command from the menu, we set the event object into signalled state as below:

            ...If ax = M00_Run_Thread
                 call 'Kernel32.SetEvent' D§EventStartHandle
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Run_Thread  &MF_GRAYED
                 call 'User32.EnableMenuItem' D§MenuHandle  M00_Stop_Thread &MF_ENABLED

The call to SetEvent turns the event object into the signalled state which in turn makes the WaitForSingleObject call in the thread procedure return and the thread starts running. When the user selects "stop thread" command,  we set the value of the global variable "EventStop" to TRUE.
 

       If B§EventStop = &FALSE
          add eax eax
          dec ecx
       Else
          call 'User32.MessageBoxA' D§WindowHandle StopString  AppName &MB_OK
          mov B§EventStop &FALSE
          jmp ThreadProc
       End_If

This stops the thread and jumps to the WaitForSingleObject call again. Note that we don't have to manually reset the event object into nonsignalled state because we specify the bManualReset parameter of the CreateEvent call as FALSE.


[Iczelion's Win32 Assembly HomePage]