Visual Basic Concepts
As the name implies, a root object is at the base of an object model. Thus it can be thought of as the object that contains all the other objects in the object model. Root objects are usually externally creatable — that is, clients can create instances of the root class using the New operator or the CreateObject function.
For example, the root object of a billing system might have, as one of its properties, an Invoices collection. Each Invoice object in the collection might contain a LineItems collection, so that you could access a particular LineItem object in this fashion:
Dim li As LineItem
Set li = <root object>.Invoices(1138).LineItems(3)
The name of the root object will vary, depending on the type of component. If your component is a standalone desktop application that also provides objects, the way Microsoft Excel does, then "Application" might seem like a logical name for your root object.
On the other hand, if your component runs in process, providing objects to clients but having no user interface of its own, "Application" is probably not an appropriate name for the root object. Use a name that suggests the functionality of your component, or that is similar to your component’s type library name.
Tip So many object models use "Application" as the name of their root object that it has become a source of global name space pollution. That is, in order to get the right Application object, you frequently have to qualify it with the type library name. Be original. Find a name that suggests the unique character of your component’s root object.
Note Although "Application" is generally recognized as the prototypical root object name, the code examples in this topic use "Root" instead — simply because "Application" is so thoroughly overused.
A root object is the logical place to define properties and methods that affect the entire component. For example, a root object might have a read-only Version property. By definition, a root object will also contain collections or object properties for objects at the next level of the hierarchy.
It’s fairly common for a root object to be a global object. You can do this in your component by setting the Instancing property of your root object’s class module to GlobalMultiUse, as described in "Global Objects," in "Building Code Components." This makes the properties and methods of the root object appear to be global procedures, the way the members of the VB and VBA libraries are.
If your root object is global, you can allow users to start an instance of your component simply by referring to the root object in code. Provide the root object with a read-only property of the same name, which returns a self-reference. The following code fragment shows how this might look for a root object named "Root."
Public Property Get Root() As Root
Set Root = Me
End Sub
This sounds self-referential, but it works — when a client refers to Root in code, the global object is created before the Property Get is called.
For some components, it may not be appropriate to have all of the properties and methods of your root object dumped willy nilly into the global name space. In this case, don’t make your root object global. Instead, create a class named Globals, and set its Instancing property to GlobalMultiUse.
You can add to this Globals class those properties and methods you want to have in the global name space. In those cases where you want to expose the root object’s methods, add methods with the same arguments to the Globals class, and delegate to the method of the same name on the root object.
You can still allow users to start your component simply by referring to its root object in code; simply add a property such as the following to the Globals class:
Public Property Get Root() As Root
If gRoot Is Nothing Then
Set gRoot = New Root
End If
Set Root = gRoot
End Property
This example assumes your root object is named "Root," and that you keep a reference to it in the global variable gRoot
.
The Globals class is a good place to put general-purpose procedures you want your users to be able to access globally.
Tip Implement any object properties you add to the Globals class using Property Get procedures, rather than with public object variables. Property Get defines a read-only property, whereas users may accidentally set public object variables to Nothing.
"Programming with ActiveX Components," in the Visual Basic Programmer’s Guide, describes the use of the GetObject function to get a reference to a running instance of a component, or to test to see if one is running. A client application cannot automatically use the GetObject function to obtain a reference to an existing instance of a class defined in a Visual Basic component.
You can do the work yourself, by putting your root object in the Running Object Table (ROT). COM provides the ROT as a location for running objects to register themselves, so that functions like GetObject can be used to locate them.
An alternative technique is to make your root object PublicNotCreatable, so that clients can’t create instances directly. Instead, you can provide a MultiUse Connector object which clients can create. The Connector object can provide a property (generally with the same name as your root object) that returns a reference to a single shared root object.
This technique is described in "Asynchronous Call-Backs and Events" in "Building Code Components," and demonstrated in the sample applications for that chapter.
Tip A Globals object, described earlier in this topic, can provide the same functionality as a Connector object, in an unobtrusive fashion.
For More Information The ROT is explained in many books about COM, such as Inside OLE, by Kraig Brockschmidt, or the OLE Programmer’s Reference, both of which are available from Microsoft Press.
The functionality your root object provides depends heavily on which value you choose for the Instancing property of its class module: PublicNotCreatable, SingleUse, GlobalSingleUse, MultiUse, or GlobalMultiUse.
If your root object is marked PublicNotCreatable, client applications cannot create instances of it using the New operator or the CreateObject function. While this may seem somewhat upside down — most people will expect the root object to be a way to get at all the other objects in your object model — in conjunction with a global object or a Connector object, as described earlier in this topic, it can be a way to allow clients to share a single instance of your root class.
In order to give clients access to the root object in this case, you must give your public creatable objects (such as the Connector object or the Globals object) a property that returns a reference to the root object, as shown here:
Property Get Root() As Root
Set Root = gRoot
End Property
Note that this does not create circular references, because the dependent objects are not holding actual references to the Root
object; they are only using Property Get to return a global reference.
You can also implement a global object with such a property, as described earlier in this topic.
If the class that defines your root object is has the Instancing property settings SingleUse or GlobalSingleUse, a client application will start a new running instance of your component every time it uses the New operator or the CreateObject function to create a root object.
In its Initialize event procedure, your root object might set a global reference to itself that other objects can use to gain access to it, as shown in the following code fragment:
Private Sub Class_Initialize()
' Variable gRoot is declared Public in a standard
' module.
Set gRoot = Me
' ...additional initialization code...
End Sub
If you don’t keep a global reference to the root object, and the client releases its reference — while keeping references to one or more dependent objects — your component can get into a state in which the dependent objects exist without the root.
Note Because a SingleUse root object causes a new instance of your component to be loaded for each client request, keeping a global reference doesn’t result in orphaned objects (as described in "ActiveX Component Shutdown"), unless clients pass each other references to root objects.
Depending on whether you mark additional class modules as externally creatable, marking your root object SingleUse can also give each client application exclusive use of a copy of your component.
If the only externally creatable class in your component is the root object, every client application will have its own instance of your component. Because Visual Basic classes do not support the use of the GetObject function to obtain references to existing objects, there is no way another client can get access to a running copy of your component.
The disadvantage of this is that every client must start an instance of your component, which will use more memory and system resources. If you need to give each client application exclusive use of a copy of your component, you may find it more efficient to write an in-process component.
If you mark other classes in your component MultiUse or GlobalMultiUse, clients can create objects from those classes without first creating a root object.
This is similar to the way Microsoft Excel’s externally creatable objects work. If you create a Microsoft Excel Application object, you start a new running instance of Microsoft Excel. If you create a new Sheet or Chart object, however, they will be provided by a previously running instance of Microsoft Excel, if there is one.
To handle additional externally creatable objects, the Initialize event procedures for all externally creatable objects should test for the existence of the root object, and create it if it doesn’t yet exist:
' Code fragment from any externally creatable class.
Private Sub Class_Initialize()
If gRoot Is Nothing Then
Set gRoot = _
CreateObject("MyComponent.Root")
End If
' ...more initialization code for this object...
End Sub
It’s important to use the CreateObject function to create the root object, rather than the New operator. Otherwise a client can create another root object in the same instance of the component at a later time. For more details, see "Scalability Through Multiple Processes: SingleUse Objects" and "How Object Creation Works in Visual Basic Components" in "Building Code Components."
Tip Why not create the root object in your Sub Main procedure? For one thing, creating objects in Sub Main is not a good idea. It can lead to deadlocks and object creation time-outs. Furthermore, if your component is starting in response to a request from a client, you cannot tell what object is being created. If you create the SingleUse root object in Sub Main when a client has requested the root object, the client’s request will fail.
Note Marking multiple classes in your component SingleUse or GlobalSingleUse may produce unexpected results, as discussed in "Scalability Through Multiple Processes: SingleUse Objects" in "Building Code Components."
If you mark your root class MultiUse or GlobalMultiUse, each client that will get its own root object, but all the instances may come from the same running instance of your component.
The implementation decision you face is this: Should all those instances share the same collections of dependent objects, or should each root object have its own collections? This depends entirely on what use you intend for your component. If it’s an out-of-process component that’s also a standalone desktop application, you may want each of the root class instances to act like an Application object — in which case they should all share global collections of dependent objects.
On the other hand, if each root object and the dependent objects it contains represent a unit of functionality, and you want clients to be able to use more than one unit, then the root class instances should be independent of each other, each with its own collections of dependent objects.
You can still back your root objects with a Globals object, as described earlier in this topic, only in this case the Globals object would need to contain a collection of root objects instead of a single instance.
Data that’s global to the running instance of your component, such as version number, could also be exposed using read-only properties of the Globals object.