[Previous] [Contents] [Next]

The Standard Marshaling Architecture

In standard marshaling, COM+ provides a generic proxy object, called the proxy manager, and a generic stub object, sometimes called the stub manager. These communicate through an RPC-based channel. When you marshal a custom interface pointer, the standard marshaling architecture lets you plug in an interface marshaler that knows how to marshal that specific interface; proxies and stubs for most of the standard interfaces are loaded from ole32.dll. When loaded, interface proxies are always aggregated with the proxy manager, giving clients the illusion that all of the interfaces are exposed from a single object and thus preserving the identity of the object even when it is remoted to another address space. Without this feature, there would be no way to distinguish between calls to the same interfaces implemented on entirely different objects.

A client holding a pointer to the proxy manager's IUnknown interface believes that it holds a pointer to the actual object in the component's address space because the proxy manager represents the object in the client process. The proxy manager appears to implement the interfaces of the actual object, but this bit of subterfuge is supported by interface proxies that are aggregated into the proxy manager. The proxy manager also implements IMarshal, the fundamental marshaling interface of COM+. While complex, this general purpose mechanism is required because the standard marshaler cannot know in advance what custom interfaces a particular object might implement.

To see how standard marshaling fits into the custom marshaling model and to provide some context for the upcoming discussion of standard marshaling for the ISum interface, let's review the code executed by the prototypical client application:

// client.cpp

// Start your engines.
CoInitializeEx(NULL, COINIT_MULTITHREADED);

// Get a pointer to the object's class factory.
IClassFactory* pClassFactory;
CoGetClassObject(CLSID_InsideCOM, CLSCTX_LOCAL_SERVER, 
    NULL, IID_IClassFactory, (void**)&pClassFactory);

// Instantiate the InsideCOM object
// and get a pointer to its ISum interface.
ISum* pSum = 0;
pClassFactory->CreateInstance(NULL, IID_ISum, (void**)&pSum);

// Client uses pSum...

The client calls IClassFactory::CreateInstance to request that the InsideCOM object be instantiated and a pointer to ISum be returned. In the component's address space, the stub code for IClassFactory::CreateInstance realizes that the ISum interface pointer about to be returned to the client must be marshaled. The stub then calls the CoMarshalInterface function to marshal the ISum interface pointer. CoMarshalInterface queries the InsideCOM object for the IMarshal interface to determine whether the object wants standard or custom marshaling. By the simple act of returning E_NOINTERFACE in the QueryInterface call for the IMarshal interface, we tell the system of our desire to use standard marshaling.

Having determined that our object wants standard marshaling, CoMarshalInterface calls the CoGetStandardMarshal function to obtain a pointer to the system-provided implementation of the IMarshal interface; this is the standard marshaler. CoMarshalInterface uses the default implementation of IMarshal to call the IMarshal::GetUnmarshalClass method in order to determine the CLSID of the proxy coclass that should be loaded into the client's address space. In the case of the standard marshaler, the CLSID provided is that of the proxy manager.

The CLSID returned by GetUnmarshalClass is written into the marshaling stream, along with any other data the standard marshaler provides in its implementation of the IMarshal::MarshalInterface method. The marshaling stream is returned to the client's address space, where the proxy code for the IClassFactory::CreateInstance method calls the CoUnmarshalInterface function. CoUnmarshalInterface reads the CLSID from the marshaling stream and instantiates the object by calling CoCreateInstance. This loads the specified proxy in the client's address space; in the case of standard marshaling, this has the effect of creating the proxy manager. CoUnmarshalInterface then calls the IMarshal:: UnmarshalInterface method in the proxy manager. The proxy manager uses the interface identifier (IID) of the interface being marshaled, which is provided by UnmarshalInterface, to load the interface proxy registered for that interface. You are responsible for implementing the interface proxy and the interface stub in the standard marshaling model.

When the proxy manager instantiates a new interface proxy, it provides the interface proxy with a pointer to the proxy manager's implementation of IUnknown, to which the interface proxy delegates all QueryInterface, AddRef, and Release calls as dictated by the aggregation model. Each interface proxy implements two interfaces: the custom interface it represents and IRpcProxyBuffer. The custom interface is exposed directly to clients, which obtain access to the interface through the QueryInterface mechanism; the proxy manager uses the IRpcProxyBuffer interface to communicate with the interface proxy. The standard marshaling architecture is depicted in Figure 15-1.

Click to view at full size.

Figure 15-1. The standard marshaling architecture.

The client calls QueryInterface to obtain an ISum interface exposed by the interface proxy object. The interface proxy is part of the greater proxy manager; it communicates with the InsideCOM object through the IRpcChannelBuffer interface implemented by the standard marshaling channel. On the server side, the channel communicates with the interface stub through the IRpcStubBuffer interface, which in turn calls the actual InsideCOM object. The stub manager represents the client in the object's address space; because the stub manager does not need to present a unified identity to any clients, interface stubs are not aggregated with the stub manager.1

The Service Control Manager (SCM) knows which proxy/stub DLL to load based on the IID of the interface pointer returned to the client. In the HKEY_CLASSES_ROOT\Interface section of the registry, the IID must be declared along with the subkey ProxyStubClsid32. This subkey must contain the CLSID of the proxy/stub component for this interface. The SCM then proceeds to the HKEY_CLASSES_ROOT\CLSID section of the registry to locate the CLSID and the InprocServer32 value specifying the name of the proxy/stub DLL to load. Notice that with respect to the HKEY_CLASSES_ROOT\CLSID section of the registry, proxy/stub DLLs are not treated any differently than regular components.

Figure 15-2 illustrates the registry scenario for a proxy/stub DLL configured to marshal an interface. In this case, the interface uses the type library marshaler. (The numbers indicate the order in which the SCM traverses the registry.) Remember that type library marshaling is built on top of standard marshaling, and thus the registry settings follow the same rules.

Click to view at full size.

Figure 15-2. The order in which the SCM scours the registry to find a proxy/stub DLL for standard marshaling.

The Standard Marshaling Interfaces

Standard marshaling involves four interfaces: IPSFactoryBuffer, IRpcProxyBuffer, IRpcStubBuffer, and IRpcChannelBuffer. The proxy/stub DLL is itself an in-process component that implements the IPSFactoryBuffer, IRpcProxyBuffer, and IRpcStubBuffer interfaces. The IRpcChannelBuffer interface is implemented by a built-in object called the channel, which is provided by the standard marshaler and is called by the proxy/stub DLL for interprocess communication. But before we launch into a detailed explanation of these interfaces and the ways in which they are used, take a moment to peruse Figure 15-3.

Click to view at full size.

Figure 15-3. How a proxy/stub DLL turns into a proxy in the client's address space and into a stub in an object's address space.

Figure 15-3 shows how proxy and stub objects start from the same code base and then diverge. In both the client and server address spaces, the proxy/stub coclass's class object implements the IPSFactoryBuffer interface in place of the standard IClassFactory interface typically implemented by class objects. Then something dramatic happens. In the client's address space, the IPSFactoryBuffer::CreateProxy method is called to create an interface proxy object that implements the IRpcProxyBuffer interface; in the server's address space, the IPSFactoryBuffer::CreateStub method is called to instantiate an interface stub object that implements the IRpcStubBuffer interface. From there, the proxy calls IRpcProxyBuffer::Connect and then communicates with the stub using the IRpcChannelBuffer interface. In response to a request by the proxy, the stub is awakened using the IRpcStubBuffer::Invoke method.

The IPSFactoryBuffer Interface

Proxy/stub DLLs that support standard marshaling are themselves COM+ objects, but their class objects do not need to implement the IClassFactory interface. Recall that clients typically call the IClassFactory::CreateInstance method to instantiate objects in a component. Unlike the method IClassFactory::CreateInstance, a proxy/stub DLL does not instantiate a single coclass; it can instantiate a proxy or a stub object for a particular interface. Thus, instead of supporting IClassFactory, the class objects of proxy/stub coclasses implement the IPSFactoryBuffer interface.

IPSFactoryBuffer is the interface through which proxies and stubs are created. Every proxy/stub DLL must implement the IPSFactoryBuffer interface on a class object accessible through its DllGetClassObject entry point. As shown in the following code, the implementation of the DllGetClassObject function in a proxy/stub DLL is not substantially different from that of a normal component:

HRESULT __stdcall DllGetClassObject(REFCLSID clsid, 
    REFIID riid, void** ppv)
{
    // This is a proxy/stub object.
    if(clsid != CLSID_InsideCOMStdProxy)
        return CLASS_E_CLASSNOTAVAILABLE;

    // Create the PSFactoryBuffer object.
    CPSFactoryBuffer* pPSFactoryBuffer = new CPSFactoryBuffer;
    if(pPSFactoryBuffer == NULL)
        return E_OUTOFMEMORY;

    // Calling QueryInterface for IPSFactoryBuffer
    return pPSFactoryBuffer->QueryInterface(riid, ppv);
}

As described earlier in this chapter, the SCM consults the registry for the ProxyStubClsid32 subkey of the HKEY_CLASSES_ROOT\Interface entry to get the CLSID of the coclass that provides for the interface's marshaling needs. DllGetClassObject is then called to request the IPSFactoryBuffer interface. Here is the IPSFactoryBuffer interface declared in IDL notation:

interface IPSFactoryBuffer : IUnknown
{
    HRESULT CreateProxy
    (
        [in] IUnknown* pUnkOuter,
        [in] REFIID riid,
        [out] IRpcProxyBuffer** ppProxy,
        [out] void** ppv
    );

    HRESULT CreateStub
    (
        [in] REFIID riid,
        [in, unique] IUnknown* pUnkServer,
        [out] IRpcStubBuffer** ppStub
    );
}

From this single interface, the gender of a proxy/stub DLL is decided. In the client's address space, IPSFactoryBuffer::CreateProxy is called to instantiate the proxy object, and in the component's address space, the same DLL is loaded but IPSFactoryBuffer::CreateStub is called. The client-side pseudo-code for the steps executed by the proxy manager after locating the ProxyStubClsid32 in the registry is shown here:

clsid = LookUpInRegistry(riid);
CoGetClassObject(clsid, 
    CLSCTX_INPROC_HANDLER|CLSCTX_INPROC_SERVER, 
    NULL, IID_IPSFactoryBuffer, (void**)&pPSFactoryBuffer);
pPSFactoryBuffer->CreateProxy(pUnkOuter, riid, &pRpcProxyBuffer, 
    (void**)&ppv);

In the object's address space, the stub manager executes the following pseudo-code:

clsid = LookUpInRegistry(riid);
CoGetClassObject(clsid, 
    CLSCTX_INPROC_HANDLER|CLSCTX_INPROC_SERVER, 
    NULL, IID_IPSFactoryBuffer, (void**)&pPSFactoryBuffer);
pPSFactoryBuffer->CreateStub(riid, pUnkServer, &pRpcStubBuffer);

In our proxy/stub DLL, the IPSFactoryBuffer::CreateProxy method is written to instantiate a new CRpcProxyBuffer object, as shown in the code on below. CRpcProxyBuffer is the class that actually implements the IRpcProxyBuffer interface as well as the ISum custom interface that we are marshaling in this DLL. Notice that when a new interface proxy is instantiated using the CreateProxy method, it receives as its first parameter a pointer to the IUnknown interface of the proxy manager, to which the interface proxy must delegate all QueryInterface, AddRef, and Release calls. This is how the proxy manager aggregates interface proxies into its identity. In the following code, we simply pass this interface pointer to the constructor of the CRpcProxyBuffer class, where it is stored for use in the methods of the IUnknown interface.

HRESULT CPSFactoryBuffer::CreateProxy(IUnknown* pUnknownOuter, 
    REFIID riid, IRpcProxyBuffer** ppProxy, void** ppv)
{
    // Create the proxy object; pUnknownOuter is the 
    // proxy manager.
    CRpcProxyBuffer* pRpcProxyBuffer = 
        new CRpcProxyBuffer(pUnknownOuter);

    // Code omitted...

}

As with standard aggregation,2 the goal is to have the proxy present a single identity to the client. To achieve this, the object must delegate any IUnknown method calls to the outer object; this is why the IPSFactoryBuffer::CreateProxy method provides the pUnknownOuter parameter. The delegating implementation of IUnknown is the one seen by all external clients. In addition to the delegating implementation of IUnknown, the interface proxy must also implement a second, nondelegating version of IUnknown for use only by the outer object. Standard aggregation requires the outer object to first request the inner object's IUnknown interface via the IClassFactory::CreateInstance method so that the inner object can return a pointer to its nondelegating version of the IUnknown interface.

Unfortunately for us, proxy/stub DLLs don't implement the IClassFactory activation interface—they implement IPSFactoryBuffer instead. To add insult to injury, the IPSFactoryBuffer::CreateProxy method does not request the interface proxy's IUnknown interface first; it requests the IRpcProxyBuffer interface. So in place of a nondelegating version of IUnknown, we must implement a nondelegating version of the IRpcProxyBuffer interface. The code below shows the declaration of the INoAggregationRpcProxyBuffer custom interface that mimics the v-table layout of the real IRpcProxyBuffer interface but uses a custom implementation of IUnknown whose methods do not delegate to the proxy manager:

interface INoAggregationRpcProxyBuffer
{
    virtual HRESULT __stdcall QueryInterface_NoAggregation(
        REFIID riid, void** ppv)=0;
    virtual ULONG __stdcall AddRef_NoAggregation()=0;

    virtual ULONG __stdcall Release_NoAggregation()=0;
    virtual HRESULT __stdcall Connect(
        IRpcChannelBuffer* pRpcChannel)=0;
    virtual void __stdcall Disconnect(void)=0;
};

Next the code in the IPSFactoryBuffer::CreateProxy method calls the nondelegating version of the QueryInterface method for a pointer to the ISum interface. This is the ISum interface pointer that is returned to the client. Now you can see how a v-table is fabricated in the client's address space—a fake object implementing the custom interface is created so the client has an in-process object on which to call methods. Finally, the code calls the nondelegating version of QueryInterface so that a pointer to the nondelegating version of the IRpcProxyBuffer interface implemented by the interface proxy can be returned to the proxy manager:

HRESULT CPSFactoryBuffer::CreateProxy(IUnknown* pUnknownOuter, 
    REFIID riid, IRpcProxyBuffer** ppProxy, void** ppv)
{
    // Create the proxy object; pUnknownOuter is the 
    // proxy manager.
    CRpcProxyBuffer* pRpcProxyBuffer = 
        new CRpcProxyBuffer(pUnknownOuter);

    // Call QueryInterface for IID_ISum.
    HRESULT hr = pRpcProxyBuffer->QueryInterface_NoAggregation(
        riid, ppv);

    // Return a pointer to the IRpcProxyBuffer interface.
    return pRpcProxyBuffer->QueryInterface_NoAggregation(
        IID_IRpcProxyBuffer, (void**)ppProxy);
}

Meanwhile, on the server-side, the IPSFactoryBuffer::CreateStub method is called to instantiate the CRpcStubBuffer class, which then implements the IRpcStubBuffer interface. The code below shows how the CreateStub method receives a pointer to the InsideCOM object for later use by the interface stub when it needs to call the object. CreateStub also provides the IID of the interface we are marshaling—in this case, IID_ISum. This IID is passed to the CRpcStubBuffer class's constructor for safekeeping by the stub. The IRpcStubBuffer::Connect method is then called to provide the interface stub with a pointer to this object. Finally, the code calls QueryInterface for IRpcStubBuffer so that a pointer to the interface stub can be returned to the stub manager.

HRESULT CPSFactoryBuffer::CreateStub(REFIID riid, 
    IUnknown* pUnkServer, IRpcStubBuffer** ppStub)
{
    // Create the stub object.
    CRpcStubBuffer* pRpcStubBuffer = new CRpcStubBuffer(riid);

    // Give the stub the pointer to the object.
    pRpcStubBuffer->Connect(pUnkServer);

    // Return a pointer to the IRpcStubBuffer interface.
    return pRpcStubBuffer->QueryInterface(IID_IRpcStubBuffer, 
        (void**)ppStub);
}

The IRpcProxyBuffer Interface

The IRpcProxyBuffer interface is the primary interface implemented by an interface proxy. It is the interface through which the proxy manager talks to an interface proxy that it has aggregated. The IRpcProxyBuffer interface is shown here in IDL notation:

interface IRpcProxyBuffer : IUnknown
{
    // Here is a pointer to the channel so you can talk 
    // to the stub.
    HRESULT Connect
    (
        [in, unique] IRpcChannelBuffer *pRpcChannelBuffer
    );

    // Disconnect from the stub.
    void Disconnect
    (
        void
    );
}

Connect is obviously the more interesting of IRpcProxyBuffer's two methods. It is called by the proxy manager shortly after the interface proxy is created in the IPSFactoryBuffer::CreateProxy method. The first (and only) argument for Connect is a pointer to a channel object implementing the IRpcChannelBuffer interface through which the proxy communicates with the stub. The implementation of Connect can be as simple as this:

HRESULT CRpcProxyBuffer::Connect(IRpcChannelBuffer* pRpcChannel)
{
    // Store the pointer to the channel.
    m_pRpcChannel = pRpcChannel;

    // AddRef the pointer.
    m_pRpcChannel->AddRef();
    return S_OK;
}

This code simply stores the IRpcChannelBuffer pointer in a member variable and then calls AddRef as dictated by the COM+ reference counting rules. This pointer will be used whenever the client calls a method of the object. As we've seen, the client thinks it has a pointer to the object, but it in fact has only a pointer to the proxy. Therefore, all client calls actually end up in the proxy; from there, the request is forwarded to the stub using the IRpcChannelBuffer pointer.

The IRpcProxyBuffer::Disconnect method notifies the proxy that it is no longer connected to the stub. At this stage, the RPC channel must be released to counteract the AddRef method called in IRpcProxyBuffer::Connect. A sample implementation of the Disconnect method is shown here:

void CRpcProxyBuffer::Disconnect()
{
    // Release the channel pointer and set it to NULL.
    m_pRpcChannel->Release();
    m_pRpcChannel = NULL;
}

The IRpcStubBuffer Interface

Like IRpcProxyBuffer, IRpcStubBuffer is the main interface of an interface stub. The stub manager dynamically loads the interface stub into the component's address space and uses the IRpcStubBuffer interface to communicate with it. The following code shows the IRpcStubBuffer interface in IDL notation:

interface IRpcStubBuffer : IUnknown
{
    // Connect to the object.
    HRESULT Connect([in] IUnknown* pUnkServer);

    // Disconnect from the object.
    void Disconnect();

    // Call a method of the object.
    HRESULT Invoke
    (
        [in] RPCOLEMESSAGE* _prpcmsg,
        [in] IRpcChannelBuffer* _pRpcChannelBuffer
    );

    // Do you support other interfaces?
    IRpcStubBuffer* IsIIDSupported([in] REFIID riid);

    // How many references are you holding?
    ULONG CountRefs(void);

    HRESULT DebugServerQueryInterface(void** ppv);

    void DebugServerRelease(void* pv);
}

The IRpcStubBuffer::Connect method provides the interface stub with a pointer to the actual object in the component's address space to which the stub is connected. The stub forwards all client requests from the proxy to this object. To obtain the correct interface pointer for this object, the implementation of IRpcStubBuffer::Connect should call QueryInterface for the interface we are marshaling—in this case, ISum. As shown in the following code, QueryInterface takes care of the need to call AddRef. The resulting pointer is stored for later use in the m_pObj member variable.

HRESULT CRpcStubBuffer::Connect(IUnknown* pUnknown)
{
    // Need to keep track of component references for later.
    m_cConnection++;

    // Get a pointer to the interface we are marshaling.
    return pUnknown->QueryInterface(m_iid, (void**)&m_pObj);
}

The IRpcStubBuffer::IsIIDSupported method is called to determine if a stub is designed to handle the unmarshaling of a particular interface, as shown in the code on the below. When a new interface pointer is remoted on a given object, the existing interface stubs are first queried using the IsIIDSupported method in an attempt to locate a stub that can handle marshaling for the interface before the system goes to the trouble of creating a new interface stub. An interface stub is usually designed to support only one interface. In such cases, the IsIIDSupported method should simply check whether the requested interface is the one the stub was designed to handle. If it is, the method should return true; otherwise, it should return false. If the interface stub supports multiple interfaces, the IsIIDSupported method should check whether the IID provided is one of the supported ones. If so, IsIIDSupported should return an appropriate IRpcStubBuffer pointer for that interface.

IRpcStubBuffer* CRpcStubBuffer::IsIIDSupported(REFIID riid)
{
    // Do we support this IID?
    if(riid == m_iid)
        return (IRpcStubBuffer*)true;
    return (IRpcStubBuffer*)false;
}

The IRpcStubBuffer::CountRefs method returns the total number of references the stub is holding on the component's object, as shown in the following code. The counter value returned should be incremented in every call to IRpcStubBuffer::Connect and decremented in IRpcStubBuffer::Disconnect.

ULONG CRpcStubBuffer::CountRefs()
{
    // Return component connection reference count.
    return m_cConnection;
}

IRpcStubBuffer::Disconnect informs the stub that it should disconnect from the component's object. The following implementation of Disconnect releases the object reference stored in m_pObj and then decrements the connection counter m_cConnection:

void CRpcStubBuffer::Disconnect()
{
    // Release our reference on the object.
    m_pObj->Release();
    m_cConnection--;
}

The DebugServerQueryInterface and DebugServerRelease methods of the IRpcStubBuffer interface are designed to support debuggers that provide the ability to step into remote invocations on objects. For our purposes, the implementation shown in the following code is sufficient.3

HRESULT CRpcStubBuffer::DebugServerQueryInterface(void**)
{
    return E_NOTIMPL;
}

void CRpcStubBuffer::DebugServerRelease(void*)
{
}

At this stage, the proxy and stub are fully set up, and the client can make calls into the component through the marshaling code. When a call is made by the interface proxy through the channel, it is received by the stub in the IRpcStubBuffer::Invoke method. To better understand how it works, we'll defer our discussion of the Invoke method until after we discuss the IRpcChannelBuffer interface.

The IRpcChannelBuffer Interface

At the core of the functionality offered by standard marshaling is the built-in channel, through which a proxy communicates with its corresponding stub. Conceptually, the channel provides a magic box into which the proxy puts data in the client's address space. When the proxy tells the channel to send the data, the channel transmits the data to the component's address space and wakes up the stub, regardless of the nature of the boundaries separating those two processes. The great thing about the channel is that it completely handles the transmission of marshaled data between processes, regardless of whether the processes are on the same machine or on different machines connected over a network.

The channel implements the IRpcChannelBuffer interface; the proxy and stub obtain this interface through the IRpcProxyBuffer::Connect and IRpcStubBuffer::Invoke methods, respectively. The IRpcChannelBuffer interface is internal to and implemented by the COM+ remoting infrastructure and is not available outside of the standard marshaling interfaces. Here is the IRpcChannelBuffer interface in IDL notation:

interface IRpcChannelBuffer : IUnknown
{
    // Allocate a transmission buffer.
    HRESULT GetBuffer
    (
        [in] RPCOLEMESSAGE* pMessage,
        [in] REFIID riid
    );

    // Invoke a method and wait for it to return.
    HRESULT SendReceive
    (
        [in, out] RPCOLEMESSAGE* pMessage,
        [out] ULONG* pStatus
    );

    // Free the transmission buffer.
    HRESULT FreeBuffer([in] RPCOLEMESSAGE* pMessage);

    // What is the nature of the boundary between the 
    // proxy and stub?
    HRESULT GetDestCtx
    (
        [out] DWORD* pdwDestContext,
        [out] void** ppvDestContext
    );

    // Am I connected to the stub?
    HRESULT IsConnected(void);
}

The RPCOLEMESSAGE Data Structure

Of the five IRpcChannelBuffer methods, we'll focus initially on the GetBuffer, SendReceive, and FreeBuffer methods. Common to these three methods is the RPCOLEMESSAGE data structure—the structure actually transmitted from the proxy to the stub and back. This data structure is declared as shown here:

typedef struct tagRPCOLEMESSAGE
    {
        void              *reserved1;
        RPCOLEDATAREP     dataRepresentation;
        void              *Buffer;
        ULONG             cbBuffer;
        ULONG             iMethod;
        void              *reserved2[5];
        ULONG             rpcFlags;
    } RPCOLEMESSAGE;

The most significant field in this structure is the Buffer pointer. The proxy packs the parameters of a method into this buffer and then transmits it to the stub. The cbBuffer member of the RPCOLEMESSAGE data structure specifies the current size in bytes of the marshaling buffer. Memory is allocated for the Buffer pointer by a call to the IRpcChannelBuffer::GetBuffer method, not by the proxy. The GetBuffer method uses the value of the cbBuffer field to determine how much memory to allocate for the channel's transmission buffer.

The dataRepresentation field of the RPCOLEMESSAGE structure is an unsigned long (4-byte) value defined as the RPCOLEDATAREP type. This field contains the Network Data Representation (NDR) format label that identifies the data formats used to represent primitive values. The stub can use this value to identify the particular variation of NDR transfer syntax being used. Figure 154 shows the layout of this 4-byte structure.

Figure 15-4. The layout of the RPCOLEMESSAGE dataRepresentation field.

The bit flags that you can set in the format label are shown in the following table.

Data Type Value Format
Character representation 0 ASCII
1 EBCDIC
Integer and floating-point byte order 0 Big endian
1 Little endian
Floating-point representation 0 IEEE
1 VAX
2 Cray
3 IBM

Let's pause and retrace our steps back to the client to see how all these pieces fit together. To retrieve an interface pointer from the component, the client executes the following code:

ISum* pSum;
HRESULT hr = pUnknown->QueryInterface(IID_ISum, (void**)&pSum);
int retval;
hr = pSum->Sum(2, 7, &retval);

The QueryInterface call actually returns the pointer to the proxy's implementation of ISum. When the client calls the ISum::Sum method, the proxy's implementation of this method is invoked in the client's address space. At this stage, the proxy must pack the necessary method parameters into a buffer for transmission to the stub. Here is the proxy's implementation of the Sum method, which is invoked when the client calls ISum::Sum:

HRESULT CRpcProxyBuffer::Sum(int x, int y, int* retval)
{
    RPCOLEMESSAGE Message = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    ULONG status;

    // Specify how much memory to allocate for Message.Buffer.
    Message.cbBuffer = sizeof(int)*2;

    // Allocate the memory for Message.Buffer.
    m_pRpcChannel->GetBuffer(&Message, IID_ISum);

    // Marshal the Sum method's x and y arguments.
    ((int*)Message.Buffer)[0] = x;
    ((int*)Message.Buffer)[1] = y;

    // Set the v-table entry for ISum::Sum.
    Message.iMethod = 3;

    // Send the packet to the stub.
    m_pRpcChannel->SendReceive(&Message, &status);

    // Now the result is available.
    *retval = ((int*)Message.Buffer)[0];

    // Free the memory used for Message.Buffer.
    m_pRpcChannel->FreeBuffer(&Message);
    return S_OK;
}

The marshaling of method arguments takes place in the CRpcProxyBuffer::Sum method shown above. This surrogate implementation of the Sum method declares a variable named Message of type RPCOLEMESSAGE. Message.cbBuffer is then set to the size of two integers—the total space needed to store the two in arguments accepted by the Sum method. IRpcChannelBuffer::GetBuffer is called to allocate the memory required for the transmission buffer. The following simple code marshals both the x and y arguments of the Sum method into the allocated buffer:

// Marshal the Sum method's x and y arguments.
((int*)Message.Buffer)[0] = x;
((int*)Message.Buffer)[1] = y;

Note that if any interfaces were transmitted as method arguments, they would also have to be packed into the RPCOLEMESSAGE structure. Of course, actual interface pointers would be useless to the recipient, so you should call CoMarshalInterface to marshal the interface into a stream and then transmit the contents of the stream in the RPCOLEMESSAGE structure. The stub will need to pass the stream to the CoUnmarshalInterface function to load a proxy and obtain a usable interface pointer.

Before the marshaled data packet can be sent to the proxy, you must indicate which method of the ISum interface is being called. Recall that ISum has four methods—the three methods of IUnknown plus ISum::Sum. The iMethod field of the RPCOLEMESSAGE structure specifies the desired method. This value is a zero-based number for the desired method's v-table entry in the interface. In the case of ISum, the order of the entries in the v-table is QueryInterface, AddRef, Release, and finally Sum. Thus, Sum exists as the third method in a zero-based enumeration of the v-table entries, as shown here:

// Set the v-table entry for ISum::Sum.
Message.iMethod = 3;

Now we're ready to send the marshaled data packet from the proxy to the stub. The SendReceive method is the heart of the IRpcChannelBuffer interface. Using the RPC infrastructure, this two-way method transmits the entire RPCOLEMESSAGE structure to the stub. The proxy then goes to sleep while it waits for the actual method to execute and for the return values to be sent back through the RPC channel to the proxy. In the interface stub, the IRpcStubBuffer::Invoke method is called automatically when data is received in the channel from the proxy as the result of a call to SendReceive.

In the IRpcStubBuffer::Invoke method, the stub first determines which method in the interface is being called. The iMethod field of the RPCOLEMESSAGE structure provides this crucial information. If it doesn't know which method is being called, the stub has no idea how to interpret the data in the channel. In the following code, a switch statement is used to execute the correct code for the specified method being invoked. In this case, we check only for the third method—the Sum method.

HRESULT CRpcStubBuffer::Invoke(RPCOLEMESSAGE* pMessage, 
    IRpcCha~nnelBuffer* pRpcChannel)
{
    // What method of ISum is the proxy calling?
    switch(pMessage->iMethod)
    {
    case 3: // The proxy is calling ISum::Sum.
        int result;

        // Calling the Sum method!!!
        m_pObj->Sum(((int*)pMessage->Buffer)[0], 
            ((int*)pMessage->Buffer)[1], &result);

        // How much memory is needed for the return value?
        pMessage->cbBuffer = sizeof(int);

        // Free the proxy buffer and allocate a new one.
        pRpcChannel->GetBuffer(pMessage, m_iid);

        // Pack the return value into the buffer.
        ((int*)pMessage->Buffer)[0] = result;

        // Take it away...
        return NOERROR;

    // case n: Other methods here...
    }
    return E_UNEXPECTED;
}

By using the m_pObj member variable that points to the actual Sum interface in the component, we can call the real Sum method. The two parameters, x and y, are retrieved from the buffer packed by the proxy. For efficiency's sake, the Sum method is passed direct pointers into the message buffer; no copy of the data is made. Now the real Sum method executes, adds the two values, and returns the result to the stub. The stub then transmits the method's out parameters back to the proxy. First, the cbBuffer field is set to the size (in bytes) of the memory required for the out parameters. Then the IRpcChannelBuffer::GetBuffer method is called to allocate the memory needed to pack the out parameters for transmission. Before allocating a reply buffer, the GetBuffer method automatically frees the memory buffer allocated for the proxy's buffer. Then the result of the Sum method is stored in the transmission buffer. Returning from IRpcStubBuffer::Invoke sends the RPCOLEMESSAGE structure to the proxy.

In the proxy, the IRpcChannelBuffer::SendReceive function returns and the RPCOLEMESSAGE structure contains the data returned by the stub. The proxy unpacks the out parameters from the buffer and copies them into variables returned to the client, as shown in the following code. Finally, the IRpcChannelBuffer::FreeBuffer method is called to free the buffer containing the marshaled data packet from the stub.

// Send the packet to the stub.
m_pRpcChannel->SendReceive(&Message, &status);
// Now the result is available.
*retval = ((int*)Message.Buffer)[0];

// Free the memory used for Message.Buffer.
m_pRpcChannel->FreeBuffer(&Message);

You can use the IRpcChannelBuffer::GetDestCtx method to obtain the destination context information for this interface. The destination context is identified by one of the MSHCTX enumeration constants, which describe the barrier between the object and its client. You can use IsConnected, the last method of the IRpcChannelBuffer interface, to determine whether the channel is still connected to the other side. Interface proxies can use this method as an optimization for quickly returning an error to the client if the proxy is not connected to the stub. This technique saves the time wasted by attempting a call to IRpcChannelBuffer::SendReceive simply to receive the E_RPCFAULT error.

Registering the Proxy/Stub DLL

Although proxy/stub DLLs are full-fledged COM+ components, they are registered a little differently from more standard components. First, a proxy/stub DLL has no need for a program identifier (ProgID) entry in the registry because it is unlikely that any code aside from the marshaling infrastructure will want to instantiate a proxy or a stub object. Second, proxy/stub DLLs must have the special subkey ProxyStubClsid32 for the specified interface in the HKEY_CLASSES_ROOT\Interface section of the registry. The table below shows the legal subkeys of the HKEY_CLASSES_ROOT\Interface section of the registry.

Interface Subkey Description
BaseInterface The IID of the interface that this interface derives from.
NumMethods The number of methods in the interface, including the three methods of IUnknown.
ProxyStubClsid CLSID of the 16-bit proxy/stub coclass.
ProxyStubClsid32 CLSID of the 32-bit proxy/stub coclass.

The following code sets up the REG_DATA structure so that the correct registry entries are added for the ISum interface marshaler. You simply pass this structure to the RegisterServerEx and UnregisterServerEx functions as usual. Notice that proxy/stub DLLs should be registered with the ThreadingModel = Both setting to ensure that they are always instantiated in the apartment of their creator. This also means that they must be thread-safe.

const REG_DATA g_regData[] = {
    { "CLSID\\{10000006-0000-0000-0000-000000000001}", 0, "PSSum" },
    { "CLSID\\{10000006-0000-0000-0000-000000000001}\\InprocServer32", 0, 
        (const char*)-1 },
    { "CLSID\\{10000006-0000-0000-0000-000000000001}\\InprocServer32", 
        "ThreadingModel", "Both" },
    { "Interface\\{10000001-0000-0000-0000-000000000001}", 0, "ISum" },
    { "Interface\\{10000001-0000-0000-0000-000000000001}\\ProxyStubClsid32", 
        0, "{10000006-0000-0000-0000-000000000001}" },
    { "Interface\\{10000001-0000-0000-0000-000000000001}\\NumMethods", 0, 
        "4"},
    { 0, 0, 0 }
};