Tutorial 15: Multithreading Programming
Download the example here.
CreateThread proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD
CreateThread function looks a lot like CreateProcess.
lpThreadAttributes --> You can use NULL if you want the thread
to have default security descriptor.
dwStackSize --> specify the stack size of the thread. If you want the
thread to have the same stack size as the primary thread, use NULL as this parameter.
lpStartAddress--> Address of the thread function.It's the function that
will perform the work of the thread. This function MUST receive one and only
one 32-bit parameter and return a 32-bit value.
lpParameter --> The parameter you want to pass to the thread function.
dwCreationFlags --> 0 means the thread runs immediately after it's created.
The opposite is CREATE_SUSPENDED flag.
lpThreadId --> CreateThread function will fill the thread ID of the newly
created thread at this address.
If CreateThread call is sucessful, it returns the handle of the newly created
thread. Otherwise, it returns NULL.
The thread function runs as soon as CreateThread call is success ful unless
you specify CREATE_SUSPENDED flag in dwCreationFlags. In that case, the thread
is suspended until ResumeThread function is called.
When the thread function returns with ret instruction, Windows calls ExitThread
function for the thread function implicitly. You can call ExitThread function
with in your thread function yourself but there' s little point in doing so.
You can retrieve the exit code of a thread by calling GetExitCodeThread function.
If you want to terminate a thread from other thread, you can call TerminateThread
function. But you should use this function under extreme circumstance since
this function terminates the thread immediately without giving the thread any
chance to clean up after itself.
Now let's move to the communication methods between threads.
There are three of them:
WM_MYCUSTOMMSG equ WM_USER+100h
Windows will not use any value from WM_USER upward for its own messages so
you can use the value WM_USER and above as your own custom message value.
If one of the thread is a user interface thread and the other is a worker one,
you cannot use this method as two-way communication since a worker thread doesn't
have its own window so it doesn't have a message queue. You can use the following
scheme:
User interface Thread ------> global variable(s)----> Worker thread
Worker Thread ------> custom window message(s) ----> User interface Thread
In fact, we will use this method in our example.
The last communication method is an event object. You can view an event object
as a kind of flag. If the event object is in "unsignalled" state, the thread
is dormant or sleeping, in this state, the thread doesn't receive CPU time slice.
When the event object is in "signalled" state,Windows "wakes up" the thread
and it starts performing the assigned task.
.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_THREAD equ 1
IDM_EXIT equ 2
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMThreadClass",0
AppName db "Win32 ASM MultiThreading Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
ThreadID DWORD ?
.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
.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_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.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
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP
end start
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
The above function creates a thread that will run a procedure named ThreadProc
concurrently with the primary thread. After the successful call, CreateThread
returns immediately and ThreadProc begins to run. Since we do not use thread
handle, we should close it else there'll be some leakage of memory. Note that
closing the thread handle doesn't terminate the thread. Its only effect is that
we cannot use the thread handle anymore.
ThreadProc PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc ENDP
As you can see, ThreadProc performs a savage calculation which takes quite a while to finish and when it finishs it posts a WM_FINISH message to the main window. WM_FINISH is our custom message defined like this: