I mentioned that OLE, or more accurately COM, allows components to be implemented in either DLL or EXE servers, which means that those components can run either in the same process as the client or in a separate process. When Microsoft enables distributed services and remote objects, components will then be able to run in another process on another machine. In all cases, COM's architecture enables a client to communicate with any object through an interface pointer in the client's address space. When the object is in-process, calls go directly to the object's implementation. When there is a process or a machine boundary between the client and the object, the call cannot be direct because the client's pointer is meaningless outside its process. How does COM route calls from the client to the object's real implementation, wherever it is running, and do so transparently to the client?
COM's Local/Remote Transparency is the technology that makes calls to a local or a remote object identical to a call to an in-process object as far as the client is concerned.7 The way it works, shown in Figure 1-10 on the next page, is that the client always has a pointer to some in-process implementation of the interface in question. If the object is truly in-process, that implementation is the object's. If the object is local or remote, the implementation is part of an in-process object proxy. When the client makes a call to the interface, the proxy takes all the arguments to that function and packages them in some portable 32-bit data structure (which involves copying data structures, strings, and so forth) and generates some sort of remote procedure call (RPC)8 to the other process (or machine). In that other process, a stub, which maintains the real interface pointers to that object, receives the call, unpacks the data structure, pushes arguments on the stack, and makes the call to the object. When that call returns, the stub packages the return values and any out-parameters (such as structures that the function fills) and sends them back to the proxy, which unpacks that information and returns it to the client. The client never knows that any of this happens.
Figure 1-10.
Making a cross-process call through a proxy and a stub.
Making remote calls across process boundaries did not, of course, originate with OLE. However, OLE provides this capability on a much higher level than something like direct RPC, named pipes, Windows messages, or DDE. Furthermore, OLE makes most interface calls inherently synchronous, with the exception of certain notifications that are specifically designed to be asynchronous. This greatly simplifies programming because you don't have to sit in message loops or spin off threads to wait for things to finish—OLE waits for you and handles the messy considerations of time-outs and errors. If you need to, OLE allows you to hook into this mechanism by implementing a small object (no CLSID needed) called a message filter with the IMessageFilter interface. This is an example of a case in which you install a small object to customize an OLE-provided service.
Marshaling is the process of passing function arguments and return values among processes and among machines, taking any system or process differences into account. As Chapter 6 will show, you can do this through custom marshaling on an object-by-object basis, where the real object specifies what piece of code to use for its proxy so that it can establish a private communication channel however it wants. The other option is to use standard marshaling on an interface-by-interface basis, where OLE provides small interface proxies and stubs for those interfaces it defines 9 and where you can easily create the same for your own custom interfaces. Generally, this is a simple process of describing the interface in an Interface Definition Language (IDL, similar to ODL) and pumping it through the Microsoft IDL compiler (MIDL), which writes the source code for you. Then you compile this code into a DLL that fits right into OLE's standard marshaling architecture.
7 Even as COM doesn't support the remote case at the time of writing it was intentionally designed to handle remote objects through the same architecture. |
8 Under Windows 3.1 (16 bits) the mechanism is a private implementation called LRPC for "lightweight remote procedure calls" which is strictly local to a machine. On 32-bit platforms OLE uses the true underlying system RPC as defined by the Open Software Foundation (OSF) Distributed Computing Environment (DCE). |
9 Some interfaces have function arguments like an HDC (handle to a device context) that simply cannot be marshaled and some interfaces do not at the time of writing have marshaling support particularly those provided with OLE Controls. |