Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating New Threads From VB Applications

0.00/5 (No votes)
22 Apr 2004 1  
A component to execute a COM method in a separate thread.

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.

' source folder path

dim source
source = "c:\test"
' target folder path

dim target
target = "c:\temp\"
' execute method CopyFolder in COM object 

' Scripting.FileSystemObject

dim obj
set obj = CreateObject("VBThreadObj.1")
obj.ExecuteComMethod "Scripting.FileSystemObject", _ 
  "CopyFolder", source, target, True
' click the OK button when done

wscript.echo "Copying folder " & source _ 
  & " into folder " & target & _ 
  ", click OK when it is done"
set obj = nothing

All parameters of ExecuteComMethod are VARIANTs. 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 VARIANTs.

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.

' source folder path

dim source
source = "c:\test"
' target folder path

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
        ' handle error here

    Else
        ' process return value here

    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here