[Previous] [Contents] [Next]

Handler Marshaling

Handler marshaling is closely related to standard marshaling. You can think of it as a middle ground between regular standard marshaling and full custom marshaling. Handler marshaling is useful for objects that want to perform some of the work in the client's address space, making remote calls only when absolutely necessary. An object might decide to use handler marshaling for a variety of reasons. Sometimes the overhead of a remote call simply overshadows the amount of work the object will perform. For example, making a remote call simply to add two values, as was shown with the ISum interface, is terribly inefficient. It would be much more efficient to implement that call within an in-process handler.

Objects that want to support handler marshaling implement the interface IStdMarshalInfo instead of IMarshal. The IStdMarshalInfo interface is shown here in IDL notation:

interface IStdMarshalInfo : IUnknown
{
    HRESULT GetClassForHandler
    (
        [in] DWORD dwDestContext,
        [in, unique] void *pvDestContext,
        [out] CLSID *pClsid
    );
}

When marshaling an interface pointer, CoMarshalInterface automatically calls QueryInterface for the IStdMarshalInfo interface. If the object implements IStdMarshalInfo, CoMarshalInterface calls the IStdMarshalInfo::GetClassForHandler method to retrieve the CLSID of the handler object to be loaded into the client's address space. The IStdMarshalInfo::GetClassForHandler method is similar to the IMarshal::GetUnmarshalClass method, which obtains the CLSID of a proxy that is to be loaded in the client's address space for custom marshaling (as described in Chapter 14). The InprocHandler32 subkey in the HKEY_CLASSES_ROOT\CLSID section of the registry references the handler coclass. When the interface pointer is unmarshaled, CoUnmarshalInterface activates the handler object in the client's address space using the CoCreateInstance function. The following code illustrates a standard implementation of the IStdMarshalInfo::GetClassForHandler method:

const CLSID CLSID_InsideCOMHandler = 
    {0x11000006,0x0000,0x0000,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x01};

HRESULT CInsideCOM::GetClassForHandler(DWORD dwDestContext, 
    void* pvDestContext, CLSID* pClsid)
{
    // Load this handler into the client's address space.
    *pClsid = CLSID_InsideCOMHandler;
    return S_OK;
}

In the client's address space, CoCreateInstance is called with the CLSCTX_ INPROC_HANDLER flag to instantiate the handler. The CLSCTX_INPROC_ HANDLER flag indicates that the handler coclass must be registered using the InprocHandler32 subkey. Handlers, like regular proxies and stubs, should be registered with the ThreadingModel = Both setting so that they are always loaded into the apartment of their creator. The REG_DATA structure for registering the handler is shown below:

const REG_DATA g_regData[] = {
    { "CLSID\\{11000006-0000-0000-0000-000000000001}", 0, 
        "InsideCOM Handler" },
    { "CLSID\\{11000006-0000-00000000000000000001}\\InprocHandler32", 0, 
        (const char*)-1 },
    { "CLSID\\{11000006-0000-0000-0000-000000000001}\\InprocHandler32", 
        "ThreadingModel", "Both" },
    { 0, 0, 0 }
};

As you'd expect, CoCreateInstance calls the handler's DllGetClassObject function, followed by its IClassFactory::CreateInstance method. Using this CreateInstance method, CoUnmarshalInterface informs the handler that it is being aggregated and passes it a pointer to the controlling IUnknown interface implemented by the system identity object, to which the handler should delegate its own implementation of IUnknown. While the IClassFactory::CreateInstance method executes, the handler should create an aggregated standard marshaler (proxy manager) using the CoGetStdMarshalEx function.

When the CoGetStdMarshalEx function is called by the handler, it takes a pointer to the controlling IUnknown interface provided by the CreateInstance method and the SMEXF_HANDLER flag, which indicates that the function is being called from the client's address space, as shown in the following code fragment. The third parameter of the CoGetStdMarshalEx function returns a pointer, named m_pUnknownInner here, to the IUnknown interface implemented by the proxy manager. Through this pointer, the handler can communicate with the interface proxy and therefore with the actual object in the component's address space.

IUnknown* m_pUnknownInner = 0;
CoGetStdMarshalEx(pUnknownOuter, SMEXF_HANDLER, 
    &m_pUnknownInner);

Figure 15-5 shows how the identity object aggregates the handler and how the handler, in turn, aggregates the proxy manager. Of course, the proxy manager aggregates the interface proxy as dictated by standard marshaling. The identity object, also called the controlling unknown, is a built-in system object that gives the system control over the lifetime and the identity of the standard marshaler.

Click to view at full size.

Figure 15-5. Lightweight client-side handlers are sandwiched between the proxy manager and the identity object.

Recall that the real goal of most handlers is to enable some work to be done in the client's address space while delegating other work to the actual object in the component's address space. With this in mind, a handler's implementation of the ISum::Sum method might perform some calculations in-process while delegating others to the InsideCOM object via the m_pUnknownInner pointer. Because the proxy manager aggregates the interface proxy, the handler can obtain a pointer to the ISum interface implemented by the interface proxy simply by calling QueryInterface on the m_pUnknownInner pointer, which is actually a pointer to the proxy manager. This concept is illustrated in the following code, in which the handler's implementation of the Sum method adds only numbers smaller than 50. For summations involving values of 50 or higher, the method executes a cross-process or possibly even a cross-machine call to the InsideCOM object running in the component's address space.

HRESULT CInsideCOM::Sum(int x, int y, int* retval)
{
    if(x > 50 || y > 50)
    {
        // Numbers bigger than 50 had better call the 
        // real object.
        ISum* pSum = 0;

        // Ask the proxy manager for a pointer to the ISum 
        // interface proxy.
        m_pUnknownInner->QueryInterface(IID_ISum, 
            (void**)&pSum);

        // Now call the interface proxy's ISum::Sum method.
        // This results in a call the object's Sum method.
        HRESULT hr = pSum->Sum(x, y, retval);

        pSum->Release();
        return hr;
    }

    // Numbers smaller than 50 need not call the object.
    // We'll do the work right here in the handler.
    *retval = x + y;
    return S_OK;
}

If you compare Figure 15-5 and Figure 15-1, you'll notice that the IMultiQI and IClientSecurity interfaces implemented by the proxy manager in Figure 15-1 are missing in Figure 15-5, where the IInternalUnknown is implemented instead. The proxy manager is a very sensitive object, and it does not like handlers to blindly expose its internal interfaces. To prevent this, when the proxy manager is aggregated by a handler via the CoGetStdMarshalEx function, it does not expose some of its internal interfaces via the standard IUnknown::QueryInterface method. Instead, handlers that require access to internal interfaces implemented by the proxy manager go through the IInternalUnknown interface, which is shown below in IDL notation. The IInternalUnknown::QueryInternalInterface method, which has the same semantics as the regular IUnknown::QueryInterface method, allows the handler to obtain access to these hidden interfaces.

interface IInternalUnknown : IUnknown
{
    HRESULT QueryInternalInterface(
        [in]  REFIID riid,
        [out] void** ppv);
}

Another variation on handler marshaling allows the object to return extra data to the handler via the marshaling packet. The object can use this technique to initialize the handler with extra data. In this case, the object must implement both the IStdMarshalInfo and IMarshal interfaces. The object aggregates the standard marshaler by calling CoGetStdMarshalEx with the flag SMEXF_SERVER, indicating that you want to aggregate the stub manager. The first part of the marshaling operation is delegated to the standard marshaler, but the object must add the size of the data it wants to send to the client to the value returned from the IMarshal::GetMarshalSizeMax method. After delegating to the standard marshaler's IMarshal::MarshalInterface method, the object writes its own extra data into the marshaling stream. This architecture is shown in Figure 15-6.

Click to view at full size.

Figure 15-6. The InsideCOM object aggregates the standard marshaler.

In the client's process, the handler must read the extra data stored by the object in the marshaling stream. The handler implements the IMarshal interface by delegating to the proxy manager's implementation of the IMarshal:: UnmarshalInterface method and then reading the extra data that remains.

Of course, you could build this type of example using the standard marshaling technique described earlier in this chapter. Notice, however, how much easier it is to use handler marshaling to override certain aspects of standard marshaling. We don't have to implement or call any of the standard marshaling interfaces, such as IPSFactoryBuffer, IRpcProxyBuffer, IRpcStubBuffer, or IRpcChannelBuffer. Of course, a standard proxy/stub DLL is still needed, but you can fulfill this requirement using the type library marshaler or MIDL-generated marshaling code.