When an object does not want to provide its own custom marshaling, COM will automatically install standard marshaling for the object and all of its interfaces, employing specific interface marshalers as necessary. In standard marshaling, the proxy is connected to a stub that is then connected to the remote object itself. Underneath it all, both proxy and stub communicate with what is called the RPC Channel, an object (inside the COM Library) that encapsulates all the details about the underlying cross-process or cross-network transport, as illustrated in Figure 6-2. The RPC Channel itself makes calls into whatever RPC API exists on the system, so it only needs to control the data packets sent across the wire, so to speak, leaving the physical transport to the system.
Figure 6-2.
Proxies and stubs communicate with the RPC Channel, which in turn generates the necessary RPC sequences for cross-process or cross-machine communication.
A key point here is that COM performs standard marshaling interface by interface, not object by object. In other words, COM does not create a connection between a proxy and a stub for a particular interface until the client actually has a pointer to that interface itself. In Figure 6-2, the proxy object manages facelets, one for each interface, that individually communicate with a corresponding stublet managed by the stub object, as shown in the exploded view in Figure 6-3.
Figure 6-3.
A proxy manager contains any number of facelets (one per interface); and the stub manager, any number of stublets. Client calls go directly to the appropriate facelet, which marshals the call to the stublet through the RPC Channel. The stublet maintains the pointer to the remote object's interface to make the real call.
Facelets and stublets perform all the communication because they, not the proxy or stub managers, know how to marshal arguments, out-parameters, and return values correctly for their specific interfaces. In other words, the semantics of each interface member function are known somewhat to the facelet and stublet implementations. They know when an out-parameter, for example, is an interface pointer to a new and separate object, which then requires a call to CoMarshalInterface. An interface pointer to the same object, on the other hand, requires only a new facelet and stublet pair.
As you might expect, there are a great many details about the way proxy and stub managers are built and how they get into memory along with facelets and stublets. For interested readers, APPB.WRI on the companion CD describes the architectural objects and how they come into and out of memory. APPB.WRI discusses a short piece of client code that calls CoGetClassObject, IClassFactory::CreateInstance, and IProvideClassInfo::GetTypeInfo, an example that involves three objects, each with different interfaces. A few points, however, are worth mentioning here to help us understand other topics in this chapter.
Three members of the IRpcChannelBuffer interface, implemented on the RPC Channel objects shown in Figure 6-3, are important to the operation of standard marshaling. By calling the function GetBuffer, a facelet obtains a buffer in which it stores the function arguments to pass to its corresponding stublet. (This buffer is freed with the member function FreeBuffer.) When it's ready to transmit the call, the facelet calls the member function SendReceive. The stublet is then told to call the appropriate member function of the remote object through IRpcStubBuffer. When that stublet is ready to return out-parameters and return values, it calls GetBuffer as well and stores the data. When the stublet returns, the RPC Channel brings the data back to the facelet, which recognizes it as an out-parameter from SendReceive.
In most cases, the call is synchronous. This means that SendReceive will not return until the call has been completely processed in the remote object. Calls might also be asynchronous or input-synchronized, as discussed in the section "Concurrency Management" later in this chapter. When a call is blocked inside SendReceive, concurrency management allows the caller to control how long it is willing to wait for the call to be completed.
In some cases, the interface member that was called in this manner might create a new object and return an interface pointer to that object as an out-parameter. The stublet, knowing the semantics of that interface pointer, then has to call CoMarshalInterface and store the contents of the stream in the return buffer. The facelet that receives this buffer reintegrates the stream and passes it to CoUnmarshalInterface to obtain the correct pointer to return to the client. This mechanism is the same when the facelet has to pass an interface pointer to some object to the stublet (such as with a sink interface pointer). In this case, the facelet calls CoMarshalInterface and stores the stream in the buffer so that the stublet can then reintegrate the stream and call CoUnmarshalInterface to get the pointer to pass to the object itself. In each case, the form of marshaling is established according to the needs of the new object being marshaled.
In other cases, the interface member being called from the client returns a pointer to an interface other than the one being used, but still one that is on the same object. Here the correct proxy and stub already exist, but they now need to create a new facelet/stublet pair for the new pointer (assuming it doesn't already exist). This is one of the operations that QueryInterface must perform inside COM's standard IUnknown marshaler. Whatever code needs the new interface marshaler takes the IID and looks for a registry entry that maps to a CLSID. That CLSID then maps to a server that implements the appropriate facelet and stublet for the IID in question. The class factory in that server implements the interface IPSFactoryBuffer, whose members CreateProxy and CreateStub create the necessary facelet and stublet, as described in detail in APPB.WRI on the companion CD.
Interfaces that have no standard marshaling support simply do not have either the necessary registry entries or a server that provides the marshalers (which is, of course, irrelevant for objects using their own custom marshaling). The lack of marshaling support will cause any operation involving a cross-process QueryInterface call to fail. In other words, any facelet or stublet for any interface member that takes IID and void ** arguments to identify an interface pointer will fail when the appropriate registry entries for the IID in question are not found. The specific error is REGDB_E_IIDNOTREG. If the entries do exist but the server doesn't, the error is CO_E_DLLNOTFOUND. There are other possibilities, of course, and these are the errors you will see if you try to use an interface across process boundaries when marshaling support doesn't exist. The nice thing is that you can install marshalers on a running system and make marshaling suddenly work for whatever interface. The same code that failed before will now work without a hitch because the necessary marshaling support is available.
The following section describes the process of creating marshalers for custom interfaces, after which we'll look at one more topic of relevance to standard marshaling—strong and weak references.