Search This Blog

Monday, February 7, 2011

Using Threads in Visual Basic

Threads in Visual Basic are not commonly used because of complexity. In a 32 bit Windows environment (talking about Windows 95/98/NT) it has become a necessity to use more than one thread for each process. But is it true that creating threads in Visual Basic is tough? Not really! Visual Basic has its own feature of simplifying Windows 32 bit API calls. Think about a long backup procedure, which needs to be intervened in case the user wants to suspend or stop the backup procedure. If you have not implemented a separate thread for this copy purpose, you will not be able to stop the process until and unless you kill the process forcefully or you restart your machine (Windows 95 non-OEM versions might show endless blue screens). This is not a good idea for a professional grade application. Therefore let us talk about creating threads.
We will look at creating free threading model (and not apartment model of threading) in Visual Basic using the 'CreateThread' Win32 API call. Those who are familiar with creating Win32 applications using Visual C++, will be able to recognize this little function which helps in creating a new thread within a process very easily. In Visual Basic, it is also pretty easy to create a thread. I will take a look at the example code ExCopy (which is supplied with this article). This code is used to break a long file into several small 3¼-inch high-density floppy disks (similar to what you see in WinZip utility). An independent thread handles the copy process. Therefore you can see that during the copy process, you can easily move the ExCopy window.

How the thread is created:
Let us have a look at the following function written in the example code…

'Thread creation helper function
Public Function StartCopy(clsObject As clsExCopy)
    Dim NewThreadID As Long
    Dim Threadhandle As Long
    Dim Param As Long

    ' Free threaded approach
    Param = ObjPtr(clsObject)

    'Create a thread with no security attribute but
    'with default stack size and default creation flag
    Threadhandle = CreateThread(0,_
        0, _
        AddressOf ThreadFunction, _
        Param, _
        0, _

    If Threadhandle = 0 Then
        ' Return with zero (error)
        Debug.Print "Unable to create the free thread"
        Exit Function
    End If

    ' We don't need the thread handle
    CloseHandle Threadhandle

    'Return the created ID
    StartCopy = NewThreadID
End Function

This public function is used to create the thread. StartCopy takes only one parameter of clsExCopy type. The class clsExCopy is defined in the ExCopyFile_IO.cls module. This class is used to copy data to the disks. You should call the StartCopy function with a valid clsExCopy class object. For example you might want to call this function when the user clicks on 'Start' button…

Private Sub StartBtn_Click()
    'Create a new Thread object
    If ExCopyObj Is Nothing Then
        Set ExCopyObj = New clsExCopy
        Set ExCopyObj = Nothing
        Set ExCopyObj = New clsExCopy
    End If

    ExCopyObj.bStopNow = False

    'Create a new thread
    StartCopy ExCopyObj

    'Can not click on Start now
    StartBtn.Enabled = False
End Sub

Therefore you are passing an object value to the StartCopy function. In this function the Win32 API CreateThread function is used to create a new thread.
Now take a closer look at the call to this function.
The first parameter is actually a pointer to the SECURITY_ATTRIBUTE structure. We are passing zero to avoid any security feature.
The second parameter is used to represent the stack size of the newly created thread. We are passing zero to let Windows Kernel determine the stack value.
The CreateThread function uses the third parameter as a function pointer, which it calls when the thread is created. The third parameter uses the 'AddressOf' unary operator which returns the pointer to a function (acceptable by the API). Therefore AddressOf ThreadFunction will return the actual pointer to the function 'ThreadFunction' which does the Win32 API very well accept.
The fourth parameter is used as a pointer to the arguments passed to the function mentioned in third parameter. Visual Basic has a function ObjPtr which returns the pointer to memory object which I have used to pass as a parameter to the function ThreadFunction.
The fifth parameter is used to specify how you want the thread to behave. For example you may set the thread priority of a particular thread to a higher value by specifying THREAD_PRIORITY_ABOVE_NORMAL to this parameter. This is a bit field where several options can be OR-ed.
The CreateThread function returns the thread identification value in the sixth and the final parameter.
The StartCopy function returns the handle of the thread which you can destroy using the CloseHandle Win32 API call after the thread is successfully created. Destroying this handle does not destroy the thread. This merely frees the handle associated with that thread.
The CreateThread function returns immediately after it creates the thread. The newly created thread runs as an independent thread.
As you have noticed that we have passed a pointer the function ThreadFunction to the third parameter of CreateThread function, let us see what it looks like…

'The thread entry point
Public Function ThreadFunction(ByVal Param As IUnknown) As Long
    Dim clsObject As clsExCopy
    ' Free threaded approach
    Set clsObject = Param
End Function

Pretty small, isn't it? ThreadFunction is used to call a method (ExCopy in this case) from the clsExCopy class. This function accepts parameters as IUnknown which represents the basic interface supported by Visual Basic COM layer. The value from this parameter is copied to a clsExCopy class object. Remember that once you go out of this function, the thread is destroyed.
This is all about creating multiple threads in Visual Basic. Remember to create the project as 'ActiveX EXE'. The rest of the example code deals with form controls and the copy technique (in clsExCopy). This way of creating a thread is called 'Free Threading', which is not supported by Visual Basic COM/DCOM layer. Visual Basic COM/DCOM uses either 'Single Threading' model or 'Apartment model' threading. But free threading models are very fast compared to Apartment model threading which uses marshalling technique to transport data between two objects. Anyway we will discuss about this approach later.

Article by:
Samit Ray
Price Waterhouse Associates (P) Ltd.