Tutorial 16: Event Object

このチュートリアルでは、イベントオブジェクトがどういったものかを説明し、 マルチスレッドプログラムにおける使用方法を紹介する。
 ソース   リソーススクリプト   実行結果 

Theory:

前章ではウィンドウメッセージをカスタマイズしてスレッド間の通信方法をどのように行うかの説明をしたが、 残り2つの方法(グローバル領域の変数を使用する方法とイベントオブジェクトを使用する方法のことだ)は、 まだ説明していないので、この章ではこの2つの方法の説明を行う。

イベントオブジェクトは2つの状態しか表現せず、スイッチのようなものになっている。 イベントオブジェクトスイッチがONになると、「シグナル状態」となり、 イベントオブジェクトスイッチがOFFになると、「非シグナル状態」ということになる。 イベントオブジェクトを作成すると、 そのイベントオブジェクトの状態を監視する必要のあるスレッドに、 監視するプログラムを記述しなければならない。 もしイベントオブジェクトが非シグナル状態なら、 そのイベントオブジェクトを待ち受けるスレッドが休止状態である。 スレッドが待ち状態であるとき、ほとんどCPUに負荷はかからない。

CreateEvent関数をCALLすることにより、イベントオブジェクトを作成することができる。 この関数のプロトタイプは以下のようになっている。

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

呼び出しが成功すれば作成されたイベントオブジェクトのハンドルを返し、失敗すれば、NULLが返る。
そのイベントオブジェクトの状態を変更する2つのAPI関数があり、SetEvent関数とResetEvent関数である。 SetEvent関数はイベントオブジェクトをシグナル状態にセットし、ResetEvent関数はその逆である。

イベントオブジェクトが作成されたとき、 そのイベントオブジェクトの状態を監視したいスレッドで、WaitForSingleObject関数をCALLしないといけない。 WaitForSingleObject関数のプロトタイプは以下のようになっている。

WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD

Example:

下のサンプルはユーザがメニューの選択を待ち受けるプログラムだ。 ユーザが「run thread」を選択したら、スレッドは泥臭い計算を開始させる。 それが終了すれば、処理が終了したというメッセージボックスが表示される。 スレッドが起動している間は、ユーザがスレッドを中止させることができるように、 「stop thread」メニューが選択できるようになる。

.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

Analysis:

この例では、前章とは違ったスレッドテクニックを紹介している。

.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

見ればわかるように、WM_CREATEメッセージを受け取ると、 初期状態が非シグナル状態の自動リセット型のイベントオブジェクトを作成したあと、スレッドを作成する。 しかしながら、スレッドはすぐには起動しない。 というのは、以下のコードのように、イベントオブジェクトがシグナル状態になるのを待つためである。

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

スレッド関数の最初の行はWaitForSingleObject関数の呼び出しで、 イベントオブジェクトがシグナル状態になるまで制御を返さない。 つまり、スレッドが作成されたときでさえ、 スレッドは停止状態となっている。

ユーザがメニューから「run thread」を選択すると、以下のようにイベントオブジェクトをシグナル状態にする。

.if ax==IDM_START_THREAD
    invoke SetEvent,hEventStart

SetEvent関数の呼び出しにより、イベントオブジェクトをシグナル状態にセットし、次に、 イベントオブジェクトの状態を監視しているスレッドプロシージャで呼び出しているWaitForSingleObject関数の制御が返り、 そして、スレッドが開始される。 ユーザが「stop thread」メニューを選択したとき、グローバル変数の「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

上記のコードでは、スレッドを終了し、WaitForSingleObject関数を再度CALLするところへジャンプする。 ここでイベントオブジェクトを手動で非シグナル状態にする必要はない。 なぜなら、CreateEvent関数の引数の1つである bManualReset をFALSEに設定しているからである。


[戻る]