Architecture of Standard Interface / Object Marshaling
If the object being marshaled5. chooses not to implement custom object marshaling, a "default" or "standard" object marshaling technique is used. An important part of this standard marshaling technique involves locating and loading the interface-specific pieces of code that are responsible for marshaling and unmarshaling remote calls to instances of that interface. We call these interface-specific pieces of code used in standard marshaling and unmarshaling "interface proxies" and "interface stubs" respectively.6. (It is important not to confuse interface proxies with the object proxy, which relates to the whole representative in the client process, rather than just one interface on that representative. We apologize for the subtleties of the terminology.)
The following figure gives an slightly simplified view of how the standard client- and server-side structures cooperate.
Simplified conceptual view of client-server remoting structures
When an interface of type IFoo needs to be remoted, a system registry is consulted under a key derived from IID_IFoo to locate a class ID that implements the interface proxy and interface stub for the given interface. Both the interface proxies and the interface stubs for a given interface must be implemented by the same class. Most often, this class is automatically generated by a tool whose input is a description of the function signatures and semantics of the interface, written in some interface description language, often known as IDL.
However, while highly recommended and encouraged for accuracy's sake, the use of such a tool is by no means required; interface proxies and stubs are merely Component Object Model components which are used by the RPC infrastructure, and as such, can be written in any manner desired so long as the correct external contracts are upheld. From a logical perspective, it is ultimately the programmer who is the designer of a new interface who is responsible for ensuring that all interface proxies and stubs that ever exist agree on the representation of their marshaled data. The programmer has the freedom to achieve this by whatever means he sees fit, but with that freedom comes the responsibility for ensuring the compatibility.
In the figure, the stub manager is conceptual in the sense that while it useful in this document to have a term to refer to the pieces of code and state in the server-side RPC infrastructure which service the remoting of a given object, there is no direct requirement that the code and state take any particular well-specified form.7. In contrast, on the client side, there is an identifiable piece of state and associated behavior which appears to the client code to be the one, whole object. The term proxy manager is used to refer to the COM Library-provided code that manages the client object identity, and so forth., and which dynamically loads in interface proxies as needed (per QueryInterface calls). The proxy manager implementation is intimate with the client-side RPC channel implementation, and the server-side RPC channel implementation is intimate with the stub manager implementation.
Interface proxies are created by the client-side COM Library infrastructure using a code sequence resembling the following:
clsid = LookUpInRegistry(key derived from iid)
CoGetClassObject(clsid, CLSCTX_SERVER, NULL, IID_IPSFactoryBuffer, &pPSFactory));
pPSFactory->CreateProxy(pUnkOuter, riid, &pProxy, &piid);
Interface stubs are created by the server-side RPC infrastructure using a code sequence resembling:
clsid = LookUpInRegistry(key derived from iid)
CoGetClassObject(clsid, CLSCTX_SERVER, NULL, IID_IPSFactoryBuffer, &pPSFactory));
pPSFactory->CreateStub(iid, pUnkServer, &pStub);
In particular, notice that the class object is talked-to with IPSFactoryBuffer interface rather than the more common IClassFactory.
The interfaces mentioned here are as follows:
interface IPSFactoryBuffer : IUnknown {
HRESULT CreateProxy(pUnkOuter, iid, ppProxy, ppv);
HRESULT CreateStub(iid, pUnkServer, ppStub);
};
interface IRpcChannelBuffer : IUnknown {
HRESULT GetBuffer(pMessage, riid);
HRESULT SendReceive(pMessage, pStatus);
HRESULT FreeBuffer(pMessage);
HRESULT GetDestCtx(pdwDestCtx, ppvDestCtx);
HRESULT IsConnected();
};
interface IRpcProxyBuffer : IUnknown {
HRESULT Connect(pRpcChannelBuffer);
void Disconnect();
};
interface IRpcStubBuffer : IUnknown {
HRESULT Connect(pUnkServer);
void Disconnect();
HRESULT Invoke(pMessage, pChannel);
IRPCStubBuffer* IsIIDSupported(iid);
ULONG CountRefs();
HRESULT DebugServerQueryInterface(ppv);
void DebugServerRelease(pv);
};
Suppose an interface proxy receives a method invocation on one of its interfaces (such as IFoo, IBar, or IBaz in the above figure). The interface proxy's implementation of this method first obtains a marshaling packet from its RPC channel using IRpcChannelBuffer::GetBuffer(). The process of marshaling the arguments will copy data into the buffer. When marshaling is complete, the interface proxy invokes IRpcChannelBuffer::SendReceive() to send the method invocation across the "wire" to the corresponding interface stub. When IRpcChannelBuffer::SendReceive() returns, the contents of buffer into which the arguments were marshaled will have been replaced by the return values marshaled from the interface stub. The interface proxy unmarshals the return values, invokes IRpcChannelBuffer::FreeBuffer() to free the buffer, then returns the return values to the original caller of the method.
It is the implementation of IRpcChannelBuffer::SendReceive() that actually sends the request over to the server process. It is only the channel who knows or cares how to identify the server process and object within that process to which the request should be sent; this encapsulation allows the architecture we are describing here to function for a variety of different kinds of channels: intra-computer channels, inter-computer channels (such as across the network), and so forth. The channel implementation knows how to forward the request onto the appropriate stub manager object in the appropriate process.
From the perspective of this specification, the channel and the stub manager are intimate with each other (and intimate with the proxy manager, for that matter). Through this intimacy, eventually the appropriate interface stub receives an IRpcStubBuffer::Invoke() call. The stub unmarshals the arguments from the provided buffer, invokes the indicated method on the server object, and marshals the return values back into a new buffer, allocated by a call to IRpcChannelBuffer::GetBuffer(). The stub manager and the channel then cooperate to ferry the return data packet back to the interface proxy, who is still in the middle of IRpcChannelBuffer::SendReceive(). IRpcChannelBuffer::SendReceive() returns to the proxy, and we proceed as just described above.
When created, interface proxies are always aggregated into the larger object proxy: at interface-proxy-creation time, the proxy is given the IUnknown* to which it should delegate its QueryInterface(), and so forth calls, as per the usual aggregation rules. When connected, the interface proxy is also given (with IRpcProxyBuffer:Connect()) a pointer to an IRpcChannelBuffer interface instance. It is through this pointer that the interface proxy actually sends calls to the server process. Interface proxies bring a small twist to the normal everyday aggregation scenario.
In aggregation, each interface supported by an aggregate-able object is classified as either external or internal. External interfaces are the norm. They are the ones whose instances are exposed directly to the clients of the aggregate as whole. It is always the case that a QueryInterface() that requests an external interface of an aggregated object should be delegated by the object to its controlling unknown (ditto for AddRef() and Release()). Internal interfaces, on the other hand, are never exposed to outside clients. Instead, they are solely for the use of the controlling unknown in manipulating the aggregated object. QueryInterface() for internal interfaces should never be delegated to the controlling unknown (ditto again). In the common uses of aggregation, the IUnknown interface on the object is the only internal interface. The twist that interface proxies bring is that IRpcProxyBuffer is also an internal interface.
Interface stubs, by contrast with interface proxies, are not aggregated, since there is no need that they appear to some external client to be part of a larger whole. When connected, an interface stub is given (with IRpcStubBuffer::Connect()) a pointer to the server object to which they should forward invocations that they receive.
A given interface proxy instance can, if it chooses to do so, service more than one interface. For example, in the above figure, one interface proxy could have chosen to service both IFoo and IBar. To accomplish this, in addition to installing itself under the appropriate registry entries, the proxy should support QueryInterface()ing from one supported interface (and from IUnknown and IRpcProxyBuffer) to the other interfaces, as usual. When the Proxy Manager in a given object proxy finds that it needs the interface proxy for some new interface that it doesn't already have, before it goes out to the registry to load in the appropriate code using the code sequence described above, it first does a QueryInterface() for the new interface ID (IID) on all of its existing interface proxies. If one of them supports the interface, then it is used rather than loading a new interface proxy.
Interface stub instances, too, can service more than one interface on a server object. However, the extent to which they can do so is quite restricted: a given interface stub instance may support one or more interfaces only if that set of interfaces has in fact a strict single-inheritance relationship. In short, a given interface stub needs to know how to interpret a given method number that it is asked to invoke without at that same time also being told the interface ID (IID) in which that method belongs; the stub must already know the relevant IID. The IID which an interface stub is initially created to service is passed as parameter to IPSFactoryBuffer::CreateStub(). After creation, the interface stub may from time to time be asked using IRpcStubBuffer::IsIIDSupported() if it in fact would also like be used to service another IID. If the stub also supports the second IID, then it should return the appropriate IRpcStubBuffer* for that IID; otherwise, the stub buffer should return NULL. This permits the stub manager in certain cases to optimize the loading of interface stubs.
Both proxies and stubs will at various times have need to allocate or free memory. Interface proxies, for example, will need to allocate memory in which to return out parameters to their caller. In this respect interface proxies and interface stubs are just normal Component Object Model components, in that they should use the standard task allocator; see CoGetMalloc(). See also the earlier discussion regarding specific rules for passing in, out, and in out pointers.
On Microsoft Windows platforms, the "key derived from IID" under which the registry is consulted to learn the proxy/stub class is as follows:
Interfaces
{IID}
ProxyStubClsid32 = {CLSID}
Here {CLSID} is a shorthand for any class ID; the actual value of the unique ID is put between the {}s; as in for example, {DEADBEEF-DEAD-BEEF-C000-000000000046}; all digits are upper-case hexadecimal and there can be no spaces. This string format for a unique ID (without the {}s) is the same as the OSF DCE™ standard and is the result of the StringFromCLSID routine. {IID} is a shorthand for an interface ID; this is similar to {CLSID}; StringFromIID can be used to produce this string.