Reviewing Location Transparency

 < Free Open Study > 



The notion of location transparency allows a COM client to access COM objects in such a way that the existing code base need not be altered, regardless of where the COM server is physically located (in-proc, local, or remote). Before COM, application developers needed to be concerned with creating unique code blocks (from different APIs) to access in-process, local, and remote binaries. The greatest drawback of this approach is the simple fact that three different APIs result in a rather large surface area the developer is responsible for understanding. In COM development, a single block of code is used to access a COM object, regardless of its physical proximity to the caller. In effect, COM encapsulates the activation and data transmission details from the client by providing the illusion that all interface method invocations are happening in the client's own address space (i.e., in-process).

Recall that clients and servers may have three relationships. First, we have the in-process relationship shown below. When a COM client has loaded an in-proc server and obtains an interface pointer on some object, the two entities talk directly through the acquired interface. This results in a very fast (but not very robust) connection.

click to expand
Figure 5-1: In-proc servers offer a fast though less robust connection.

Note 

Recall that if a client and in-proc server are running under different threading models, a client does not have direct access to the interface pointer. Instead, the client holds a pointer to a proxy object just as it does during local or remote access. We will examine the details of COM's threading models in Chapter 7. For the time being, it is safe to assume that the in-process relationship entails a direct connection to the interface.

Next, we have the local (or out-of-process) relationship. When a COM client obtains an interface pointer from a coclass residing in a local (EXE) server, the client does not have a direct connection to the acquired interface. Instead, the COM runtime secretly loads a "stub/proxy DLL". This DLL contains marshaling code for each custom interface defined in the server's IDL file, providing a safe bridge between the two collaborating processes. We will see where this stub/proxy DLL comes from soon enough.

A Win32 process is extremely protective of the memory it represents, and access between processes cannot occur without a safe and well-defined protocol (such as RPC). This is a good thing, as a given process cannot inadvertently invade another process and cause damage. So then, what exactly is a "stub" and "proxy"? We will define a stub and proxy as the following, and expand from here:

  • Proxy: A COM object loaded into the client's process, which packages up client requests for any out-of-process (or out of apartment) method invocation.

  • Stub: A COM object loaded into the server's process, which receives the client's requests, unpackages them, and hands them off to the "real COM object."

The figure at the top of the next page illustrates the local relationship. Assume the client has acquired an IDraw interface pointer and wishes to call the Draw() implementation of CoHexagon residing outside of its own process.

Because a proxy looks and feels like the "real COM interface" as far as the client can tell, the existing code base remains unaltered. As well, the server object regards the stub as "the real client" and thus the code in your coclass does not need to change in order to return information to clients located outside of its own process.

click to expand
Figure 5-2: Clients and local servers communicate through stubs and proxies.

Note 

This is the core of location transparency: provide the illusion that a COM client and COM objects are always communicating with an in-process entity.

Under the hood, processes have information transmitted between them using a procedure called marshaling. Marshaling is the process of packing, sending, and unpacking data between clients and local or remote servers, via stubs and proxies. Marshaling between local servers entails lightweight RPC (LRPC) calls. LRPC is a Microsoft extension to DCE RPC that includes support for marshaling interface pointers between stubs and proxies (among other things).

A given stub/proxy DLL contains marshaling code for every interface supported by the coclass. Recall that the MIDL compiler will generate a handful of files (most notably the *_p.c file) that define the stub/proxy code for all IDL-defined interfaces. With these files, you may build and register a stub/proxy DLL used in order to achieve local or remote method invocations.

Last but not least, we have the remote relationship. If a client and server are communicating across machine boundaries using DCOM, the client is once again really calling a proxy loaded into its memory partition, which communicates with a stub loaded in the memory partition of the remote process. This time, calls are made over the wire using full-blown RPC:

click to expand
Figure 5-3: Remote invocations require DCOM’s underlying RPC protocol.

Note 

LRPC and RPC are often simply referred to as ORPC (Object RPC).

Dissecting a Proxy

Let's begin to better understand the role of the interface proxy. The very first thing to be aware of is that proxy objects are true-blue COM objects, which support standard COM interfaces to get the job done. Proxy objects reside in the client's process and have the same look and feel as the local or remote interface they pretend to be. As mentioned, this illusion is the backbone of location transparency, as the client assumes the IDraw interface is always right next door.

Every proxy object supports an additional standard COM interface beyond a given interface of the remote coclass: IRpcProxyBuffer. IRpcProxyBuffer contains two methods used by an entity called the proxy manager. These methods, Connect() and Disconnect(), provide a way for the proxy to communicate with yet another middleman between your client and distant object: the ORPC channel object. Here is the IDL definition of IRpcProxyBuffer, as defined in <objidl.idl>:

// IRpcProxyBuffer allows a proxy to connect to the ORPC channel object, when // told to do so by the proxy manager. [local, object, uuid(D5F56A34-593B-101A-B569-08002B2DBF7A)] interface IRpcProxyBuffer : IUnknown {      HRESULT Connect( [in, unique]                       IRpcChannelBuffer *pRpcChannelBuffer);      void Disconnect(void); };

Understanding the Channel Object

ORPC channel objects are COM objects that support the standard COM interface IRpcChannelBuffer (notice that the Connect() method of IRpcProxyBuffer takes an IRpcChannelBuffer interface pointer parameter). IRpcChannelBuffer has a handful of methods that make calls into the RPC layer in order to assemble a client's request packet and send it off into the stub. Channel objects are the worker bees of the COM marshaling process, and are responsible for pushing data between processes. IRpcChannelBuffer is defined in <objidl.idl> as the following:

// Channel objects implement IRpcChannelBuffer, which does the grunge // work to move data between process boundaries. [ local, object, uuid(D5F56B60-593B-101A-B569-08002B2DBF7A) ] interface IRpcChannelBuffer : IUnknown {      HRESULT GetBuffer([in] RPCOLEMESSAGE *pMessage, [in] REFIID riid);      HRESULT SendReceive ([in,out] RPCOLEMESSAGE *pMessage,                         [out] ULONG *pStatus);      HRESULT FreeBuffer([in] RPCOLEMESSAGE *pMessage);      HRESULT GetDestCtx([out] DWORD *pdwDestContext,                          [out] void **ppvDestContext);      HRESULT IsConnected (void); };

This orchestration between proxies and the ORPC channel sounds more complex than it really is. The good news is that the loading of stubs and proxies (and the implementation of these new standard interfaces) is taken care of automatically by the COM runtime and the MIDL-generated code. To reiterate the story thus far, two new COM interfaces are used to help move the client's request to the stub:

  • IRpcProxyBuffer: Implemented by the proxy and used by the proxy manager to connect or disconnect a proxy to the ORPC channel object.

  • IRpcChannelBuffer: Implemented by the channel object. Packages up the client requests and sends them to the stub using low-level ORPC calls.

With these two additional interfaces, we can get a more intimate look at what is really happening when a client calls into the proxy object, as illustrated below:

click to expand
Figure 5-4: Proxies support IRpcProxyBuffer, which allows them to communicate to the channel object. The channel object supports IRpcChannel- Buffer, which packages up method requests.

Understanding the Proxy Manager

The proxy manager is the acting chieftain for all proxies loaded in the client's process. Using the IRpcProxyBuffer interface, the proxy manager informs a given proxy object that it is up for duty by calling the Connect() method. This method takes a pointer to the channel object's IRpcChannelBuffer interface, allowing the proxy to send client requests into the channel. The proxy manager is responsible for assembling all loaded proxies into a unified whole, providing yet another illusion for the client, that the entire "real COM object" is right next door.

There is a one-to-one correspondence between a given out-of-process interface and a proxy. If the client has interface pointers to ten interfaces on some local or remote object, this results in ten proxies representing those interfaces, all maintained by the proxy manager. The proxy manager assembles each individual proxy into a collective using (of all things) COM aggregation, and provides an IUnknown implementation for the aggregated interfaces. Figure 5-5 illustrates the role of the proxy manager. Notice how the unified proxies look just like the remote CoHexagon object as far as the client is concerned:

click to expand
Figure 5-5: The proxy manager aggregates all proxies into a whole, simulating the distant coclass.

It is comforting to know that a given AddRef() or Release() call by the client does not result in an ORPC call until the final release has been detected. This of course results in a much more optimized scenario than one round trip per AddRef() and Release().

Dissecting a Stub

So then, the proxy manager provides the illusion that the distant object is an in-process entity. IRpcProxyBuffer and IRpcChannelBuffer do the grunge work of packing up the request and sending it over to the distant process using ORPC. Who, then, is waiting on the other side? The stub.

Recall that a stub is responsible for taking incoming request packets sent from a proxy and sending them to the real COM object. Each stub object implements a standard COM interface named IRpcStubBuffer which, like IRpcProxyBuffer, also supports Connect() and Disconnect() methods. IRpcStubBuffer is defined in <objidl.idl> as so:

// IRpcStubBuffer allows the stub to communicate with the channel object. [ local, object, uuid(D5F56AFC-593B-101A-B569-08002B2DBF7A)] interface IRpcStubBuffer : IUnknown {      HRESULT Connect( [in] IUnknown *pUnkServer);      void Disconnect();      HRESULT Invoke( [in] RPCOLEMESSAGE *_prpcmsg,                     [in] IRpcChannelBuffer *_pRpcChannelBuffer);      IRpcStubBuffer *IsIIDSupported( [in] REFIID riid );      ULONG CountRefs (void);      HRESULT DebugServerQueryInterface(void **ppv);      void DebugServerRelease(void *pv); } 

The stub manager makes use of IRpcStubBuffer to receive the incoming request packets and forward the remote invocation to the distant COM object:

click to expand
Figure 5-6: Stubs implement IRpcStubBuffer to unpackage the request and hand it off to the real COM object.

Unlike the proxy manager, the stub manager does not aggregate the stubs into a unified whole. Beyond taking the incoming request and passing it along to the "real object," stubs are also in charge of the task of assembling the object's response to the method invocation. This response may be a single HRESULT, as well as any [out] parameters that are defined by the interface's IDL.

All Together Now!

Under the hood we now see what stubs and proxies are all about. In essence, a proxy packages up a client's request for a local or remote method call. This request is sent to the stub via the intervening ORPC channel object. Once the request makes it to the server process, the request packet is intercepted by the stub manager and directed to the correct interface stub, and finally the correct information is sent to the coclass interface method. We can summarize this handshaking as follows (managers omitted for sanity):

click to expand
Figure 5-7: The movement of remote interface method invocations.

Now then, if stubs and proxies are so central to COM's remoting architecture, a logical question would be to ask how these objects are created in the first place. Sounds like another standard COM interface to me.

Creating Stubs and Proxies: IPSFactoryBuffer

The last item to note concerning stubs and proxies is that they are true COM objects that are created by a true COM class factory. Unlike your custom coclasses, a single class factory creates both the stub and proxy for a given interface using a special interface named IPSFactoryBuffer. As soon as a client requests access to a distant interface, the corresponding managers call the CreateProxy() or CreateStub() methods of IPSFactoryBuffer to load the stubs and proxy objects into the correct process:

// The class object responsible for creating stubs and proxies implements // the IPSFactoryBuffer interface (defined in <objidl.idl>). [ local, object, uuid(D5F569D0-593B-101A-B569-08002B2DBF7A)] 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); }

In summary, the remoting architecture of COM involves four standard COM interfaces: IRpcProxyBuffer, IRpcStubBuffer, IRpcChannelBuffer, and IPSFactoryBuffer. So (you may be asking) why is it important to understand all these low-level COM interfaces? At the very least you should be able to see why (a) calling a remote or local server is slower than calling an in-proc server, (b) it is a good thing that COM provides automatic loading of stubs and proxies, and (c) it is a very good thing that MIDL generates the required stub/proxy code.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net