Introduction
When writing complicated applications, you may need to do some tasks in the background. For example, you may need to write code to periodically check if an external database server is up and send alert e-mails automatically to production support folks if it is down. Another example is, you may want to monitor a folder that contains files uploaded by Internet users and process these files as they come in.
If you know C or C++, you can use the CreateThread
function in kernel32.dll. This function creates a worker thread and executes a function of your choice in the new thread. The new thread will terminate once your function is executed. What you need to do is, pass the address of your function and its parameter when calling CreateThread
.
What if you are working on VB or ASP applications? Can you create a new thread from your code using VB or VBScript? It is possible to call Win32 APIs from VB, however it cannot be done easily, it is not an elegant solution, and you cannot pass the address of any VB function to CreateThread
because only certain types of functions can be executed in the new thread it creates. I haven't tried calling the CreateThread
function from VB, personally.
However, if the function you want to execute in a new thread is in a COM DLL, then you can do this easily using a simple tool I wrote. And nothing is easier than creating a COM DLL in VB!
VBThread.dll
This is a regular COM component written with VC++. I initially intended to use it to create new threads from VB code, hence the name VBThread.dll, but VB doesn't really matter here. There are only two methods in this COM component, ExecuteComMethod
and ExecuteComMethodRepeat
.
To call the first method, you specify the name (the progid string) of a COM object, the name of a method of the COM object, and up to 5 optional parameters as input. It will start a new thread in the background, create a COM object using the progid string from the new thread, and call the COM method identified by the method name string. The 5 optional parameters will be passed to the COM method being called, of course.
So here is what you need to do to process background jobs in VB. For each background job, you write the VB code in a COM method, then call the ExecuteComMethod
method in VBThread.dll to execute the COM method in a new thread. Please note that there is nothing to stop you from using VBThread.dll in C or C++ code.
Here is a sample VBScript file that uses VBThread.dll to copy one folder into another.
dim source
source = "c:\test"
dim target
target = "c:\temp\"
dim obj
set obj = CreateObject("VBThreadObj.1")
obj.ExecuteComMethod "Scripting.FileSystemObject", _
"CopyFolder", source, target, True
wscript.echo "Copying folder " & source _
& " into folder " & target & _
", click OK when it is done"
set obj = nothing
All parameters of ExecuteComMethod
are VARIANT
s. Instead of passing the progid string as the first parameter, you can also pass a COM object already created in your VB code, in which case the existing COM object will be used in the new thread. Similarly, the second parameter can be either the method name string or the dispatch ID value of that COM method. In VB applications, you need to use the VARIANT
type explicitly when passing the 5 optional parameters to ExecuteComMethod
, you don't have to do this for VBScript because all variables in VBScript are already VARIANT
s.
The code below is the same example as above except that the COM object Scripting.FileSystemObject
is not created in the new thread, it is already created before calling ExecuteComMethod
.
dim source
source = "c:\test"
dim target
target = "c:\temp\"
' execute method CopyFolder in COM object
' Scripting.FileSystemObject
dim obj
set obj = CreateObject("VBThreadObj.1")
dim oFS
set oFS = CreateObject("Scripting.FileSystemObject")
obj.ExecuteComMethod oFS, "CopyFolder", source, target, True
set oFS = nothing
' click the OK button when done
wscript.echo "Copying folder " & source _
& " into folder " & target _
& ", click OK when it is done"
set obj = nothing
Please note:
- You should be very careful when using a COM object in two different threads at the same time, your COM object needs to be thread-safe. For some (apartment model threading) COM objects, if you create it from the main thread of your VB application and use it in a new thread, the main thread will block while it is being used, hence defeating the purpose of executing the method in a new thread.
- Also, some COM objects cannot be created in one thread and then used in a different thread. Some COM objects cannot be created or used in a worker thread at all because they rely on windows.
The second method in VBThread.dll, ExecuteComMethodRepeat
, is almost the same as the first one except that the COM object and its method you specified in the input parameters will be called repeatedly in the new thread.
This method has two extra parameters. The first one is nInitialPause
, which specifies the number of seconds to wait initially before the COM method is executed in the new thread. The second one is nPause
, which specifies the number of seconds to wait before the COM method is executed again. If nPause
is 0 or negative, then the COM method will be executed only once. The following command will create a COM object with progid HelloTest
in a new thread, then wait 30 seconds and call the Hello
method, and the Hello
method will be called repeatedly every 150 seconds until the program dies.
dim obj
set obj = CreateObject("VBThreadObj.1")
obj.ExecuteComMethodRepeat _
30, 150, "HelloTest", "Hello", ...
Return value and error description of your COM method
The two methods in VBThread.dll will return True
if the new thread is created successfully, otherwise they will return False
. They will not wait for the new thread to finish. Then, how do you know if the COM method specified in the input has been executed successfully? How do you get the output value of the COM method? This problem is solved by the WorkDone
COM event. This event will be fired by VBThread.dll after the COM method is executed in the new thread. It has two parameters, the first one is a VARIANT
representing the return value of the COM method specified in the input to ExecuteComMethod
, the second one is an error description string. When there is no error, the error string will be empty. Here is how to handle the WorkDone
event in your VB application, and retrieve the return value and/or the error description string.
First, you need to add a reference for the COM object VBThreadObj
to your VB project. Then declare and create the COM object using the following code:
Dim WithEvents vbThreadObj As VBTHREADLib.ThreadObj
...
Set vbThreadObj = New VBTHREADLib.ThreadObj
...
Then you define an event handler subroutine for the WorkDone
event as follows:
Private Sub vbThreadObj_WorkDone(ByVal vOutput As Variant, _
ByVal sError As String)
If sError <> "" Then
Else
End If
End Sub
When you call ExecuteComMethod
to execute your COM method in a new thread, the above event handler routine will be invoked by VBThread.dll after the new thread finished executing your COM method. Note that the WorkDone
event will be fired repeatedly if you call the ExecuteComMethodRepeat
method to execute your COM method repeatedly.
More details and a test program
The ExecuteComMethodRepeat
method can be used, for example, to set up a background job that runs every 20 minutes or every 2 days. You can also schedule a job that runs only once at specified time, such as 10:00AM next Monday, all you have to do is use 0 as the nPause
parameter value and figure out the correct value for the nInitialPause
parameter.
You may argue that if the program that uses VBThread.dll dies, the background threads it started will be gone and all the scheduled jobs will be lost. What you can do is run the program from XYNTService, which means the program can be running without anyone logged on to the machine, XYNTService can also restart the program if it dies.
The code in VBThread.dll is very simple. The main part is the XYDispDriver class described in one of my other articles. I initially wrote it to simplify my C++ code, now I am reusing it to simplify my VB programs. :-) As you can see from the source code, the limit on the number of parameters (for the COM method you want to execute in a new thread) is not hard to change.
I have included with this article, a VB test program VBThreadTest.exe. This program copies one folder into another folder just as the VBScript shown above does. The copying is done in a new background thread using VBThread.dll, the WorkDone
event is handled within the program. When copying a large folder, you can actually start another thread to copy a different folder, before the first background thread is finished. A known problem with this program is that it will crash if you don't have permission to read the content of the folder you are copying.
The component VBThread.dll has been tested on Windows NT 4.0, Windows 2000 and Windows XP. It may not work on Windows 9x.
Thank you for reading my article.
Recent Updates
- 10/29/2003: Added the
WorkDone
event so that client programs can retrieve the return value and handle error for the COM method executed in the new thread. Modified article text.
- 10/23/2003: Fixed a bug related to the previous enhancement (the previous version only takes dispatch id that is less than 65536).
- 10/16/2003: Changed code so that a dispatch id can be used to identify a COM method. Modified article text.