[/b]
Multithreading in DarkBASIC Professional
Here are a set of functions to call DLL functions in their own threads, leaving the main thread free for you to operate in while the call is in progress. This is particularly useful for Win API functions such as MessageBox, ChooseColor, and ChooseFont where the application must wait for the user to click something before continuing.
It is also possible to call DBPro functions like this, although you must be very careful about the operations you do while a call is in progress. For example, it is possible to call
sync to handle rendering in another thread, but while this is processing you must not make/load any media, or modify any data that rendering uses. You can of course read the position/angle of objects and sprites, but you can't set them. This means you are very limited in what you can do while rendering is in progress, but there are some uses to it. Do not assume that just because the application doesn't crash when you test it, what you are doing is safe. Note that for DBPro functions you should use the cdecl version of the functions,
PrepareThreadedDllCallC and
CallThreadedDllFunctionC. Here is an example:
` Disable escape key. This is necessary to avoid exiting while the call is
` in progress, which will cause a crash.
disable escapekey
` Prepare SYNC call
syncCallPtr= PrepareThreadedDllCall("DBProCore.dll", "?Sync@@YAXXZ", 0, 0)
` Main loop
do
` Do whatever
...
` Start SYNC
ThreadedCall(syncCallPtr)
` Do whatever while call is in progress
...
` Wait for call to finish
repeat
until CallIsComplete(syncCallPtr)
` Handle escape key
if escapekey() then end
loop
You can either set up and call a function in one operation, or prepare a function that you can call multiple times with less overhead. With the latter method the memory allocated for parameter data isn't automatically freed, so you can modify it before each call without having to allocate more memory for it.
When you prepare or make a function call, you pass in a pointer to a block of memory created by
make memory that the parameters are passed from. The size of this data specified must be a multiple of 4. Here is an example:
` Make memory for parameters. This is automatically freed if you do a simple function
` call rather than preparing one first.
ptr = make memory(16) : tempPtr = ptr
` Use * symbol to write data at the address contained in tempPtr. Note that you can
` specify a string and it'll write the address of it, but never use local variables
` for this and never modify them while the call is in progress.
*tempPtr = 0 : inc tempPtr, 4
*tempPtr = "Caption" : inc tempPtr, 4
*tempPtr = "Title" : inc tempPtr, 4
*tempPtr = 0 : inc tempPtr, 4
` Call function and return pointer to function call data. This can be used to check
` when the call completes by calling CallIsComplete and passing in this value.
callPtr = CallThreadedDllFunction("user32.dll", "MessageBoxA", ptr, 16)
You can then check when the call completes by calling
CallIsComplete and passing in the pointer returned by
CallThreadedDllFunction. When this function reports the call as complete, you must discard the pointer if you did not prepare the function call. Otherwise, you can reuse it to call the function again.
` Wait for call to complete
repeat
until CallIsComplete(callPtr)
Note: You must define DLL_KERNEL32 at the top of your code, to specify a DLL number for the functions to use.
Function list
PrepareThreadedDllCall - Prepares a DLL function call that you can use multiple times.
CallThreadedDllFunction - Calls a DLL function in another thread.
PrepareThreadedDllCallC - Identical to
PrepareThreadedDllCall, but for cdecl functions.
CallThreadedDllFunctionC - Identical to
CallThreadedDllFunction, but for cdecl functions.
ThreadedCall - Calls a function with a previously prepared function call.
CallIsComplete - Checks if the specified call has completed.
GetCallReturnValue - Retrieves the value returned by the call.
Functions
function PrepareThreadedDllCall(dllName as string, funcName as string, paramPtr as dword, paramSize as dword)
local callPtr as dword
callPtr = SetupThreadedDllCall(dllName, funcName, paramPtr, paramSize, 0, 0)
endfunction callPtr
function CallThreadedDLLFunction(dllName as string, funcName as string, paramPtr as dword, paramSize as dword)
local callPtr as dword
callPtr = SetupThreadedDllCall(dllName, funcName, paramPtr, paramSize, 1, 0)
endfunction callPtr
function PrepareThreadedDllCallC(dllName as string, funcName as string, paramPtr as dword, paramSize as dword)
local callPtr as dword
callPtr = SetupThreadedDllCall(dllName, funcName, paramPtr, paramSize, 0, 1)
endfunction callPtr
function CallThreadedDLLFunctionC(dllName as string, funcName as string, paramPtr as dword, paramSize as dword)
local callPtr as dword
callPtr = SetupThreadedDllCall(dllName, funcName, paramPtr, paramSize, 1, 1)
endfunction callPtr
function ThreadedCall(callDataPtr as dword)
local tempPtr as dword : tempPtr = callDataPtr + 16
*callDataPtr = 0
*tempPtr = call dll(DLL_KERNEL32, "CreateThread", 0, 0, callDataPtr+20, 0, 0, 0)
endfunction
function CallIsComplete(callDataPtr as dword)
local tempPtr as dword : tempPtr = callDataPtr + 16
if (*callDataPtr) = 0 then exitfunction 0
call dll DLL_KERNEL32, "CloseHandle", *tempPtr
` If call is not reusable, free memory
if not (*callDataPtr)
tempPtr = callDataPtr + 4
if (*tempPtr) then delete memory *tempPtr
call dll DLL_KERNEL32, "VirtualFree", callDataPtr, 0, 0x8000
endif
endfunction 1
function GetCallReturnValue(callDataPtr as dword)
local tempPtr as dword : tempPtr = callDataPtr + 12
local returnValue as dword : returnValue = *tempPtr
endfunction returnValue
function SetupThreadedDllCall(dllName as string, funcName as string, paramPtr as dword, paramSize as dword, useOnce as boolean, clearStack as boolean)
local callDataPtr as dword
local tempPtr as dword
local tempPtr2 as dword
if not dll exist(DLL_KERNEL32) then load dll "kernel32.dll", DLL_KERNEL32
dllHandle = call dll(DLL_KERNEL32, "LoadLibraryA", dllName)
if not dllHandle
exit prompt "Failed to load DLL: "+dllName, "Error"
end
endif
procAddr = call dll(DLL_KERNEL32, "GetProcAddress", dllHandle, funcName)
if not procAddr
exit prompt "Failed to load function pointer: "+funcName, "Error"
end
endif
if useOnce
kernel32Handle = call dll(DLL_KERNEL32, "GetModuleHandleA", "kernel32.dll")
freeLibrary = call dll(DLL_KERNEL32, "GetProcAddress", kernel32Handle, "FreeLibrary")
endif
callDataPtr = call dll(DLL_KERNEL32, "VirtualAlloc", 0, (paramSize/4 * 5) + 70, 0x3000, 0x40)
tempPtr = callDataPtr + 20
if paramSize > 0
tempPtr2 = paramPtr + paramSize - 4
for x = 1 to paramSize/4
*tempPtr = 0x68 : inc tempPtr
*tempPtr = *tempPtr2 : inc tempPtr, 4
dec tempPtr2, 4
next x
endif
*tempPtr = 0xB8 : inc tempPtr
*tempPtr = procAddr : inc tempPtr, 4
*tempPtr = 0xD0FF : inc tempPtr, 2
*tempPtr = 0xA3 : inc tempPtr
*tempPtr = callDataPtr + 12 : inc tempPtr, 4
if clearStack
*tempPtr = 0xC481 : inc tempPtr, 2
*tempPtr = paramSize : inc tempPtr, 4
endif
*tempPtr = 0x05C7 : inc tempPtr, 2
*tempPtr = callDataPtr : inc tempPtr, 4
*tempPtr = 1 : inc tempPtr, 4
if useOnce
*tempPtr = 0x68 : inc tempPtr
*tempPtr = dllHandle : inc tempPtr, 4
*tempPtr = 0xB8 : inc tempPtr
*tempPtr = freeLibrary : inc tempPtr, 4
*tempPtr = 0xD0FF : inc tempPtr, 2
endif
*tempPtr = 0xC2 || (4 << 8) : inc tempPtr, 3
tempPtr = callDataPtr + 4
*tempPtr = paramPtr : inc tempPtr, 4
if useOnce
*tempPtr = 0
ThreadedCall(callDataPtr)
else
*tempPtr = 1
endif
endfunction callDataPtr
Example 1
#constant DLL_KERNEL32 1
sync on
sync rate 60
disable escapekey
make object cube 1, 10
` Allocate parameter buffer for function
ptr = make memory(16) : tempPtr = ptr
*tempPtr = 0 : inc tempPtr, 4
*tempPtr = "Now formatting HDD "Z". Please wait while all your files are lost..." : inc tempPtr, 4
*tempPtr = "Notice" : inc tempPtr, 4
*tempPtr = 0x21
` Prepare MessageBox call
callPtr = PrepareThreadedDllCall("user32.dll", "MessageBoxA", ptr, 16)
do
set cursor 0, 0
if msgBoxOpen = 0
print "Press enter to display a message box."
if buttonClicked = 1 then print "Last operation confirmed."
if buttonClicked = 2 then print "Last operation canceled."
endif
` If message box isn't currently open, allow it to be opened
if msgBoxOpen = 0
if returnkey()
ThreadedCall(callPtr)
msgBoxOpen = 1
endif
` Else check if the call has completed
else
if CallIsComplete(callPtr)
msgBoxOpen = 0
buttonClicked = GetCallReturnValue(callPtr)
endif
endif
` Rotate the cube and refresh the screen
turn object right 1, 1
sync
` Handle escape key
if escapekey() then end
loop