Multithreaded connectable objects may be the cause of several problems to clients incapable of multithreaded operations. This paper examines how to overcome those problems when proper inter-thread marshalling may not be used

 

Using Multithreaded Connectable Objects in VB6

 

Nicola Di Nisio(*)

 

Connectable objects are COM objects that are able to perform callbacks towards their clients, via known outgoing interfaces. Callback interfaces are said outgoing interfaces because they come out from the clients of an object and are used by the object, just the opposite of the usual relation between a client and the server object:

 


 


In the picture above we can see the relation between an object MtCom and its client. The client uses the object via its interface IMtCom, but at the same time the client implements the interface _IMtCom, specified by the type library of the component MtCom, and gives a pointer to that interface to the object.

_IMtCom is an outgoing interface, a callback interface for the object to the client.

 

Connectable Objects in VB

In the Visual Basic terminology the methods of the interface _IMtCom are said to be events of the objects. Lightnings in the Visual Basic object browser represent the events of an object.

An object with events should be declared by using the keyword WithEvents and instantiated later by means of CreateObject(), or the New operator. Here is an example of how a client VB application should declare and instantiate the MtCom object:

 

Dim WithEvents MtCom As MtCom

 

Private Sub Form_Load()

    Set MtCom = CreateObject("DNN.MtCom")

End Sub

 

Each time the object MtCom fires an event a handling subroutine is executed. The name of that handling routine is the juxtaposition of the name of the firing object and the name of the fired event, separated by an underscore.

VB6 builds the outgoing interface on the fly and in correspondence of an event firing, it searches for the proper handling routine to execute it. If it does not find, it raises an access violation.

The check for the existence of all the expected handling routines is not performed at compile-time, hence be careful when typing the name of the handling routines. Alternatively let the VB IDE generate them, by selecting them on the right combo-box on top of the editor, where you can find the list of events of the object selected in the left combo-box.

 

A single-thread issue

If MtCom is an in-proc component, it is created in the same thread of the creator, thus if MtCom is single threaded it may not cause any race condition. This assertion remains valid even if it is an out-of-proc component, until it is single threaded, because in COM all method calls are synchronous, that is the caller thread is blocked until the method call returns.

The observations above lead to a minor issue shown in the following fragment of VB code. Suppose that IMtCom has a method DoSomething() and that there is an event OnDoSomething() in the outgoing interface _IMtCom to report progress information for DoSomething(), with the following signature

 

OnDoSomething(Progress As Long, Message As String)

 

The event returns a Progress information, and a string message. We may think to use the progress information to update a progress bar and to show the message in a list box, like in the following code

 

Private Sub MtCom_OnDoSomething(ByVal Progress As Long, ByVal Message As String)

    ProgressBar1.Value = Progress

    List1.AddItem Message

End Sub

 

Suppose that the object MtCom, when we call DoSomething(), fires three times that event, with progress information equal to 1, 2 and 3 and the corresponding messages are “1”, “2” and “3”. Suppose also that those events are fired at a rate of one per second. Well, you will see all those information in your listbox only after the last event is fired, after three seconds, and you will see your progress bar stay still for three seconds and only after the third event going to 100%, without intermediate progresses. This happened because the form and its components were not refreshed so you didn’t see anything. They were not refreshed, because the main thread of the application was blocked on the IMtCom::DoSomething() method call.

To overcome this trouble you have to explicitly refresh the visual components during the update of their data

 

Private Sub MtCom_OnDoSomething(ByVal Progress As Long, ByVal Message As String)

    ProgressBar1.Value = Progress

    List1.AddItem Message

    ProgressBar1.Refresh

    List1.Refresh

End Sub

 

A multi-thread issue

Multithreaded connectable objects propose another scenario, at least by their asynchronous methods.

In COM all method calls are synchronous, they block the calling thread until they return. You may implement asynchronous method call, by letting the method start another thread that performs the operations, in order to return the control to the caller before the completion of the operations in the second thread. In COM+ we will be able to implement an asynchronous method in a declarative fashion, without explicitly start another thread.

Suppose to have a DoSomethingAsych() method in IMtCom that performs the same operations of DoSomething(), but asynchronously, then we may think that the refresh problem on the components of the form should no more exist. In fact the main thread of the application is not blocked on the call and it may freely repaint the form and its components while the events coming from MtCom add new messages and progress information on our form. Well our picture is still not so good, because another trouble happens. A COM golden rule is that it is not allowed for unmarshalled interfaces to be passed across thread boundaries. In our case this means that the _ImtCom interface must be marshalled inside each thread firing events of this interface, otherwise access violations could arise, or even worse, race conditions. In fact this rule exists to protect clients uncapable to perform multi-threaded operations and clients compiled from VB programs are among them.

To do this could be simple, tough boring, when we code all component by hand in C++, but it could become impractical when we use ATL 3.0 to code our multithreaded connectable object. In fact when the client registers himself to the server calls IconnectionPoint::Advise() in its thread of execution. There a pointer to the outgoing interface is stored into m_vec  and from that same array is retrieved and used in each Fire_ method. In this case you should marshall the incoming interface pointers in Advise() and then unmarshall them in each Fire_ method. This requires modifications to the wizard-generated code in the Fire_ methods (a wizard-generated comments reminds you that any change to that code may be lost in future regeneration of the same code, due for example to interface change…) and worse changes to the ATL code (this same topic is discussed in the paper Dr. GUI and COM Events, Part 2, at http://msdn.microsoft.com/).

Until a new version of ATL will solve this problem, we have two choices

 

 

The first solution is obvious and tedious, but works fine. It is the most cost-effective when you have many clients to code and what to keep them simple.

The second solution might be more practical, but it is dangerous. You have to modify the ATL source installed for you by Visual Studio and this may affect other components you maintain with that installation of Visual Studio. I do not like this, especially in a production environment.

The last solution is not a solution, but a workaround, that is cost-effective only when you have few clients to code that use your multithreaded object. We are going to analyze this workaround.

 

Queuing messages

This is a quick-and-dirty answer to the multi-thread issue.

When an event takes some information that have to be added in thread-unsafe structure shared with a controlling thread, we have to queue those information somewhere an let the controlling thread add those information for us. This is the case of our listbox, let’s take a look to the solution. First of all we need of a thread-safe queue and for the purpose of this article a thread-safe queue of variants was developed. It has two methods:

 

Sub Add(ByVal v As Variant)

Function Pop(ByRef v As Variant) As Long

 

The first method adds a variant value to the queue, while the second pops the older value in the queue and return True if there is still another item in the queue, False otherwise.

The PROGID of this queue is DNN.TSVariantQueue and implemented by the TSVariantQueue.dll, which accompanies this paper.

The item queued are read by the handler of a timer running in the form, hence running under the controlling thread of the components on the form, and from that handler are placed in the proper component. The timer interval should be small enough for us not to register delays, but not smaller, to avoid overheads.

Status information, instead, do not need to be queued, they can be simply stored in private attribute of the form  and read in by the handle routine of the timer. An example of such information is the progress value.

Supposing to have an event OnDoSomethingAsynch() fired by the method DoSomethingAsynch() and a timer named Timer1, with the relative handler routine, the whole code to handle our events coming from an asynchronous method should look like the following:

 

Dim WithEvents MtCom As MtCom

Dim TsqMessages As New TSVariantQueue

Private ProgressAsynch As Integer

 

Private Sub Form_Load()

    Set MtCom = CreateObject("DNN.MtCom")

    ProgressAsynch = 0

End Sub

 

Private Sub MtCom_OnDoSomethingAsynch(ByVal Progress As Long, ByVal Message As String)

    ProgressAsynch = Progress

    TsqMessages.Add Message

End Sub

 

Private Sub Timer1_Timer()

    Dim Message As Variant

   

    ProgressBar1.Value = ProgressAsynch

 

    While TsqMessages.Pop(Message)

        List1.AddItem Message

    Wend

End Sub

 

The final demo

To conclude let’s run this all on real code. Two COM components and a VB6 client application accompany this paper.

The first component is the aforementioned thread safe queue TSVariantQueue, in the TSVariantQueue.dll.

The second component is the component MtCom component shown at the beginning of the paper, implementing the interface IMtCom and defining the outgoing interface for its client _IMtCom. The MtCom.dll implements it.

You must register those components to use them by executing the following command in a command shell open on the directory where you placed the dll

 

RegSvr32 TSVariantQueue.dll

RegSvr32 MtCom.dll

 

Here is the VB-like complete definition of the interfaces IMtCom and _IMtCom:

 

IMtCom

     Sub DoSomething()

     Sub DoSomethingAsynch()

 

_IMtCom

     OnDoSomething(Progress As Long, Message As String)

     OnDoSomethingAsynch(Progress As Long, Message As String)

 

DoSomething() is a synchronous method that fires three times the event OnDoSomething() with varying values for Progress (1, 2 and 3) and Message (“1”, “2” and “3”). The events are fired at a rate of one per second. DoSomethingAsynch() does the same but asynchronously.

The visual basic application’s form looks like the following:

 


 

 


The button “DoSomething” calls the method DoSomething(), while the button “DoSomethingAsynch” calls the DoSomethingAsynch(). At the center of the form there is the listbox where the messages are written into and at the bottom there is the progress bar.

By the check box on the top right you can choose whether or not to use the queue. There is a timer on the form, with 200 ms of timer interval named Timer1 that reads the queue and writes messages and progress information when you choose to use the queue.

Here is the code of this Visual Basic application:

 

Option Explicit

 

Dim WithEvents MtCom As MtCom

Dim TsqMessages As New TSVariantQueue

Private ProgressAsynch As Integer

 

Private Sub DoSomethingCommand_Click()

    List1.Clear

    ProgressAsynch = 0

    ProgressBar1.Value = 0

    ProgressBar1.Refresh

    MtCom.DoSomething

End Sub

 

Private Sub DoSomethingAsynchCommand_Click()

    List1.Clear

    ProgressAsynch = 0

    ProgressBar1.Value = 0

    MtCom.DoSomethingAsynch

End Sub

 

Private Sub Form_Load()

    Set MtCom = CreateObject("DNN.MtCom")

    ProgressAsynch = 0

End Sub

 

Private Sub MtCom_OnDoSomething(ByVal Progress As Long, ByVal Message As String)

    If (1 = UseQueueCheck.Value) Then

        ProgressAsynch = Progress

        TsqMessages.Add Message

    Else

        ProgressBar1.Value = Progress

        List1.AddItem Message

        ProgressBar1.Refresh

        List1.Refresh

    End If

End Sub

 

Private Sub MtCom_OnDoSomethingAsynch(ByVal Progress As Long, ByVal Message As String)

    If (1 = UseQueueCheck.Value) Then

        ProgressAsynch = Progress

        TsqMessages.Add Message

    Else

        ProgressBar1.Value = Progress

        List1.AddItem Message

    End If

End Sub

 

Private Sub Timer1_Timer()

    Dim Message As Variant

   

    If (ProgressAsynch <> 0) Then

        ProgressBar1.Value = ProgressAsynch

    End If

   

    While TsqMessages.Pop(Message)

        List1.AddItem Message

    Wend

End Sub

 

As you can see the check box is thread safe as regard the reading of its value. Usually most of the operations you do with components into your event handler routines may lead to errors, because of interactions with their controlling thread, and also other apparently harmless operations, like the string concatenation (e.g. “aa” & “bb”), are source of trouble. Only the experience may guide you in what you can and cannot do in you event handler routines for events fired by asynchronous methods.

Here is the matrix of errors or problems for that application. The access violation comes out only in the executable version of the application, not when running in the VB environment:

 

 

Use Queue

Do Not Use Queue

DoSomething

Lack of refresh

Ok (with forced Refresh)

DoSomethingAsynch

Ok

Access Violation

 

The “Lack of refresh” for the DoSomething() method when using the queue is due to the fact that the queue is read by the timer, the timer is controlled by the main thread of the application and this thread is blocked on the synchronous call to DoSomething(). Only at the return from DoSomething() the timer can read the queue and the last progress value: all the three messages are written together and the progress bar goes from 0 to 3 in a single leap.

 

Conclusions

VB6 is not a completely thread safe environment, some parts of it are not. Furthermore the components we use in our VB applications may not be thread safe, both the ones shipped with VB and third party ones.

Errors may arise when we use those thread-unsafe parts in events handler for events fired by asynchronous methods, when they are not meant to deal with thread-unsafe clients. We have to test all such event handlers against such trouble and this test is to be performed out of the VB environment, because at least in VB6 they usually do not come out when we run the application in the VB IDE. Run the executable instead.

When we discover components, or the part of them, that are not thread-safe, we have to use a timer to let the controlling thread update the interested properties or call the interested methods. Private attributes have to be used to store status information and thread-safe queues to store sequences of messages.

The timer interval should be smaller enough to let not perceive any delay in the information flow, but not smaller, in order to avoid overheads.

The problems investigated in this paper may appear in any environment using multithreaded (and not properly coded) connectable objects, not only VB, and the solution here exposed may be used in all such situations, being based on a queue that is a COM object and on a timer.

 



(*) The author has got a degree in Computer Science and is a Microsoft Certified Professional. Works as software developer for Dataspazio S.p.A. in Roma, Italy. His homepage is at www.dinisio.net/nicola