Tutorial 16: Ereignis-Objekte
Wir werden lernen, was ein Ereignis Objekt ist und wie es in multithreaded Programmen benutzt wird.
Laden Sie hier das Beispiel herunter.
Theorie:
In dem vorherigen Tutorial habe ich demonstriert, wie Threads mit einer eigenen Fenster-Nachricht kommunizieren können. Ich habe zwei andere Methoden ausgelassen: globale Variablen und Ereignis Objekte. Wir werden beide in diesem Tutorial benutzen.
Ein Ereignis Objekt ist wie ein Schalter: es hat nur zwei Stati: an oder aus. Wenn ein Ereignis Objekt angeschaltet ist, ist es im "signalisierendem" Status. Wenn es ausgeschaltet ist, ist es im "nicht signalisiertem" Status. Sie können ein Ereignis Objekt erzeugen und einen Code-Schnippsel in die relevanten Threads tun, um den Status des Ereignis Objekts zu überwachen. Wenn das Ereignis Objekt im nicht signalisiertem Status ist, werden die Threads die darauf warten auf den Status 'sleep' gestellt. Wenn Threads im wartenden Status sind, brauchen sie ein wenig CPU-Zeit.
Sie erzeugen ein Ereignis Objekt indem Sie die CreateEvent-Funktion aufrufen, welche folgende Syntax hat:
CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD
lpEventAttribute-- Wenn Sie den Wert NULL angeben, wird das Ereignis Objekt mit den Standard-Sicherheits-Deskriptor erstellt.
bManualReset-- Wenn Sie wollen, dass Windows automatisch das Ereignis Objekt in den nicht signalisierenden Status zurücksetzt, müssen Sie FALSE als Parameter angeben. Ansonsten müssen Sie das Ereignis Objekt manuell resetten, indem Sie ResetEvent aufrufen.
bInitialState-- Wenn Sie wollen, dass das Ereignis Objekt im signalisierendem Status erzeugt wird, geben Sie als TRUE bei diesem Parameter an, ansonsten wird das Ereignis Objekt in einem nichtsignalisierenden Objekt erzeugt.
lpName -- Zeiger auf einen ASCIIZ String der den Namen des Ereignis Objekts enthält. Dieser Name wird benutzt, wenn Sie OpenEvent aufrufen wollen.
Wenn der Aufruf erfolgreich war, wird das Handle des neu erzeugten Ereignis Objekts zurückgeliefert, ansonsten NULL.
Sie können den Status eines Ereignis Objekts mit zwei API Aufrufen verändern. SetEvent und ResetEvent. Die SetEvent-Funktion versetzt das Ereignis Objekt in den signalisierenden Status. ResetEvent macht das Gegenteilige.
Wenn das Ereignis Objekt erzeugt wurde, müssen Sie den Aufruf von WaitForSingleObject in dem Thread plazieren, der den Status des Ereignis Objekts überwachen soll.
WaitForSingleObject hat folgende Syntax:
WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD
hObject -- Ein Handle eines zu synchronisierenden Objekts. Ein Ereignis Objekt ist eine Art von synchronisierenden Objekten.
dwTimeout -- spezifiziert die Zeit in Millisekunden, die die Funktion auf das Objekt wartet, bis es im signalisierenden Status (verfügbar) ist. Wenn die angegebene Zeit verstrichen ist und das Ereignis Objekt immer noch nicht verfügbar ist, kehrt WaitForSingleObject zum Aufrufer zurück. Wenn Sie wollen, dass unendlich lange auf das Objekt gewartet wird, müssen Sie den Wert INFINITE als Parameter übergeben.
Beispiel:
Das Beispiel unten, zeigt eine Fenster an, dass darauf wartet, dass der Benutzer ein Kommando aus dem Menü auswählt. Wenn der Benutzer "run thread" auswählt startet der Thread die wilde Berechnung. Wenn diese beendet ist, erscheint eine Message Box, die den Benutzer darüber informiert, dass die Aufgabe erledigt ist. Während der Zeit, wo der Thread läuft kann der Benutzer "stop thread" auswählen, um den Thread zu unterbrechen.
.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 "Die Berechnung ist beendet!",0
StopString db "Der Thread ist gestoppt ",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:
In diesem Beispiel demonstriere ich eine andere Thread-Technik.
.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
Sie können sehen, dass ich das Ereignis Objekt und den Thread dann erzeuge, wenn die WM_CREATE Nachricht bearbeitet wird. Ich erzeuge das Ereignis Objekt im nicht signalisierendem Status mit automatischem Reset. Nachdem das Ereignis Objekt erzeugt wurde, erzeuge ich den Thread. Der Thread läuft aber nicht sofort lost, da er darauf wartet, dass das Ereignis Objekt im signalisierendem Stauts ist, wie im folgenden Code:
ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
Die erste Zeile der Thread-Prozedur ist der Aufruf von WaitForSingleObject. Er wartet unendlich auf das signalisierende Signal des Ereignis Objekts bevor er zurückkehrt. Das bedeutet, dass selbst wenn der Thread erzeugt wird, hat er einen schlafenden Status.
Wenn der Benutzer "run thread" aus dem Menü auswählt, setzen wir das Ereignis Objekt in den signalisierenden Stauts wie folgt:
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
Der Aufruf von SetEvent versetzt das Ereignis Objekt in den signalisierenden Status, welches wiederum den WaitForSingleObject Aufruf in der Thread-Prozedur zurückkehren lässt und der Thread anfängt zu laufen. Wenn der Benutzer "stop thread" auswählt, setzen wir die globale Variable "EventStop" auf 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
Das stoppt den Thread und springt wieder zu dem WaitForSingleObject Aufruf. Beachten Sie, dass wir das Ereignis Objekt nicht manuell in den nicht signalisierenden Zustand versetzen müssen, da wir den bManualReset Parameter vom CreateEvent-Aufruf auf FALSE gesetzt haben.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage