[Previous] [Contents] [Next]

Managing the Lifetime of an Executable Component

At this stage, the executable component is fully operational, but it has a serious problem. Because the program will continue executing, it will soon reach the end of its main function and terminate. Clients of our component will be justifiably upset if the component simply exits without warning, so we must find a way to keep our component alive. In-process components do not have to worry about this problem because they are loaded into the address space of their client and politely wait to be called. When a client finishes using an in-process component, COM+ calls the exported function DllCanUnloadNow before unloading the DLL.

Executable components, however, are responsible for managing their own lifetimes. How this issue is addressed depends on whether the executable component runs in a single-threaded apartment (STA) or the multithreaded apartment (MTA). Multithreaded components typically address this problem by creating an event object using the Win32 CreateEvent function, as shown in the following code. This function call is followed by a call to the Win32 function WaitForSingleObject, which waits for the event to be signaled.

g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
WaitForSingleObject(g_hEvent, INFINITE);

Because the INFINITE flag is used in the call to WaitForSingleObject, the code will wait forever until the event is signaled, solving the problem of the component exiting prematurely. A client can request all the services it wants from the executable component, and only when all clients have finished using the component do we want it to exit. Exiting can be accomplished by adding code in the object's destructor to detect whether this is the last outstanding reference to the component, as shown here:

CInsideCOM::~CInsideCOM()
{
    if(CoReleaseServerProcess() == 0)
        SetEvent(g_hEvent);
}

When the SetEvent function is called, the WaitForSingleObject call that was on hold in the main function returns. After performing a few cleanup steps, the component is permitted to exit. First, the component must inform COM+ that it is no longer offering any objects for use. This notification is sent using the CoRevokeClassObject function, the ancillary of CoRegisterClassObject. Once there is no danger of more clients calling to request objects, you release what should be the final class factory pointer. The IUnknown::Release method is followed immediately by a call to CoUninitialize. The entire main function for an executable component is shown below:

void main(int argc, char** argv)
{
    CommandLineParameters(argc, argv);

    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    DWORD dwRegister;
    IClassFactory *pClassFactory = new CFactory();

    CoRegisterClassObject(CLSID_InsideCOM, 
        pClassFactory, CLSCTX_LOCAL_SERVER, 
        REGCLS_SUSPENDED|REGCLS_MULTIPLEUSE, &dwRegister);
    CoResumeClassObjects();

    WaitForSingleObject(g_hEvent, INFINITE);
    CloseHandle(g_hEvent);

    CoRevokeClassObject(dwRegister);
    pClassFactory->Release();
    CoUninitialize();
}

Race Conditions

In the preceding code fragments, you might have noticed some unfamiliar functions, including CoReleaseServerProcess, CoResumeClassObjects, and CoRevokeClassObject. These belong to a set of four helper functions, described in the table below, that simplify the job of writing robust server-side code in multithreaded executable components. Collectively, these functions address the unique race conditions afflicting multithreaded executable components. While they are designed for use by multithreaded executable components, you can use them just as easily with single-threaded components. In-process components have no use for these functions.

Function Description
CoResumeClassObjects Called by a server to inform the SCM about all registered classes; permits activation requests for those class objects
CoSuspendClassObjects Prevents any new activation requests from the SCM on all class objects registered within the process
CoAddRefServerProcess Increments a global per-process reference count
CoReleaseServerProcess Decrements a global per-process reference count

During the start-up and initialization phase of a local component, the CoRegisterClassObject function is called once for each CLSID supported by the component. This function tells COM+ about the component's classes and gives it a pointer to each class's class object. After it registers all of its class objects, the component is ready for use. For multithreaded components, however, calls might arrive after the first object is registered. This is a problem because, at least theoretically, a client might instantiate an object and perform the final IUnknown::Release call before the rest of the component has had a chance to finish initializing. This final IUnknown::Release call would decrement the usage reference counter to 0 and initiate a shutdown of the component process.

To avoid this type of activation race condition and simplify the developer's job, a multithreaded component with more than one class object should use the REGCLS_SUSPENDED flag when calling the CoRegisterClassObject function, as shown in boldface in the following code. This flag tells COM+, "I'm registering a class, but please don't tell anyone else just yet." The component can peacefully register the remainder of its class objects, passing the REGCLS_SUSPENDED flag with every call to CoRegisterClassObject. Once all the classes have been registered with COM+ and the component is ready to accept client requests, the component calls the CoResumeClassObjects function to tell COM+ that the classes are available for use.

DWORD dwRegister;
IClassFactory *pIFactory = new CFactory();
CoRegisterClassObject(CLSID_InsideCOM, 
    pIFactory, CLSCTX_LOCAL_SERVER, 
    REGCLS_SUSPENDED|REGCLS_MULTIPLEUSE, &dwRegister);
// Register more class objects here.
// Use the REGCLS_SUSPENDED flag with each call.

// Now tell the SCM about all the call objects in one blow.
CoResumeClassObjects();

The CoResumeClassObjects function tells COM+ to inform the SCM about all the registered classes. This technique offers several advantages, the most important of which is that no activation or shutdown requests are received before the component is ready. Aside from solving the aforementioned race condition, this call is also more efficient because COM+ needs to make only one call to the SCM to tell it about all the class objects instead of working in the piecemeal fashion used when CoRegisterClassObject is called without the REGCLS_SUSPENDED flag. This technique also reduces the overall start-up and registration time required by the component.

One additional function, CoSuspendClassObjects, prevents any new activation requests from the SCM on all class objects registered within the process. This function is not usually called directly by local components, but it is nonetheless the complement of CoResumeClassObjects. Note also that CoSuspendClassObjects does not relieve the component of the need to call CoRevokeClassObject for each registered class object.

A similar race condition might occur when a client calls the CoGetClassObject function to obtain a pointer to an object's class factory. This call is often followed by a call to the IClassFactory::LockServer(TRUE) method to prevent the component from exiting. However, a window of opportunity exists between the time the class object is obtained and the time the client calls the LockServer method during which another client can connect to the same component. This second client can instantiate an object and then immediately call IUnknown::Release, causing the component to shut down and thus leave the first client with a disconnected IClassFactory pointer.

To prevent this race condition, COM+ implicitly calls the IClassFactory::LockServer(TRUE) method as part of CoGetClassObject and implicitly calls IClassFactory::LockServer(FALSE) when the client releases the IClassFactory interface pointer. This obviates the need for the client process to call the LockServer method because COM+ handles this step automatically. In fact, the standard proxy for the IClassFactory::LockServer method implemented by ole32.dll simply returns S_OK without actually making a remote call.

Executable Component Shutdown

To shut down properly, a local component must keep track of how many objects it has instantiated and the number of times its IClassFactory::LockServer method has been called. Only when both of these counts, often represented in code as the variables g_cComponents and g_cServerLocks, reach 0 can the component legally exit. In a thread-oblivious component, the decision to shut down is automatically coordinated with incoming activation requests by virtue of the fact that all incoming requests are serialized by the message queue. Upon receiving a call to the IUnknown::Release method for its last object and deciding to shut down, a component revokes its class objects using the CoRevokeClassObject function. This function tells COM+ not to accept any more activation requests. If an activation request comes in after this point, COM+ recognizes that the class objects are revoked and returns an error to the SCM. The SCM then automatically attempts to launch a new instance of the component process, beginning the entire process anew.

If multiple objects are running in the MTA or in multiple STAs, another thread in the component will hand out a new object while the last object in a component is shutting down. If the component proceeds with the shutdown at this critical stage, somewhere a client thinks it has a valid interface pointer. You can imagine what happens if the client tries to use that pointer. Thus, the decision to shut down must be coordinated across multiple threads, possibly in different apartments, so that a thread of the component does not shut down while another thread is busy handing out objects. To help you coordinate a component's shutdown across multiple threads, COM+ provides two reference-counting functions: CoAddRefServerProcess and CoReleaseServerProcess.

CoAddRefServerProcess increments a processwide reference count. CoReleaseServerProcess decrements that reference count; also, when the reference counter reaches 0, it automatically calls the CoSuspendClassObjects function. After this call, any incoming activation requests are refused. This technique permits the component to peacefully deregister its class objects from its various threads without the risk that a simultaneous activation request will be accepted. Activation requests received during or after the shutdown procedure are handled by directing the SCM to launch a new instance of the component process.

You can use CoAddRefServerProcess and CoReleaseServerProcess to replace the global object count (g_cComponents) and the global IClassFactory::LockServer count (g_cServerLocks) as well as solve the race condition discussed earlier. The simplest way for a local component to use these functions is to call CoAddRefServerProcess in the constructor of each of its instance objects and call CoReleaseServerProcess in the destructor of each of its instance objects,4 as shown in the following code:

CInsideCOM::CInsideCOM() : m_cRef(1)
{
    CoAddRefServerProcess();
}
CInsideCOM::~CInsideCOM()
{
    if(CoReleaseServerProcess() == 0)
        InitiateComponentShutdown();
}

Also, CoAddRefServerProcess should be called in the IClassFactory::LockServer method when the bLock flag is TRUE, as shown in the following code. If the bLock flag is FALSE, the CoReleaseServerProcess function should be called.

HRESULT CFactory::LockServer(BOOL bLock)
{
    if(bLock)
        CoAddRefServerProcess();
    else
        if(CoReleaseServerProcess() == 0)
            InitiateComponentShutdown();
    return S_OK;
}

As you can see in the preceding code, the application should check the value returned by the CoReleaseServerProcess function. A value of 0 indicates that CoReleaseServerProcess has automatically called CoSuspendClassObjects and that the component can initiate shutdown. In the STA case this means that the component should signal all of its STAs to exit their message loops, and then it should call CoRevokeClassObject and CoUninitialize. It signals an STA to exit its message loop by posting a WM_QUIT message to the queue using the Win32 PostQuitMessage(0) function, as shown here:

void InitiateComponentShutdown()
{
    PostQuitMessage(0);
}

If the component supports the MTA model, it need only revoke its class objects and then call CoUninitialize, since MTA-based code does not use a message loop. (Unless you use the functions CoAddRefServerProcess and CoReleaseServerProcess diligently in both the constructors and destructors of instance objects as well as in the IClassFactory::LockServer method, the component process can shut down prematurely.) The following code sets an event object to prod the MTA-based component to exit:

void InitiateComponentShutdown()
{
    SetEvent(g_hEvent);
}

The following code shows the complete main function of a typical MTA-based component. The WaitForSingleObject function, shown in boldface, is waiting for the event to be set. When the SetEvent function is called by the shutdown code, it wakes up and executes the remaining steps needed to exit cleanly.

void main(int argc, char** argv)
{
    CommandLineParameters(argc, argv);

    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    DWORD dwRegister;
    IClassFactory *pIFactory = new CFactory();
    CoRegisterClassObject(CLSID_InsideCOM, pIFactory, 
        CLSCTX_LOCAL_SERVER, 
        REGCLS_SUSPENDED|REGCLS_MULTIPLEUSE, 
        &dwRegister);
    CoResumeClassObjects();

    WaitForSingleObject(g_hEvent, INFINITE);
    CloseHandle(g_hEvent);

    CoRevokeClassObject(dwRegister);
    pIFactory->Release();
    CoUninitialize();
}

Custom Activation Interfaces

In Chapter 11, we discussed building class objects that implement a custom activation interface instead of, or in addition to, IClassFactory. As we saw, actually implementing a custom activation interface in a class object is not difficult. Those objects, however, were housed by in-process components. Managing the lifetime of an executable component whose class objects do not support the IClassFactory interface is more difficult.

As described in Chapter 3, poorly written executable components are often structured in such a way that a client holding a reference only to a class object is not sufficient to keep the component process running. The following code in boldface shows how the IUnknown::Release call destroys the object, thereby invoking the destructor, which sets a global event and finally causes the component to exit:

ULONG CPrime::Release()
{
    unsigned cRef = InterlockedDecrement(&m_cRef);
    if(cRef != 0)
        return cRef;
    delete this;
    return 0;
}

CPrime::~CPrime()
{
    if(CoReleaseServerProcess() == 0)
        SetEvent(g_hEvent);
}

void main(int argc, char** argv)
{
    // Initialization and CoRegisterClassObject omitted

    // Create the event to wait for.
    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    // Now wait for SetEvent...
    WaitForSingleObject(g_hEvent, INFINITE);

    // Event set. Exit now.
    CloseHandle(g_hEvent);
    CoRevokeClassObject(dwRegister);
    pPrimeFactory->Release();
    CoUninitialize();
}

The difficulty arises when the client releases a pointer to an object but doesn't release a pointer to that object's class object. The client thinks that retaining an open pointer to the class object means that the server will remain running. Later, the client might want to use that class object pointer to create new instances of the actual object. This problem is illustrated in the following code using the example of the IPrimeFactory custom activation interface described in Chapter 11; the release of the Prime object is shown in boldface:

// Get the PrimeFactory class object.
IPrimeFactory* pPrimeFactory;
CoGetClassObject(CLSID_Prime, CLSCTX_LOCAL_SERVER, NULL, 
    IID_IPrimeFactory, (void**)&pPrimeFactory);

// Use the custom class object to create a Prime object.
IPrime* pPrime;
pPrimeFactory->CreatePrime(7, &pPrime);

// Use the Prime object here...
int next_prime;
pPrime->GetNextPrime(&next_prime);

// Release the Prime object.
pPrime->Release();

// Give the component a chance to exit.
Sleep(1000);

// Use pPrimeFactory to create another Prime object.
// Unfortunately, the component has already exited!
// Prepare for system meltdown.
pPrimeFactory->CreatePrime(7, &pPrime);
pPrime->GetNextPrime(&next_prime);

Typically, the system sidesteps this problem by automatically calling the IClassFactory::LockServer(TRUE) method when the first pointer is marshaled to a client. This call forces the component to keep running until the corresponding IClassFactory::LockServer(FALSE) call is made, which COM+ does automatically after the final client disconnects. Notice that this solution relies on the implementation of the IClassFactory interface in the class object. Class objects that implement a custom activation interface instead of IClassFactory cannot take advantage of this built-in functionality. Thus, a class object that does not implement the IClassFactory interface must correctly manage its own lifetime.

The IExternalConnection Interface

When an executable component calls CoRegisterClassObject, COM+ does not immediately marshal the interface pointer. Instead, it calls the IUnknown::AddRef method on the class object and then stores the pointer in an internal class object lookup table. Only when a client calls CoGetClassObject does the system find the interface pointer in the lookup table and then marshal the interface pointer back to the client, where it is subsequently unmarshaled. Since each marshaling operation represents a new client, you need hooks into this marshaling mechanism only to correctly control the lifetime of an executable component. This is where the IExternalConnection interface comes in—it offers complete lifetime control in COM+. The interface has only two methods, as shown here in IDL notation:

interface IExternalConnection : IUnknown
{
    DWORD AddConnection
    (
        [in] DWORD extconn,
        [in] DWORD reserved
    );

    DWORD ReleaseConnection
    (
        [in] DWORD extconn,
        [in] DWORD reserved,
        [in] BOOL  fLastReleaseCloses
    );
}

By implementing the IExternalConnection interface in a custom class object, you can prevent the component from exiting prematurely. When an interface pointer is first marshaled from the component to the client, the stub manager5 calls IUnknown::QueryInterface to request the IExternalConnection interface from the class object. Objects that implement IExternalConnection must explicitly destroy the stub manager before exiting via the CoDisconnectObject6 function. Most class objects do not implement this interface, and thus the stub manager first queries for the IClassFactory interface and, if found, proceeds to call the IClassFactory::LockServer method as discussed previously.

Responding affirmatively to the QueryInterface call with a valid pointer to an implementation of the IExternalConnection interface causes the stub manager to make calls to your methods IExternalConnection::AddConnection and IExternalConnection::ReleaseConnection. The AddConnection method is called whenever an interface pointer is marshaled to a client; the ReleaseConnection method is called when the pointer is released. The extconn parameter passed to the AddConnection and ReleaseConnection methods specifies the type of connection being added or released. The following table lists the valid values for this parameter. Currently, only the EXTCONN_STRONG value is used, indicating that the external connection must keep the object alive until all strong external connections are cleared using the IExternalConnection::ReleaseConnection method.

Connection Type Description
EXTCONN_STRONG Strong connection
EXTCONN_WEAK Weak connection (table, container)
EXTCONN_CALLABLE Table vs. callable connection

By implementing IExternalConnection, an object can obtain a true count of external connections. In this way, you can ensure that a client retaining only a reference to a class object is sufficient to keep the component alive. Here is a relatively standard implementation of IExternalConnection's two methods:

DWORD CPrimeFactory::AddConnection(DWORD extconn, 
    DWORD dwreserved)
{
    if(extconn & EXTCONN_STRONG)
        return CoAddRefServerProcess();
    return 0;
}

DWORD CPrimeFactory::ReleaseConnection(DWORD extconn, 
    DWORD dwreserved, BOOL fLastReleaseCloses)
{
    if(extconn & EXTCONN_STRONG)
    {
        if(CoReleaseServerProcess() == 0 && fLastReleaseCloses)
        {
            // No client references exist at all!
            // Destroy the stub manager.
            CoDisconnectObject(this, 0);
            return 0;
        }
    }
    return 1;
}

You can use the CoLockObjectExternal function to increment or decrement an object's external references as identified by the first parameter. To increment the object's lock count, you simply call CoLockObjectExternal with the second parameter set to TRUE. As a result, the stub manager calls the IExternalConnection::AddConnection method to notify the object of the new external lock. To decrement the lock count, you pass FALSE as the second parameter of the CoLockObjectExternal function; the stub manager will call  IExternalConnection::ReleaseConnection . The third parameter of CoLockObjectExternal determines whether the stub manager should be destroyed when the final external lock is released. If so, the CoDisconnectObject function is called in the IExternalConnection::ReleaseConnection method, as shown above. The declaration of the CoLockObjectExternal function is shown below:

HRESULT __stdcall CoLockObjectExternal(IUnknown* pUnk, 
    BOOL fLock, BOOL fLastUnlockReleases);