Vous êtes sur la page 1sur 6

Creating a Multithreaded Test Application

In order to test and debug in-process components (.dll and .ocx files), you will need a
multithreaded client application. The steps to create a simple multithreaded application are as
follows:
1. pen a new !cti"e# $#$ pro%ect, and name the default class module &ain!pp. 'et the
Instancing property for &ain!pp to (ublic)ot*reatable. ! &ain!pp ob%ect will occupy the
application+s first thread, and display the main user interface.

,. n the -eneral tab of the (ro%ect (roperties dialog box, select 'ub &ain in the 'tartup
b%ect box, select Thread per b%ect in the Threading &odel box, and enter a uni.ue
"alue for the (ro%ect name. ((ro%ect )ame determines the type library name/ problems
may arise if two applications ha"e the same type library name.) 0Thread1emo0 is the
pro%ect name used in the example below. n the *omponent tab, select 'tandalone in the
'tart &ode box.

2. !dd a form, name it frm(rocess, and set the 3isible and *ontrol4ox properties to 5alse.
This form functions as a hidden window, which 'ub &ain will use to identify the main
thread for the process. )o code is needed for this form.

6. !dd a standard module to the pro%ect. In this module, place the declarations, the 'ub
&ain procedure, and the $numThread7nd&ain procedure shown below. !s explained in
the accompanying text and code comments, 'ub &ain will execute when your application
starts, and e"ery time you create a new thread. The 'ub &ain sample code demonstrates
how to identify the first thread, so that you 8now when to create &ain!pp.

9. !dd a form and name it frm&T&ain. This form pro"ides the main user interface for the
test application. !dd to it the single declaration and the 5orm:;nload e"ent immediately
abo"e the heading 0&ultiple Instances of the Test !pplication.0

<. In the *lass:Initiali=e e"ent procedure for &ain!pp, add code to show frm&T&ain. *ode
to do this is pro"ided below.

>. To create additional test threads, you must ha"e at least one class in your pro%ect whose
Instancing property is set to &ulti;se. !dd a class module and form and insert the code
pro"ided under the heading 0*reating )ew Threads.0 4ecause you selected Thread per
b%ect for this pro%ect, e"ery public ob%ect that is externally created will start on a new
thread. This means that you can create a new thread by using the *reateb%ect function
to create an instance of your &ulti;se class from its programmatic I1 ((rogI1), as
discussed in the accompanying text.

?. !dd code to frm&T&ain to create new threads by creating instances of the &ulti;se
classes you defined. In the example below see the code under the heading 0*reating )ew
Threads.0

@. The de"elopment en"ironment does not support multithreading. If you press 59 to run the
pro%ect, all ob%ects will be created on the same thread. In order to test multithreaded
beha"ior, you must compile the !cti"e# $#$ pro%ect, and run the resulting executable.
Important To ensure that each new &ulti;se ob%ect starts a new thread, you must use
Thread per b%ect rather than Thread (ool.
Determining the Main Thread During Sub Main
'ub &ain executes for e"ery new thread. The reason for this is that 3isual 4asic maintains a
Page 1 of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''
separate copy of your global data for each thread (each apartment). In order to initiali=e global
data for the thread, 'ub &ain must execute. This means that if your 'ub &ain loads a hidden
form, or displays your application+s main user interface, new copies of those forms will be
loaded for e"ery new thread you create.
The following code determines whether or not 'ub &ain is executing in the first thread, so that
you can load the hidden form only once and display the test application+s main user interface
only once.
' Root value for hidden window caption
Public Const PROC_CAPTION = "ApartmentDemoProcessWindow"

Public Const ERR_InternalStartup = &H600
Public Const ERR_NoAutomation = &H601

Public Const ENUM_STOP = 0
Public Const ENUM_CONTINUE = 1

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function GetWindowThreadProcessId Lib "user32"_
(ByVal hwnd As Long, lpdwProcessId As Long) As Long
Declare Function EnumThreadWindows Lib "user32" _
(ByVal dwThreadId As Long, ByVal lpfn As Long, ByVal lParam As Long) _
As Long

' Window handle retrieved by EnumThreadWindows.
Private mhwndVB As Long
' Hidden form used to identify main thread.
Private mfrmProcess As New frmProcess
' Process ID.
Private mlngProcessID As Long

Sub Main()
Dim ma As MainApp

' Borrow a window handle to use to obtain the process
' ID (see EnumThreadWndMain call-back, below).
Call EnumThreadWindows(App.ThreadID, AddressOf EnumThreadWndMain, 0&)
If mhwndVB = 0 Then
Err.Raise ERR_InternalStartup + vbObjectError, , _
"Internal error starting thread"
Else
Call GetWindowThreadProcessId(mhwndVB, mlngProcessID)
' The process ID makes the hidden window caption unique.
If 0 = FindWindow(vbNullString, PROC_CAPTION & CStr(mlngProcessID)) Then
' The window wasn't found, so this is the first thread.
If App.StartMode = vbSModeStandalone Then
' Create hidden form with unique caption.
mfrmProcess.Caption = PROC_CAPTION & CStr(mlngProcessID)
' The Initialize event of MainApp (Instancing =
' PublicNotCreatable) shows the main user interface.
Set ma = New MainApp
' (Application shutdown is simpler if there is no
' global reference to MainApp; instead, MainApp
' should pass Me to the main user form, so that
' the form keeps MainApp from terminating.)
Else
Err.Raise ERR_NoAutomation + vbObjectError, , _
"Application can't be started with Automation"
End If
End If
End If
End Sub

' Call-back function used by EnumThreadWindows.
Public Function EnumThreadWndMain(ByVal hwnd As Long, ByVal _
lParam As Long) As Long
' Save the window handle.
mhwndVB = hwnd
' The first window is the only one required.
' Stop the iteration as soon as a window has been found.
EnumThreadWndMain = ENUM_STOP
End Function

' MainApp calls this Sub in its Terminate event;
' otherwise the hidden form will keep the
' application from closing.
Public Sub FreeProcessWindow()
Unload mfrmProcess
Set mfrmProcess = Nothing
Page 2 of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''
End Sub
Note This techni.ue for identifying the first thread may not wor8 in future "ersions of
3isual 4asic.
)otice that 'ub &ain ta8es no action for any thread after the first. 7hen you add code that
creates &ulti;se ob%ects (in order to start subse.uent threads) be sure to include code to
initiali=e those ob%ects.
$numThread7indows is used with a call-bac8, $numThread7nd&ain, to locate one of the
hidden windows 3isual 4asic creates for its internal use. The window handle of this hidden
window is passed to -et7indowThread(rocessId, which returns the process I1. The process I1
is then used to create a uni.ue caption for the hidden window (frm(rocess) that 'ub &ain
loads. 'ubse.uent threads detect this window, and thus can tell that they don+t need to create
the &ain!pp ob%ect. These gyrations are necessary because 3isual 4asic does not pro"ide a way
to identify the application+s main thread.
The &ain!pp class, in its Initiali=e e"ent, displays the test application+s main form. &ain!pp
should pass its &e reference to the main form, so that the form 8eeps &ain!pp from
terminating. 5rom the main user interface you can create all subse.uent threads. 'etting the
Instancing property for &ain!pp to (ublic)ot*reatable helps you a"oid displaying two main user
interface forms.
! simple example of a &ain!pp class and its associated form (steps 9 and <, abo"e) might loo8
li8e this:
' Code for a MainApp class.
Private mfrmMTMain As New frmMTMain

Private Sub Class_Initialize()
Set mfrmMTMain.MainApp = Me
mfrmMTMain.Caption = mfrmMTMain.Caption & " (" & App.ThreadID & ")"
mfrmMTMain.Show
End Sub

Friend Sub Closing()
Set mfrmMTMain = Nothing
End Sub

Private Sub Class_Terminate()
' Clean up the hidden window.
Call FreeProcessWindow
End Sub

' Code for the form frmMTMain.
Public MainApp As MainApp

Private Sub Form_Unload(Cancel As Integer)
Call MainApp.Closing
Set MainApp = Nothing
End Sub
Multiple Instances of the Test Application
Including the process I1 in the hidden window caption allows multiple instances of the test
application to run without interfering with each other.
7hen you call *reateb%ect, the instance of the public class you create will be on a thread in
the current application instance. This is because *reateb%ect always attempts to create an
ob%ect in the current application before loo8ing for other running $xe components that might
supply the ob%ect.
Useful Properties for the Apartment
Aou may find it useful to expose the process I1 as a read-only property of the module that
contains 'ub &ain:
Page ( of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''
'This code not required for the test application
Public Property Get ProcessID() As Long
ProcessID = mlngProcessID
End Property
This allows any ob%ect on the thread to get the process I1 by calling the un.ualified (rocessI1
property. Aou may also find it useful to expose a 4oolean Is&ainThread property in this fashion.
Creating New Threads
The Thread per b%ect option causes e"ery public ob%ect that is externally created - that is,
created using the *reateb%ect function - to start on a new thread. To create a new thread,
simply use the programmatic I1 ((rogI1) of one of your &ulti;se classes:
'This code not included in the test application
Dim tw As ThreadedWindow
Set tw = CreateObject("ThreadDemo.ThreadedWindow")
The "ariable tw now contains a reference to an ob%ect on a new thread. !ll calls to the
properties and methods of this ob%ect that are made using tw will be sub%ect to the extra
o"erhead of cross-thread marshaling.
Note !n ob%ect created with the )ew operator is not created on a new thread. It resides on
the same thread where the )ew operator was executed. 'ee 01esigning &ultithreaded ut-
of-(rocess *omponents0 and 0Bow b%ect *reation 7or8s in 3isual 4asic *omponents,.0
To ensure that &ain!pp doesn+t terminate until all of the other threads are finished, you can
gi"e each public class a &ain!pp property. 7hen an ob%ect creates a &ulti;se ob%ect on a new
thread, it can pass the new ob%ect a reference to the &ain!pp ob%ect as part of the initiali=ation
process. (Aou can also pass &ain!pp a reference to the new ob%ect, so that &ain!pp has a
collection of references to all ob%ects that control threads/ howe"er, remember that this will
create circular references. 'ee 01ealing with *ircular Ceferences.0)
If you want a class that controls a thread to show a form, you should pro"ide it with an
Initiali=e method (not to be confused with the Initiali=e e"ent) or a 'how method that displays
the form. 1on+t show the form in the *lass:Initiali=e e"ent procedure, as this could cause
timing errors when you create instances of the class. In a "ery simple case, the code for a
&ulti;se Threaded7indow class and its form, frmThreaded7indow, might loo8 li8e this:
'Code for a MultiUse ThreadedWindow class.
Private mMainApp As MainApp
Private mfrm As New frmThreadedWindow

Public Sub Initialize(ByVal ma As MainApp)
Set mMainApp = ma
Set mfrm.ThreadedWindow = Me
mfrm.Caption = mfrm.Caption & " (" & App.ThreadID & ")"
mfrm.Show
End Sub

Friend Sub Closing()
Set mfrm = Nothing
End Sub

'Code for the form frmThreadedWindow.
Public ThreadedWindow As ThreadedWindow

Private Sub Form_Unload(Cancel As Integer)
Call ThreadedWindow.Closing
Set ThreadedWindow = Nothing
End Sub

The following code snippet shows how you might initiali=e the Threaded7indow ob%ect:
'Code for the test application's main form (frmMTMain).
Private Sub mnuFileNewTW_Click()
Dim tw As ThreadedWindow
Page ) of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''
Set tw = CreateObject("ThreadDemo.ThreadedWindow")
' Tell the new object to show its form, and
' pass it a reference to the main
' application object.
Call tw.Initialize(Me.MainApp)
End Sub
If you ha"e a number of classes that can control threads, you can ma8e your code more generic
by defining an I!partment interface to contain the Initiali=e method. 7hen you implement
I!partment in each class, you can pro"ide the appropriate Initiali=e method code for that class.
Aour thread creation code might loo8 li8e this:
'This code not required for the test application
Private Sub mnuFileNewObject_Click(Index As Integer)
Dim iapt As IApartment
Select Case Index
Case otThreadedWindow
Set iapt = CreateObject("ThreadDemo.ThreadedWindow")
' (other cases...)
End Select
' Common code to initialize objects.
Call iapt.Initialize(MainApp)
End Sub
Note Aou can ma8e an I#xxx!partment interface that+s 8nown only to the multithreaded
application by defining the interface in a separate type library. In the !cti"e# $xe pro%ect,
set a reference to the type library.
Keeping eferences to Threaded !b"ects
To ensure proper shutdown of a multithreaded application, you must 8eep careful trac8 of all
references to the &ulti;se ob%ects you use to create and control threads.
1efine your ob%ect lifetime goals clearly. 5or example, consider the case of a &ulti;se ob%ect
that shows a form. The easiest way to manage ob%ect lifetime is to ha"e the ob%ect pass the
form a &e reference/ the form then 8eeps the ob%ect ali"e. 7hen the user closes the form, the
form+s ;nload e"ent must set all references to the &ulti;se ob%ect to )othing, so that the ob%ect
can terminate and in turn clean up its reference to the form. (Aou may find it useful to gi"e the
&ulti;se ob%ect a 5riend method that cleans up the reference to the form, and all other internal
ob%ect references/ the form+s ;nload e"ent can call this method.)
If the ob%ect that controls a thread creates additional ob%ects on the thread, using the )ew
operator, ma8e sure you clean up references to those ob%ects. The thread cannot close until all
references to ob%ects that were created on the thread ha"e been released. pen threads
consume system resources.
#riend Methods Cannot $e Used Cross%Thread
4ecause 5riend properties and methods are not part of the public interface of a class, you
cannot call them from another thread. *ross-thread calls between ob%ects are limited to
properties and methods that are declared (ublic.
eentranc&
If a method of an ob%ect yields by calling 1o$"ents, showing a modal form, or ma8ing a
secondary call to an ob%ect on another thread, then the code in the method can be entered by a
second caller before the first call completes. If such a method uses or changes property "alues
or module-le"el "ariables, this could result in an in"alid internal state for the ob%ect. To protect
against reentrancy, you can:
!"oid yielding.

Deep a module-le"el 4oolean flag for each method. 7hen a method begins, it tests this
Page * of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''
flag to determine whether a method call is in progress. If not, the method sets the flag to
True and continues/ otherwise it raises an error. Aou must be careful to turn this flag off
when the method completes or exits for any reason.

7rite reentrant methods - that is, methods that don+t depend on module-le"el data.
As&nchronous Tas's
3isual 4asic doesn+t pro"ide a way to for8 execution - that is, to ha"e one thread initiate a
method call on a new thread and immediately resume processing on the original thread. Aou
can simulate this beha"ior in your test application by ha"ing the original method call turn on a
timer and then return immediately. 7hen the timer e"ent occurs, you can turn the timer off
and perform the asynchronous processing. This techni.ue is discussed in 0!synchronous *all-
4ac8s and $"ents0, and is demonstrated (see 0*reating an !cti"e# $xe *omponent0) and in the
*offee sample application.
Using the Multithreaded Test Application
Aou must compile the multithreaded test application in order to test your apartment-threaded
component, because the 3isual 4asic de"elopment en"ironment does not currently support
multiple threads of execution. If you ha"e 3isual 'tudio, you may find it useful to compile the
test application with debugging information for nati"e code debugging, so that you can use the
3isual 'tudio debugger.
Page 6 of 6 Creating a Multithreaded Test Application
18/12/2012 mk:M!"T!tore:C:#Archi$os%20de%20programa#Microsoft%20&isual%20!tudio#'''

Vous aimerez peut-être aussi