Marshaling is the mechanism that enables a client in one process to transparently make interface function calls to local objects in another process or to remote objects running on other machines. For convenience, this chapter refers to both local and remote objects as remote objects and both local and remote servers as remote servers.3 Clients are not concerned with the differences. Regardless of the object's ultimate location, marshaling involves two steps. The first is to take an interface pointer in a server's own process and make the pointer available to code in the client process. This involves establishing some form of interprocess communication. The second step is to take the arguments to an interface call as passed from the client and transfer those arguments to the remote object's own implementation.
In earlier chapters, I've shown a simple diagram in which the client always makes interface calls to an in-process object of some sort. The object can be the complete object implementation, a handler, or a proxy. A handler and a proxy are structurally identical and are often discussed interchangeably. A proxy, however, is completely aware of the nature of the connection it maintains to a remote object across whatever boundary is involved. A handler, on the other hand, might itself internally use a proxy to communicate with its own remote object. A handler doesn't need to be aware of the nature of the connection. From the client's perspective, however, the handler or proxy provides callable interface pointers as well as a complete in-process object. Thus, calls to remote objects are transparent because they appear in client code exactly as calls to in-process objects do.
Here is where the second step of marshaling comes into play. (We'll see the first step momentarily.) The client has pushed arguments onto the stack and made a function call through an interface pointer. If necessary, the call winds its way into the implementation of this function in a proxy. That proxy marshals the call by packing the arguments into a data structure that can be transmitted to another process or another machine. This data structure is then picked up by some piece of code in the object's own process, either a stub or the object itself, depending on the nature of the marshaling. That stub unpacks the data structure, pushes the arguments onto the stack, and then calls the actual object's implementation. On return, the stub packs the function's return value and any out-parameters into a data structure and transmits that structure back to the proxy. The proxy takes the return values, puts them in the appropriate places, and returns them to the client.
Different argument types, of course, are marshaled differently. A simple value such as a DWORD is marshaled by copying the value, but pointers to strings or structures are marshaled by copying the data pointed to. Whatever exists in the client's address space at the time of the call must be re-created in the server's address space, which is what the object will expect. This is the idea of transparency—neither client nor object can detect the boundaries between them.
The first step in marshaling determines exactly how arguments are transmitted between processes, if they need to be transmitted at all. Basic marshaling architecture, also called custom marshaling, is what the remote object uses to control the nature of the connection between it and whatever proxy it requires. Through custom marshaling, an object specifies the CLSID of its proxy and completely controls interprocess (or intermachine) communication for all its interfaces (custom or standard) as a whole. This means that the object also controls how to marshal the arguments of all those interfaces. Various designs benefit tremendously from the object's control. Objects with an immutable state, for example, benefit because the proxy itself can simply be a complete copy of the remote object, thereby eliminating the need for any IPC at all! The next section examines the architecture that makes this possible, along with a few other cases in which custom marshaling is helpful.
Custom marshaling is the fundamental marshaling mechanism. Microsoft recognizes, of course, that it's pointless to make every object implementation supply its own marshaling mechanism, so OLE also offers standard marshaling. With standard marshaling, OLE provides a generic proxy and a generic stub that communicate through system-standard RPC. The proxy and the stub both understand all the standard OLE-defined interfaces (barring those that cannot be marshaled at all or those for which support doesn't otherwise exist). Each interface is represented by its own small piece of code, called an interface marshaler, that understands the semantics of each member of the interface and how to marshal all the arguments of those functions appropriately. The interface marshaler actually performs the packing and unpacking of argument structures. This architecture allows you to plug in your own marshalers for custom interfaces as well, making those custom interfaces appear the same as standard interfaces to the rest of the system. Thus, objects are completely relieved from marshaling burdens if standard marshaling is suitable for the design.
The generic proxy and stub objects are nothing more than containers, or managers, for interface marshalers, regardless of whether you provide the marshaler itself. Thus, a proxy is often called a proxy manager and a stub a stub manager. The marshalers themselves contain an interface proxy, which resides in the proxy object, and an interface stub, which resides in the stub object. This chapter refers to an interface proxy as a facelet and an interface stub as a stublet to eliminate any confusion about the use of proxy and stub. The latter two terms are used exclusively to refer to the proxy and stub objects as a whole, not the specific interface pieces within them.
It is vital to understand that standard marshaling is a specific instance of the generic custom marshaling architecture. With this in mind, we'll see first how custom marshaling works. But because standard marshaling is omnipresent on OLE-capable systems, we'll also spend considerable time dealing with it, especially with respect to how you create marshalers for your own custom interfaces.
3 Let me remind you again that remote servers and objects are not yet supported (at the time of writing). This chapter however is written as if such support existed. |