COM has one interface from which all other interfaces derive: IUnknown. Every interface must derive directly from IUnknown or from an interface that has IUnknown at the root of its inheritance chain. IUnknown is at the top of every COM interface hierarchy. This means that the three methods defined in IUnknown are always at the top of any COM-compliant vTable, as shown in Figure 4-1. Any connection to an object is made through an IUnknown-compatible reference.
Figure 4-1. The three methods of the IUnknown interface always appear at the top of a COM-compliant vTable. Any connection to an object guarantees a client the ability to call these methods.
IUnknown expresses the base behavior of a COM object as opposed to a domain-specific behavior that you experience through a user-defined interface. IUnknown allows every COM object to manage its own lifetime. It also allows a client to query an object to see whether it supports a given interface and to dynamically cast the object to any of the supported interfaces.
Note that Visual Basic programmers are never exposed to IUnknown directly. Visual Basic's mapping layer hides all the code that deals with IUnknown. While you can declare variables of type IUnknown, you can't invoke its methods because they are marked as [restricted] in the type library STDOLE2.TLB. Likewise, on the object side, you never supply implementations for the three methods of IUnknown. Visual Basic takes care of all this for you behind the scenes.
In COM, an object is expected to manage its own lifetime. However, an object needs help to make an informed decision about whether to terminate itself or continue running. The client of an object is responsible for calling AddRef whenever it duplicates an interface reference and for calling Release whenever it drops an existing connection. If all clients live up to their responsibilities, an object can provide a simple implementation of these two methods to properly manage its lifetime. The object maintains a count of connected references and releases itself from memory whenever this count drops to 0.
This model of lifetime management is very different from the way that C and C++ programmers have traditionally dealt with memory allocation. In C and C++, it is common for a client to allocate, use, and then explicitly free memory. The problem with this approach is that it doesn't accommodate multiple clients of a single object. With the reference counting scheme employed by COM, multiple clients can connect to an object. If a client never explicitly deletes an object, it is impossible for one client to delete an object that is currently being used by another client.
C++/COM clients are expected to follow a set of rules for calling AddRef and Release, which are listed in the COM specification. These rules aren't complicated, but they require a fair amount of discipline to follow when you write COM code by hand. The only time that reference counting is really noticeable in a C++/COM application is when it's done incorrectly.
Visual Basic programmers are never responsible for calling AddRef and Release. Consequently, they aren't vulnerable to such bugs. COM+ will address this problem with C++ clients by introducing a universal run-time layer that shields all programmers from calling AddRef and Release explicitly. This will finally give C++ programmers the same benefits that Visual Basic programmers have enjoyed for years.
When you work in Visual Basic, you don't have to worry much about lifetime management. You really need to remember only two rules: Hold the object reference when you want to keep the object alive, and release the reference when you no longer care about the object. Visual Basic handles all calls to IUnknown for you. Take a look at the following example:
Sub CreateAndUseDog Dim Dog As IDog Set Dog = New CBeagle ' AddRef is called. Dim Dog2 As IDog Set Dog2 = Dog ' AddRef is called. Set Dog2 = Nothing ' Release is called. ' Release is called on Dog when reference goes out of scope. End Sub
This code results in several calls to AddRef and Release. When an object is activated, it experiences an AddRef call. When you create a second reference to the object, the Visual Basic client calls AddRef. If you explicitly set an interface reference to Nothing, Visual Basic calls Release for you. If you don't explicitly set a reference to Nothing, Visual Basic detects when an active reference is going out of scope and calls Release on the object just before dropping the connection.
On the object side, Visual Basic automatically implements AddRef and Release to conduct standard reference counting. A Visual Basic object keeps running as long as active clients remain connected. When the last connection calls Release, the Visual Basic object terminates itself. Once again, there's nothing you can do in Visual Basic to influence how an object manages its lifetime, but it's important to understand how your objects will behave.
One of the trickier aspects of lifetime management involves circular references, such as when object A holds a reference to object B and object B holds a reference to object A. Even after all interested clients have dropped their connections, the objects remain in memory because of the outstanding references they hold on each other. If your design involves circular references, you must make sure that objects go away when they are no longer needed. One common way to prevent circular references from keeping objects alive forever is to create an explicit method in one of the objects that breaks a connection, causing the composite to start breaking down.
The first and arguably most significant method of IUnknown is QueryInterface, which allows clients to navigate among the various interfaces supported by an object. This act of dynamically casting different interfaces is known as type coercion. A client can also use QueryInterface simply to test whether an object supports a particular interface. The capabilities provided by QueryInterface are essential to COM. Without QueryInterface, COM couldn't achieve polymorphism and run-time type inspection, which are required in an interface-based programming paradigm.
A COM object must implement at least one interface, but it can implement as many as it likes. Objects that implement multiple interfaces must allow clients to navigate among them by calling QueryInterface. A client passes a desired IID when it calls QueryInterface, and the object responds by returning a reference to the interface. If the client asks for an interface that's not supported, the call to QueryInterface will fail. If the call fails, the client can determine that the requested functionality isn't available and thus degrade gracefully.
The Visual Basic run-time layer silently calls QueryInterface when you assign an object to a specific reference type. Take a look at the following example:
Dim Dog As IDog Set Dog = New CBeagle ' To get at another interface Dim WonderDog As IWonderDog Set WonderDog = Dog ' QueryInterface is called. WonderDog.FetchSlippers
If you have an IDog reference to an object and you want to retrieve an IWonderDog reference, you can simply use the Set statement to cast one interface reference to another. A trappable "Type mismatch" error will occur if the interface is not supported. If the cast is successful, you can use the new interface reference to invoke methods on the object .
Some Visual Basic programmers prefer to blindly cast interface references, as shown in the previous example. If you try to cast to an interface that isn't supported, the Visual Basic run-time layer deals with an unsuccessful call to QueryInterface by raising a trappable run-time error in your code. If there's a chance that the cast will fail, you must be prepared to trap and deal with this error. If you would rather avoid dealing with run-time errors, you can query an object for interface support by using Visual Basic's TypeOf syntax, like this:
Dim Dog As IDog Set Dog = New CBeagle ' Test for support before using interface. If TypeOf Dog Is IWonderDog Then ' Call to QueryInterface Dim WonderDog As IWonderDog Set WonderDog = Dog ' Call to QueryInterface WonderDog.FetchSlippers Else ' Degrade gracefully if interface isn't supported. End If
A Visual Basic object automatically implements QueryInterface. When you implement one or more user-defined interfaces in a class, Visual Basic provides an implementation of QueryInterface that allows clients to move among interface references. As in the case of AddRef and Release, the Visual Basic implementation of QueryInterface is fairly straightforward, but you can never see it. You have to take it on faith that it works perfectly.