When a server passes a pointer to its class factory to a function such as CoRegisterClassObject, the resulting stub maintains a reference count on that class factory so that the only way to remove that reference is by calling CoRevokeClassObject. This sort of reference, one that will guarantee that the object stays in memory no matter what else happens, is called a strong connection, or strong lock. The adjective "strong" means that the stub maintains a pointer and a reference count to some object and that the stub will stay in memory as long as that reference count exists. Only a specific function call from the server itself can remove the reference.
Class factory registration, however, is not the only function in COM that creates a stub so that that stub holds a pointer. Some functions, such as RegisterDragDrop (which we'll see in Chapter 13), create a stub to hold the pointer but do not call AddRef on that pointer. Thus, a Release call from an external client can easily remove the last reference count on the object and destroy it along with the stub. A case in which COM holds a pointer to an object but not a reference count is called a weak connection, or weak lock. Here weak means that an object and a stub can disappear without any additional action on behalf of the server or whatever agent registered the object in the first place. For example, because RegisterDragDrop has only a weak lock on an object, that object and its stub can disappear before RevokeDragDrop is called.
The most critical difference between strong and weak locks is the possible disappearance of the stub while an object remains active. Whatever code created an object might itself hold reference counts to that object so that an external Release call doesn't actually destroy the object itself. However, such a Release call will, in fact, destroy a stub that maintains only a weak lock on the object in question. When the stub is destroyed in this manner, the object, although it's still running, becomes unavailable to external clients, period. Without a stub, COM no longer has any reference to an object, and thus, any external request to open an RPC Channel to that object through a stub will fail. In other words, a weak registration function is the only chance COM has to obtain a pointer to a given object that is then held in the stub. If that stub disappears, so does that pointer as far as COM is concerned—COM simply has no way to ask the server for another pointer!
Weak locks are, in fact, useful at times, as we'll see in later chapters. The function IRunningObjectTable::Register, for example, actually gives you the choice of a strong lock or a weak lock. A server would choose a weak lock if it held no references to an object and wanted that object to be destroyed when all external references were removed. A server would choose a strong lock if it wanted control over object and stub lifetime; in this case, it would have to call IRunningObjectTable::Revoke to remove that lock.
This leaves us with two possible problems that can arise with other, less flexible, registration functions:
The solution to the first problem is an interface named IExternalConnection. When strong locks exist on an object, its reference count will remain positive until some other code in the server revokes the registration. In some cases, this is not the behavior you want, so you implement IExternalConnection on the object; this allows you to track only external connections from outside clients through the AddConnection (a new connection has been made) and ReleaseConnection (a connection has been ended) member functions. An object that wants to destroy itself does so inside ReleaseConnection when the last connection is torn down and the argument fLastReleaseCloses is TRUE, which will usually be the case. The Boolean flag exists for cases when COM is performing some operation in which an external connection is being removed before a new one is added. In these cases, COM needs a way to prevent the object from closing when it should not. If it did, the last ReleaseConnection would come with a FALSE flag, suppressing object destruction.
The solution to the second problem comes in two forms. You can count the number of weak locks through IExternalConnection and add this count to your own reference count if that suits your needs. The other solution requires a server to have some means to tell COM to add or remove a strong lock from an object. The means is a single COM API function named CoLockObjectExternal, which returns an HRESULT and takes the following arguments:
Argument | Description |
pUnk | Points to the object to be locked or unlocked. |
fLock | Specifies whether a lock is to be added (TRUE) or removed (FALSE). Adding a lock will call AddRef on the object; removing a lock will call Release. |
fLastUnlockReleases | Ignored unless fLock is TRUE, in which case it specifies whether the strong lock (reference) controls the object's lifetime, such that removal of the last strong lock will cause the stub to release all of its references to the object. |
If you call CoLockObjectExternal on an object that is already registered in the global object table through some other means, this function calls only AddRef and Release. If the object is already registered, this function will register or revoke the object from the global object table, making it suitably available or unavailable to external clients. If the object implements IExternalConnection as well, it can know when connections are being added and removed. Some object implementations might use this interface merely for information; others might well use a combination of self-imposed strong locks and IExternalConnection to control their own lifetimes.
In this context, we can better understand another COM API function that we saw in Chapter 5: CoDisconnectObject, which takes a single argument, the IUnknown pointer to the object to disconnect. We described this function as forcibly removing all external connections to an object, a useful thing to do when a user terminates a server directly. CoDisconnectObject has the effect of tearing down the server-side stub for the object and disconnecting the RPC Channel. Subsequent calls from the client will fail inside the RPC Channel, generating an RPC_E_NOTCONNECTED error. You can see how CoDisconnectObject is highly useful to ensure that external clients can no longer make any calls to an object, preventing reentrancy problems during the destruction of that object.
Along these same lines, a client might want to know whether its proxy has, in fact, an open and connected RPC Channel to the remote object. This is the purpose of another COM API function, named CoIsHandlerConnected. To this function the client passes its IUnknown pointer to what it believes is the object, which really identifies the proxy instead. This function does little more than ask the proxy whether it's connected, and the proxy then turns around and asks the RPC Channel whether it's connected.
CoIsHandlerConnected is useful for more than just diagnostics when a call to a remote object fails. A client might be using a true object handler (underneath which is the proxy itself) that implements much of what the client needs from the object, relying on a local server for only a few operations. If the client knows which calls would require COM (at the handler's behest) to launch the local server, it could delay making such calls until it has cached a sufficient number of them to warrant the overhead involved in launching the server. CoIsHandlerConnected would tell such a client whether the server is already running, allowing the client to decide when to make the optimization.