The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
3 Code Libraries &nbsp

Multi-Threaded VB Using ActiveX EXEs

Run code asynchronously with this simple and elegant design model

NOTE: this code has been superceded by the version at the new site.

ActiveX EXE Multi-Threading

Download Code
VB5 code Download the Multi-Threaded EXE project (19kb)

&nbsp Before you Begin &nbsp
&nbsp Make sure you register AsyncStartDemo.EXE in the zip before running the main project. &nbsp
&nbsp This project requires the Runnable Type Library (Runnable.TLB). Make sure you have loaded and registered this before trying the project. &nbsp


Ever since VB5 service pack 2 was released, there has been the possibility of running VB applications multi-threaded by using ActiveX EXEs. However, if you try and research this you will find it is fiddly to get working. I've never even begun to understand how to get it working: you need to use CreateObject to make the object on a new thread, and then you need to be very careful as to how you start the work you want to do asynchronously. Most times you find that VB blocks until the method call is complete regardless of whether the object is in a new thread or not.

What you want to be able to do is to say I want to perform some operation asynchronously and I would like to be notified when it is complete. This project demonstrates a tiny code module and a type library you can add into your own ActiveX projects to do just that. It is based around a part of the CodeFlow sample released at the VB Owner's Area.

Asynchronous Running - Just to get it Started
The main problem with getting a multi-threaded application up and running is how to call a method in VB without the caller being blocked. It turns out there is a simple solution to this problem. The steps are as follows:
  1. Put the method call into an ActiveX EXE, and make it private to the EXE.
  2. Create a new method to call. On calling this method, enable a timer. When the timer fires, call the private method.
This solves the problem because the method you call immediately yields control back to the caller, and then it is left to Windows pre-emptive multi-tasking to raise the timer event and kick off the process within the ActiveX EXE. There is no further interference because the ActiveX EXE is running in a different process to the caller.

In More Detail
To stop having to have a form in the ActiveX EXE, this solution is based on a Win32 API timer. Win32 API timers come in two flavours: either they notify the application when they tick by posting a WM_TIMER message to a window, or they fire a callback interface. This solution uses the callback interface, and as a consequence must be implemented within a module (because VB will not provide the address of a function to callback to for a function within an object, only one in a module).

Because the code to start the object is implemented in a module, the module must have a reference to the object instance it has to start when the timer fires. To achieve this without the possibility for errors, an interface is defined that the object can implement and the module will only use this for communication. In this sample, the interface is defined in a Type Library called Runnable. This allows you to reference the Type Library without having to declare it as a public class from the ActiveX executable.

The final implementation feature is the use of the OLE/COM API call CoLockObjectExternal to ensure that the object being started asynchronously isn't inadvertently terminated by the caller before the timer has had a chance to be fired.

Here is the code in the mStart.bas module:

' To prevent object going out of scope whilst the timer fires:
Private Declare Function CoLockObjectExternal Lib "ole32" ( _
ByVal pUnk As IUnknown, ByVal fLock As Long, _
ByVal fLastUnlockReleases As Long) As Long

' Timer API:
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, _
ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) _
As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, _
ByVal nIDEvent As Long) As Long

' Collection of Runnable items to start:
Private m_colRunnables As Collection
' The ID of our API Timer:
Private m_lTimerID As Long

Private Sub TimerProc(ByVal lHwnd As Long, ByVal lMsg As Long, _
ByVal lTimerID As Long, ByVal lTime As Long)
Dim this As Runnable
' Enumerate through the collection, firing the
' Runnable_Start method for each item in it and
' releasing our extra lock on the object:
With m_colRunnables
Do While .Count > 0
Set this = .Item(1)
.Remove 1
'Ask the system to release its lock on the object
CoLockObjectExternal this, 0, 1
End With
' Remove the timer:
KillTimer 0, lTimerID
m_lTimerID = 0
End Sub

Public Sub Start(this As Runnable)
' Ask the system to lock the object so that
' it will still perform its work even if it
' is released
CoLockObjectExternal this, 1, 1
' Add this to runnables:
If m_colRunnables Is Nothing Then
Set m_colRunnables = New Collection
End If
m_colRunnables.Add this
' Create a timer to start running the object:
If Not m_lTimerID Then
m_lTimerID = SetTimer(0, 0, 1, AddressOf TimerProc)
End If
End Sub

In Use
To create your own multi-threaded object, start a new ActiveX EXE project. Make a reference to Runnable.TLB and add mStart.bas as discussed above. In your ActiveX EXE's class, implement the Runnable interface: this only has one method called Runnable_Start which is fired when the timer starts the object. Now provide a public method to allow your user to set the object running, and in this method call:

mStart.Start Me

The sample in the download demonstrates how to use the technique in a very simple object. This object does nothing more than sleep for 2s but demonstrates using an event interface to allow the ActiveX EXE object to be cancelled.

TopBack to top
Source Code - What We're About!Back to Library Source Code
Source Code - What We're About!Back to Source Code


AboutContributeSend FeedbackPrivacy

Copyright 2000, Steve McMahon ( All Rights Reserved.
Last updated: 1 January 2000