This topic is closely related to "ActiveX Component Shutdown," which describes the correct shutdown behavior for components.
The rules Visual Basic uses to determine when to shut down are different for in-process and out-of-process components.
Rule | In-process component | Out-of-process component |
References | There are no references —internal or external — to the component’s public objects. | No out-of-process clients have references to the component’s public objects. |
Forms | The component has no forms visible. | The component has no forms loaded. |
Code | No code in the component’s modules is currently executing, or in the calls list waiting to be executed. | Same as in-process. |
Startup | The component is not in the process of being started, in response to a client request for an object. | Same as in-process. |
When determining whether to shut down a component, Visual Basic considers only the references to public objects. References to objects created from classes whose Instancing property is Private will not count toward keeping a component running.
Important Classes in the VB type library are also private, and references to objects of these classes will not keep a component loaded. Classes in the VBA type library are public. (To view the classes in each of these libraries, select the library of interest in the Project/Library box on the Object Browser.)
For in-process components, the References rule has additional consequences in two areas: private objects and internal references to public objects.
Note A client application written in Visual Basic may not attempt to unload an in-process component immediately after the last reference is released. The frequency of attempts depends on how frequently idle time becomes available. Two minutes is a good rule of thumb.
Suppose your in-process component passes a client a reference to a private object, and the client subsequently releases its last reference to your public objects. Your component may unload at any point thereafter — because the reference to the private object doesn’t prevent shutdown — leaving the client with an invalid reference to the private object.
If the client attempts to use this invalid reference, a program fault will occur, and the client will terminate abruptly. If the client rarely takes a code path in which it attempts to use the invalid reference, such program faults may appear random and difficult to debug.
Important Private objects — that is, objects from classes in the VB library, or objects from classes whose Instancing property is Private — are usually private for a reason. If you need to pass an instance of one of your private classes to a client, you should do the work to make the object safe for public use, and then change the Instancing property of the class to PublicNotCreatable.
For in-process components, Visual Basic has no way to distinguish between external references to public objects (that is, references held by a client) and internal references to public objects (that is, references in object variables within your in-process component).
Therefore, if your in-process component is holding a reference to one of its own public objects, Visual Basic will not unload it. Any objects your component is holding references to will continue taking up memory and resources.
The most common causes of such internal references are global object variables — for example, a global collection — and circular references. Circular references arise when two objects keep references to each other, as discussed in "Dealing with Circular References," in "General Principles of Component Design." The sample application for "General Principles of Component Design" explores workarounds to global collections and circular references.
If several in-process components are in use, and they obtain references to each other’s public objects, the object references will keep Visual Basic from shutting the components down.
For an out-of-process component, Visual Basic keeps separate counts of external and internal references to public objects. Only external references — that is, references held by clients — will keep your component running. This is in contrast to the counting rule for in-process components.
Note In contrast to in-process components, Visual Basic’s marshaling rules prohibit out-of-process components from passing private object references to clients.
Internal references to public objects — global object variables, global collections, or circular references — should still be avoided. Although internal references will not keep an out-of-process component running, they may keep orphaned objects from being destroyed.
For example, suppose an out-of-process component is being used by two clients, and that client A releases all of its object references before client B does. If the component is holding internal references to the objects client A was using, they will go on taking up memory and resources until the component shuts down — which will not happen until client B releases the last of its objects.
If in the meantime a third client begins using objects provided by the component, orphaned objects from both client A and client B may go on taking up memory and resources.
Your out-of-process component may be using objects provided by an in-process component, running in the out-of-process component’s process space. The out-of-process component may pass references to its public objects to the in-process component, or the in-process component may independently request an object from the out-of-process component.
Regardless of how they are obtained, references held by an in-process component will not keep the out-of-process component from unloading when out-of-process clients have released all their object references.
It may seem odd that Visual Basic takes forms into account when determining whether it’s safe to close a component. To understand the logic of this, consider applications like Microsoft Excel, which can be started by the person who uses the computer, and can also provide objects to client applications.
If it’s wrong to pull your component out from under client applications that are using its objects, it’s even worse to pull a user’s spreadsheet, perhaps representing hours of painstaking labor, out from under the user just because client applications have released all the objects they were using.
In other words, the person using the user interface (if your component has one) is also a client.
In-process components depend on their clients in a different way than out-of-process components do, so Visual Basic interprets the Forms rule differently for the two cases.
A visible form will keep an in-process component in memory, even if the client has released all object references; an invisible form will not.
Note In-process components can be kept running in this fashion only until the client application closes. At that time, all forms are forced to unload. Forms unloaded at this time do not receive QueryUnload events, and the cancel argument of the Unload event is ignored.
By contrast, any loaded form will keep an out-of-process component running past the point at which all references have been released — even past the termination of all clients.
Visual Basic makes this distinction because an out-of-process component may be a standalone desktop application, and the fact that its forms are invisible may be only a temporary state.
In general, forms should be unloaded when the object that created them terminates. For example, your component may use a hidden form to hold a Timer control. You can put code to unload the form in the Terminate event procedure of the object that creates and loads the form.
Tip If multiple objects are sharing the same form, you can create a UseCount property for the form. Increment and decrement the UseCount property in the Initialize and Terminate events of the objects, and unload the form when the UseCount reaches zero.
If your component is also a standalone desktop application, its main form will keep your component running even if no client applications are using your objects. When the user chooses Exit from your File menu, or clicks the close box on your main form, you should simply unload the form, along with any hidden forms that are loaded, such as frequently used dialog boxes.
As far as the user is concerned, your application has closed. However, if client applications still have references to your objects, Visual Basic keeps your component running in the background.
The most difficult shutdown case is for a component that provides objects that control its user interface. For example, you might create a Window class that controls the MDI child forms in your application’s main form. If the user closes your main window, should you hide the main form even though client applications have child windows visible?
Questions like this keep user interface designers awake at night. You have many options. For example, the QueryUnload event of your main form has an unloadmode argument you can test to determine if the form is closing because the user clicked the close box, or because your program code is trying to unload it.
You can unload the child forms that the user opened through your application’s menus and toolbar buttons, hide the main form, and set the Visible property of your Application object to False. Client applications can set the Visible property to True again if necessary.
Alternatively, you can unload all the user’s forms, and leave the remaining child forms visible in your main window. You may want to show the user a message indicating what you’re doing, and perhaps provide the option of hiding the main window anyway, because the computer user is generally regarded as the ultimate owner of the visual interface.
There are no right answers. Your best bet is to do lots of usability testing.
Visual Basic will not unload your component if it is currently executing code. This includes the case where your component has called a routine in another component, a function in a DLL, or a Windows API function. While the external routine is executing, the procedure that made the call is waiting in the calls list, and Visual Basic will not close your component.
There are no actions you need to take — or refrain from — in regard to this shutdown condition. Like external object references, Visual Basic manages it for you.
The last condition is a special case that covers the awkward gap between the time your component to starts up because a client application has requested one of your objects, and the moment when your component returns the object reference. There may be moments in the startup sequence when there are no forms loaded and no code executing, and there certainly aren’t any external object references.
As with the code execution condition, you don’t need to take any action because Visual Basic handles this case for you.
The following guidelines summarize what you need to remember about component shutdown.
As a corollary to this, don’t implement a Quit method. Releasing a component by releasing all references to its objects is good programming practice. Educate users of your component by explaining in your Help file that this is the correct way to release your component. Don’t encourage bad programming practices by giving developers a shortcut that could disrupt other clients.
Most of the work of being a good component is done for you by Visual Basic. You can go a long way by simply letting it do its job.