Microsoft KB Archive/172986
Article ID: 172986
Article Last Modified on 11/18/2003
- Microsoft Visual Basic 5.0 Learning Edition
- Microsoft Visual Basic 5.0 Professional Edition
- Microsoft Visual Basic 5.0 Enterprise Edition
This article was previously published under Q172986
This document is copied from VB5SP2.htm that is placed in the Visual Basic 5.0 directory on your machine when you install Visual Basic 5.0 Service Pack 2. Visual Basic 5.0 Service Pack 2 is an update to Visual Basic Professional, Enterprise, and Control Creation editions.
Microsoft Visual Basic 5.0 Service Pack 2 Release Notes
Visual Basic 5.0 Service Pack 2 is an update to Visual Basic Professional, Enterprise, and Control Creation editions. It enables apartment-model threading for Visual Basic projects that contain user interface elements, such as forms and controls. It also fixes several bugs that have been reported in Visual Basic 5.0. (Search for VB5FixListSP2 on the Knowledge Base Web site at: http://msdn.microsoft.com/support.)
Applying Visual Basic 5.0 Service Pack 2 to your licensed copy of Visual Basic allows you to create ActiveX controls and ActiveX documents that perform well in multithreaded clients like Internet Explorer 4.0. If you have the Professional or Enterprise Edition of Visual Basic, you can also create:
- Apartment-threaded DLLs that show forms.
- Multithreaded components that employ ActiveX designers, such as the UserConnection designer.
- Multithreaded client applications to test these components.
This apartment-model threading enhancement is the only feature of Visual Basic 5.0 Service Pack 2.
These release notes contain information supplementing the Microsoft Visual Basic 5.0 documentation in Help and Books Online. For additional information, visit the Microsoft Visual Basic Web site at:
Overview of Changes to Apartment-Model Threading
Projects authored with Visual Basic 5.0 Service Pack 2 can take advantage of apartment-model threading without having to suppress visual elements such as forms and controls. Applying Visual Basic 5.0 Service Pack 2 makes Forms, UserControls, UserDocuments, and ActiveX Designers thread-safe:
- Apartment-threaded versions of all the ActiveX controls shipped with Visual Basic 5.0, except: DBGrid, MSChart, and any legacy controls that were included in the Tools directory for backward compatibility.
- An apartment-threaded version of the UserConnection ActiveX designer.
- A new Threading Model option that allows you to recompile your ActiveX control and ActiveX document projects as apartment-threaded .ocx and .dll files.
NOTE: The support code for ActiveX designers is now apartment-model thread- safe, but this does not guarantee that a particular designer is thread- safe. If you're using a third-party designer, you should check with your vendor.
Limitations of the Control Creation Edition, version 5.0
The only parts of this document that apply to the Control Creation Edition are those that affect thread-safe ActiveX control projects.
If you use the Control Creation Edition to create apartment-threaded ActiveX controls, you must use a multithreaded client such as Internet Explorer 4.0 to test the multithreaded behavior of your controls. You cannot use the Control Creation Edition to create a multithreaded test client.
Selecting a Threading Model for Your Project
Visual Basic 5.0 Service Pack 2 changes the options on the General tab of the Project Properties dialog box. After you apply Visual Basic 5.0 Service Pack 2, the Unattended Execution option is no longer coupled to the threading model for your project. You can select a setting for the Threading Model option without marking your project for Unattended Execution.
To set the threading model for an ActiveX DLL, ActiveX Exe, or ActiveX Control project:
- From the Project menu, click <project> Properties to open the Project Properties dialog box.
- On the General tab, select the desired options in the Threading Model box.
For ActiveX DLL and ActiveX Control projects, you can select either Apartment-Threaded or Single-Threaded.
For ActiveX Exe projects, you can either specify that each new object is created on a new thread (Thread per Object), or limit your component to a fixed pool of threads. A thread pool size of one (1) makes the project single-threaded; a larger thread pool makes the project apartment-threaded.
NOTE: When you change the threading model for an existing project, an error occurs if the project uses single-threaded ActiveX controls. Visual Basic prevents the use of single-threaded controls in apartment-threaded projects, as described in Converting Existing Projects.
NOTE: When you specify Thread per Object (or a thread pool greater than one) for an ActiveX Exe project, only externally-created objects are created on new threads. (See "Designing Multithreaded Out-of-Process Components" in Books Online.) Thread per Object and Thread Pool are not available for ActiveX DLL and ActiveX Control projects, because thread creation is controlled by the client application.
IMPORTANT: For Professional and Enterprise Edition users, the consequences of selecting Thread per Object or Thread Pool are discussed in detail in "Designing Multithreaded Out-of-Process Components" in Books Online.
Visual Basic 5.0 Service Pack 2 and Unattended Execution
Visual Basic 5.0 Service Pack 2 does not change the functioning of the Unattended Execution option. Unattended Execution still allows you to create components that can run without operator intervention on network servers. The only change made by Visual Basic 5.0 Service Pack 2 is that selecting Unattended Execution doesn't affect the threading model of your component.
Limitations of the Apartment-Model Threading Feature
The following limitations apply to all editions of Visual Basic:
- The development environment doesn't support multithreaded debugging. If you have Microsoft Visual Studio, you can debug an apartment-threaded component with the Visual Studio debugger after compiling it to native code with debug information. You will also need a multithreaded client; MDI parent and child forms must share data in ways that are difficult to make thread-safe. Therefore, MDI forms are not allowed in apartment- threaded projects. On the Project menu, "Add MDIForm" is grayed out in apartment-threaded ActiveX DLL projects and in ActiveX Exe projects with Thread per Object selected (or with a thread pool larger than one). If you change the threading model to Apartment Threaded in a project that contains MDI forms, an error occurs.
- Single-threaded ActiveX controls can cause numerous problems in multi- threaded clients, in addition to performing poorly. Therefore, Visual Basic prevents the use of single-threaded controls in projects where Threading Model has been set to Apartment Threaded. For details, see Converting Existing Projects.
- Friend properties and methods can only be called by objects on the same thread. Because they are not part of an object's public interface, they cannot be marshaled between threads.
- ActiveX Documents in ActiveX Exe projects will not be apartment-model thread-safe unless you select Thread per Object or Thread Pool with a pool size greater than one.
- When a thread shows a form with vbModal, the form will be modal only to code and forms on that thread. Code running in other threads will not be blocked and forms shown by other threads will remain active.
- Drag-and-drop between forms and controls will work only if the drag source and the drag target are on the same thread. (OLE drag-and-drop, however, will work universally.)
- DDE between forms will work only if the forms are on the same thread.
Converting Existing Projects
Once you have applied Visual Basic 5.0 Service Pack 2, you can add apartment-threading to your existing projects by changing the Threading Model option, as described in Selecting a Threading Model for Your Project, and recompiling the project. For many projects, this is all you need to do.
If an existing ActiveX DLL, ActiveX EXE, or ActiveX control project uses single-threaded constituent controls, attempting to change Threading Model to Apartment Threaded will cause an error. Because of the number and severity of problems that single-threaded ActiveX controls cause for multithreaded clients, Visual Basic does not permit them to be used in ActiveX component projects. If you have an existing project that employs a single-threaded control, contact the vendor to see whether an apartment- threaded version is available.
Forcing the Use of Single-Threaded Controls
It is possible to trick Visual Basic into using a single-threaded control in an apartment-threaded project by manually editing the .vbp file. NOTE: Do not do this. The problems that single-threaded ActiveX controls can cause include:
- If users tab to a single-threaded control on a form that is running on a different thread, they will not be able to tab off the control. This occurs because the single-threaded control's thread has no context for the focus on the form's thread. The only way for the user to change the focus in this situation is to use the mouse. Using a single-threaded control as a constituent of an apartment-threaded control causes similar problems.
- In a multithreaded application, activating a form by clicking on a single-threaded control will fail if the form is on a different thread. This also occurs when clicking on a single-threaded constituent of an apartment-threaded control.
- Using a single-threaded OCX in a multithreaded application causes performance problems because all of the controls the OCX provides must run on the same thread. For controls on forms running in different threads, all communication requires expensive cross-thread calls. An apartment-threaded ActiveX control that uses single-threaded constituent controls will cause similar performance problems.
- In a multithreaded application, the TAB key and access keys (for example, ALT+A) will not work for single-threaded controls that are not on the application's main thread. An apartment-threaded ActiveX control that uses single-threaded constituent controls will experience similar problems.
- If your apartment-threaded control sets the Picture property of a single- threaded constituent control (or any other property that takes a Picture object), errors will occur in multi-threading clients because the Picture object cannot be marshaled between threads.
IMPORTANT: Single-threaded controls can cause these and other problems in any multithreaded component or application you build, using Visual Basic or any other development tool.
Design Considerations for ActiveX DLL and ActiveX Control Projects
Objects provided by in-process components are created on client threads; your DLL or OCX cannot create threads of its own. The first time a client thread creates an instance of an object provided by your component, a new apartment will be created for that thread and your Sub Main will execute for the new apartment. All of the objects your component provides for that client thread will reside in the same apartment.
If you have designed your component so that all the instances of a class share some common global data, that data will now be shared only by objects that happen to be on the same thread. If you have global code that accesses a database, that access will now occur separately on each thread.
For example, suppose that a multithreaded client shows one instance of your Widget control on a form on thread A and two more instances on a form on thread B. The control instances will be in two apartments, each with a copy of your global variables. Sub Main will execute once for each apartment. The two controls on thread B will share global state, separate from the global state of the control on thread A.
Other design considerations are discussed in Changes to "Apartment-Model Threading in Visual Basic" and Changes to "Designing Thread-Safe DLLs" in this article, and in the corresponding topics in Visual Basic 5.0 Books Online.
Design Considerations for ActiveX EXE Projects
The most important difference between single-threaded and apartment- threaded out-of-process components is that all global data (including the properties of the App object) is replicated for each thread. One of the implications of this is that Sub Main will be run every time a new thread is created.
Other design considerations for out-of-process components are discussed in Changes to "Apartment-Model Threading in Visual Basic" and Changes to "Designing Multithreaded Out-of-Process Components" in this document, and in the corresponding topics in Visual Basic 5.0 Books Online.
Changes to the Visual Basic 5.0 Documentation
The following topics describe specific changes to the indicated topics in Visual Basic 5.0 Books Online:
Changes to: "Apartment-Model Threading in Visual Basic"
Changes to: "Designing Thread-Safe DLLs"
Changes to: "Designing Multithreaded Out-of-Process Components"
If you have the Professional or Enterprise Edition of Visual Basic, you can locate these topics in Books Online using the Contents pane:
Component Tools Guide
Creating ActiveX Components Building Code Components Scalability and Multithreading
NOTE: The Books Online browser is not included in the Control Creation Edition. The topics listed above are not included in the Visual Basic 5.0 documentation parts that can be downloaded for use with the Control Creation Edition. However, the material on reentrancy and designing thread- safe DLLs contained in the following two topics will be of interest to Control Creation Edition users.
Changes to "Apartment-Model Threading in Visual Basic"
The original topic in Books Online explains that Visual Basic maintains a separate copy of global data for each thread. A side effect of this, which is not mentioned in Books Online, is that Sub Main is executed for each new apartment (that is, for each new thread). Otherwise, there would be no way to initialize global data for the thread. This is discussed further in Changes to "Designing Thread-Safe DLLs" and Changes to "Designing Multithreaded Out-of-Process Components" below.
Where You Can Use Threading
It is no longer true that components must be marked for Unattended Execution in order to use apartment-threading. Unattended Execution is now a separate option on the General tab of the Project Properties dialog box, as described in Selecting a Threading Model for Your Project, above.
The original topic states that when you make a cross-thread call, the method you're calling is protected from being reentered as long as the method does not yield (for example, by calling DoEvents). It is important to note that a method can also yield by making a cross-thread or cross- process method call of its own or by showing a form.
Changes to "Designing Thread-Safe DLLs"
The Books Online topic now applies to ActiveX Control projects, as well as ActiveX DLL projects.
The procedure for making an ActiveX DLL project apartment-threaded has change, as described in Selecting a Threading Model for Your Project (above). It is no longer necessary to suppress all user interaction (the Unattended Execution option) in order to have an apartment-threaded DLL.
When you create an instance of a form at run-time, Visual Basic follows the same rules it uses for other private objects. That is, the new form is created on the thread where the New operator is executed. If you create a form implicitly (for example, by accessing a form property using a variable that was declared As New Form1), the form is created on the thread where the code was executed.
NOTE: A special case of implicit form creation is the pre-declared ID (for example, Form1), a hidden global form variable that Visual Basic maintains for every form class. Because Visual Basic duplicates global data for each apartment, there is a separate Form1 for every thread. To avoid confusion, you may find it helpful to avoid using pre-declared IDs in apartment- threaded components by explicitly declaring your own form variables.
NOTE: Duplicate global data means that a variable you declare Public in a standard module is global only to the thread; each thread has an instance of the variable. It also means that the properties of the App object, such as App.ThreadID, have separate instances for each thread.
Unlike forms, ActiveX controls are public objects that can be created and used by client forms. An instance of an ActiveX control always resides on the same thread as the client form that uses it.
IMPORTANT: In an apartment-model DLL, a new apartment is created for each client thread that requests objects that the DLL provides. The first time a client thread requests such an object, Sub Main executes for the new apartment. A DLL cannot create threads of its own.
Changes to "Designing Multithreaded Out-of-Process Components"
It is no longer necessary to suppress all user interaction (the Unattended Execution option) in order to have a multithreaded Exe. The descriptions of Thread per Object and Thread Pool (round-robin threading) remain the same.
You can make an ActiveX Exe project apartment-threaded by opening the General tab of the Project Properties dialog box and selecting Thread per Object or selecting Thread Pool and setting a pool size greater than one.
Like other private objects, forms reside on the thread where they were created. Private objects cannot be created with the CreateObject function, so they cannot be used to start new threads.
IMPORTANT: In a multithreaded executable, Sub Main executes independently for each new thread. Visual Basic provides no way to determine which thread was created first.
Apartment-Threaded Controls for Internet Explorer 4.0
Internet Explorer 4.0 displays windows using multiple threads of execution. When two different windows in Internet Explorer 4.0 display instances of the same ActiveX control, the OCX must provide an instance to each thread. If both instances are provided from the same apartment in the OCX (as is the case with Visual Basic 5.0), one of the control instances will suffer from degraded performance because of cross-thread marshaling.
In addition to poor performance, a single-threaded control can cause problems with focus, tabbing, and accelerator keys when used in a multithreaded application.
Compiling your OCX with Visual Basic 5.0 Service Pack 2 improves performance and removes these problems. Each control instance used by Internet Explorer 4.0 runs on the same thread as the window that displays the control, because the OCX can support a separate apartment for each client thread.
Most ActiveX Controls Shipped with Visual Basic Have Been Updated
Visual Basic 5.0 Service Pack 2 includes new versions of most of the ActiveX controls that shipped with Visual Basic 5.0. The updated controls are now apartment-model threaded and will perform equally well on single- threaded or multithreaded clients. Controls that are still single-threaded include DBGrid, MSChart, and the legacy controls that were shipped in the Tools directory for Visual Basic 5.0.
ActiveX Controls Authored with Visual Basic
Once you have applied Visual Basic 5.0 Service Pack 2, new ActiveX control projects will default to apartment-model threading. You can add apartment- threading to an existing ActiveX Control project by changing the threading model, as described in Selecting a Threading Model for Your Project, and recompiling the project. For many ActiveX control projects, this is all you need to do.
If your ActiveX control project uses single-threaded constituent controls, attempting to change Threading Model to Apartment Threaded will cause an error. Because of the number and severity of problems that single-threaded ActiveX controls cause for multithreaded clients, Visual Basic does not permit them to be used as constituents of apartment-threaded ActiveX controls. This is discussed in Converting Existing Projects.
If you have existing ActiveX controls that employ single-threaded constituent controls, contact the vendor of the single-threaded control to see whether an apartment-threaded version is available.
Other design considerations for apartment-threaded ActiveX Controls are discussed in Converting Existing Projects.
Creating a Thread-Safe DLL with Forms
You can now set the Threading Model option to Apartment-Threaded in ActiveX DLL projects that contain forms.
Apartment-threaded DLLs cannot create their own threads; the first time a client thread requests an object provided by your DLL, a new apartment is created, and Sub Main executes for that apartment. All public objects requested by that client will reside in the same apartment, and will share global data. Any private objects (including forms) that are created by the public objects will also reside in the apartment.
Visual Basic does not provide any way for apartments to become aware of each other. However, a multithreaded client could obtain a reference to an object on Thread A, and pass that reference to an object on Thread B. If your DLL allows this, you should read the information on reentrancy in "Apartment Model Threading in Visual Basic" in Books Online, and in Changes to "Apartment-Model Threading in Visual Basic" in this document.
IMPORTANT: Modal forms shown by a thread do not block execution of code on other threads and are not modal to forms on other threads. Showing a form yields control, just as calling DoEvents would, and, therefore, may cause the code in an object to be re-entered.
Creating a Multithreaded Test Application
Applying Visual Basic 5.0 Service Pack 2 to the Professional and Enterprise Editions of Visual Basic allows you to create multithreaded test applications to exercise your multithreaded controls. The steps to create a simple multithreaded application are as follows:
- Open a new ActiveX EXE project, and name the default class module MainApp. Set the Instancing property for MainApp to PublicNotCreatable. A MainApp object will occupy the application's first thread, and display the main user interface.
- On the General tab of the Project Properties dialog box, select Sub Main in the Startup Object box, select Thread per Object in the Threading Model box, and enter a unique value for the Project name. (Project Name determines the type library name; problems may arise if two applications have the same type library name.) "ThreadDemo" is the project name used in the example below. On the Component tab, select Standalone in the Start Mode box.
- Add a form, name it frmProcess, and set the Visible and ControlBox properties to False. This form functions as a hidden window which Sub Main will use to identify the main thread for the process. No code is needed for this form.
- Add a standard module to the project. In this module, place the declarations, the Sub Main procedure, and the EnumThreadWndMain procedure shown below. As explained in the accompanying text and code comments, Sub Main will execute when your application starts and every time you create a new thread. The Sub Main sample code demonstrates how to identify the first thread, so that you know when to create MainApp.
- Add a form and name it frmMTMain. This form provides the main user interface for the test application. Add to it the single declaration and the Form_Unload event immediately above the heading "Multiple Instances of the Test Application."
- In the Class_Initialize event procedure for MainApp, add code to show frmMTMain. Code to do this is provided below.
- To create additional test threads, you must have at least one class in your project whose Instancing property is set to MultiUse. Add a class module and form, and then insert the code provided under the heading "Creating New Threads." Because you selected Thread per Object for this project, every public object that is externally created will start on a new thread. This means that you can create a new thread by using the CreateObject function to create an instance of your MultiUse class from its programmatic ID (ProgID), as discussed in the accompanying text.
- Add code to frmMTMain to create new threads by creating instances of the MultiUse classes you defined. In the example below, see the code under the heading "Creating New Threads."
- The development environment does not support multi-threading. If you press the F5 key to run the project, all objects will be created on the same thread. In order to test multithreaded behavior, you must compile the ActiveX EXE project and run the resulting executable.
IMPORTANT: To ensure that each new MultiUse object starts a new thread, you must use Thread per Object rather than Thread Pool.
Determining the Main Thread During Sub Main
Sub Main executes for every new thread because Visual Basic maintains a separate copy of your global data for each thread (each apartment). In order to initialize global data for the thread, Sub Main must execute. If your Sub Main loads a hidden form or displays your application's main user interface, new copies of those forms will be loaded for every new thread you create.
The following code determines whether Sub Main 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 End Sub
IMPORTANT: This technique for identifying the first thread may not work in future versions of Visual Basic.
Note that Sub Main takes no action for any thread after the first. When you add code that creates MultiUse objects (in order to start subsequent threads), be sure to include code to initialize those objects.
EnumThreadWindows is used with a call-back, EnumThreadWndMain, to locate one of the hidden windows Visual Basic creates for its internal use. The window handle of this hidden window is passed to GetWindowThreadProcessId, which returns the process ID. The process ID is then used to create a unique caption for the hidden window (frmProcess) that Sub Main loads. Subsequent threads detect this window and, thus, can tell that they don't need to create the MainApp object. These gyrations are necessary because Visual Basic does not provide a way to identify the application's main thread.
The MainApp class, in its Initialize event, displays the test application's main form. MainApp should pass its Me reference to the main form, so that the form keeps MainApp from terminating. From the main user interface, you can create all subsequent threads. Setting the Instancing property for MainApp to PublicNotCreatable helps you avoid displaying two main user interface forms.
A simple example of a MainApp class and its associated form (steps 5 and 6, above) might look like 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 ID in the hidden window caption allows multiple instances of the test application to run without interfering with one another.
When you call CreateObject, the instance of the public class you create will be on a thread in the current application instance. This is because CreateObject always attempts to create an object in the current application before looking for other running Exe components that might supply the object.
Useful Properties for the Apartment
You may find it useful to expose the process ID as a read-only property of the module that contains Sub Main:
'This code not required for the test application Public Property Get ProcessID() As Long ProcessID = mlngProcessID End Property
This allows any object on the thread to get the process ID by calling the unqualified ProcessID property. You may also find it useful to expose a Boolean IsMainThread property in this fashion.
Creating New Threads
The Thread per Object option causes every public object that is externally created (created using the CreateObject function) to start on a new thread. To create a new thread, simply use the programmatic ID (ProgID) of one of your MultiUse classes:
'This code not included in the test application Dim tw As ThreadedWindow Set tw = CreateObject("ThreadDemo.ThreadedWindow")
The variable "tw" now contains a reference to an object on a new thread. All calls to the properties and methods of this object that are made using "tw" will be subject to the extra overhead of cross-thread marshaling.
NOTE: An object created with the New operator is not created on a new thread. It resides on the same thread where the New operator was executed. See "Designing Multithreaded Out-of-Process Components" and "How Object Creation Works in Visual Basic Components" in Books Online.
To ensure that MainApp doesn't terminate until all of the other threads are finished, you can give each public class a MainApp property. When an object creates a MultiUse object on a new thread, it can pass the new object a reference to the MainApp object as part of the initialization process. (You can also pass MainApp a reference to the new object, so that MainApp has a collection of references to all objects that control threads. However, remember that this will create circular references. See "Dealing with Circular References" in Books Online.)
If you want a class that controls a thread to show a form, you should provide it with an Initialize method (not to be confused with the Initialize event) or a Show method that displays the form. Don't show the form in the Class_Initialize event procedure, as this could cause timing errors when you create instances of the class. In a very simple case, the code for a MultiUse ThreadedWindow class and its form, frmThreadedWindow, might look like 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 initialize the ThreadedWindow object:
'Code for the test application's main form (frmMTMain). Private Sub mnuFileNewTW_Click() Dim tw As ThreadedWindow 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 have a number of classes that can control threads, you can make your code more generic by defining an IApartment interface to contain the Initialize method. When you implement IApartment in each class, you can provide the appropriate Initialize method code for that class. Your thread creation code might look like 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[ASCII 133]) End Select ' Common code to initialize objects. Call iapt.Initialize(MainApp) End Sub
NOTE: You can make an IXxxxApartment interface that's known only to the Multithreaded application by defining the interface in a separate type library. In the ActiveX Exe project, set a reference to the type library.
Keeping References to Threaded Objects
To ensure proper shutdown of a multithreaded application, you must keep careful track of all references to the MultiUse objects you use to create and control threads.
Define your object lifetime goals clearly. For example, consider the case of a MultiUse object that shows a form. The easiest way to manage object lifetime is to have the object pass the form a Me reference; the form then keeps the object alive. When the user closes the form, the form's Unload event must set all references to the MultiUse object to Nothing so that the object can terminate and, in turn, clean up its reference to the form. (You may find it useful to give the MultiUse object a Friend method that cleans up the reference to the form and all other internal object references; the form's Unload event can call this method.)
If the object that controls a thread creates additional objects on the thread using the New operator, make sure you clean up references to those objects. The thread cannot close until all references to objects that were created on the thread have been released. Open threads consume system resources.
Friend Methods Cannot Be Used Cross-Thread
Because Friend properties and methods are not part of the public interface of a class, you cannot call them from another thread. Cross-thread calls between objects are limited to properties and methods that are declared Public.
If a method of an object yields by calling DoEvents, showing a modal form, or making a secondary call to an object 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 values or module-level variables, this could result in an invalid internal state for the object. To protect against reentrancy, you can:
- Avoid yielding.
- Keep a module-level Boolean flag for each method. When a method begins, it tests this 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. You must be careful to turn this flag off when the method completes or exits for any reason.
- Write reentrant methods (methods that don't depend on module- level data).
Visual Basic doesn't provide a way to fork execution (to have one thread initiate a method call on a new thread and immediately resume processing on the original thread). You can simulate this behavior in your test application by having the original method call turn on a timer and then return immediately. When the timer event occurs, you can turn the timer off and perform the asynchronous processing. This technique is discussed in "Asynchronous Call-Backs and Events" in Books Online, and is demonstrated in Books Online (see "Creating an ActiveX Exe Component") and in the Coffee sample application.
Using the Multithreaded Test Application
You must compile the multithreaded test application in order to test your apartment-threaded component because the Visual Basic development environment does not currently support multiple threads of execution. If you have Visual Studio, you may find it useful to compile the test application with debugging information for native code debugging so that you can use the Visual Studio debugger.
Note on Safe Scripting for ActiveX Controls
Two of the apartment-threaded controls included with Visual Basic 5.0 Service Pack 2, the Common Dialog control and the Toolbar control, now support the ActiveX IObjectSafety interface. When these controls are used in an environment where safe scripting is in force, such as Internet Explorer, they will be marked Safe for Scripting. However, certain features of these controls will be disabled in order to qualify them as safe for scripting:
- The Toolbar control cannot save its state to the registry because an ill-behaved script could exploit this to write uncontrolled information to the registry.
- Show Help is disabled for the Common Dialog control because an ill- behaved script could use this feature to execute Help macros that might cause harm to a user's computer.
The following files are included in Visual Basic 5.0 Service Pack 2:
asycfilt.dll c2.exe comct232.dep comct232.ocx comctl32.dep comctl32.ocx comdlg32.dep comdlg32.ocx dbgrid32.dep dbgrid32.ocx dblist32.dep dblist32.ocx mci32.dep mci32.ocx mscdrun.dep mscdrun.dll mscomm32.dep mscomm32.ocx mscondes.dll msflxgrd.dep msflxgrd.ocx msinet.ocx msinet.dep msmapi32.dep msmapi32.ocx msmask32.dep msmask32.ocx msrdc20.dep msrdc20.ocx msrdo20.dep msrdo20.dll msstkprp.dll msvbvm50.dll mswinsck.dep mswinsck.ocx oleaut32.dll olepro32.dll picclp32.dep picclp32.ocx readme.wri richtx32.dep richtx32.ocx setup1.bas setup1.exe setupwiz.exe stdole2.tlb sysinfo.dep sysinfo.ocx tabctl32.dep tabctl32.ocx vb5.exe vb5dep.ini vb5ide.dll vb5sp2.htm vb5sp2.wri vba332me.dll vba5.dll
Alpha systems only:
adme.dll dtccm.dll dtctrace.dll dtcutil.dll msdtcprx.dll
Search for VB5FixListSP2 on the Knowledge Base Web site at: http://msdn.microsoft.com/support
Keywords: kbinfo kbvbp500sp2fix kbvs97sp2fix KB172986