[Previous] [Contents] [Next]

Marshaling Interface Pointers: An Overview

What does it mean to marshal an interface pointer? To answer this question, we first need to address a more basic question: What is an interface pointer? As you know, an interface pointer is a pointer to a pointer to a memory-based table, the v-table, which contains pointers to the virtual functions contained in that interface. For example, the ISum interface has four methods—the three methods of IUnknown plus the ISum::Sum method. The v-table structure at the location specified by an ISum interface pointer is shown in Figure 14-1. Although this figure shows a new interface pointer being obtained via the well-known IUnknown::QueryInterface method, this is certainly not the only method that can return an interface pointer; any method of a custom interface can return interface pointers.

Click to view at full size.

Figure 14-1. The layout of a v-table structure.

Now let's return to the broader issue of marshaling. The question remains: How is it possible to take an ISum interface pointer and return it to a client in another process, which might or might not be on the same machine? The short answer is that it isn't possible. What constitutes a valid interface pointer in one apartment is complete gibberish in another.

Re-Creating an Interface's V-Table

When a component returns an interface pointer to the client—via QueryInterface or any other method—that interface pointer must be marshaled. But given that passing an interface pointer from one process to another is a fruitless pursuit, the next best option is to have the client think that it is talking directly to the component. For each interface pointer returned to the client, you must re-create that interface's v-table structure in the client's address space. In other words, marshaling an interface pointer means re-creating that interface's v-table in the client's address space.

This v-table is fabricated by loading into the client's address space a proxy object that exposes the same interfaces as those implemented by the real object. When the client makes a method call on the object, it actually calls the in-process proxy. The proxy then communicates the method request to the object running in the component process. This architecture, however, requires that the component contain code to communicate directly with the proxy. To simplify the job of developing a component, this code is sometimes encased in another object, called the stub, which is loaded into the component's address space.

The Proxy/Stub DLL

Recall that marshaling an interface pointer simply means re-creating that interface's v-table within the client's address space. You use a proxy object to do this. Proxy objects are typically built as DLLs so they can be loaded into the client's address space when necessary. There is an important reason for building the proxy code as an in-process component rather than just statically linking it into the client code. Let's say that you've written a coclass that implements a custom interface and have packaged it into an executable component. A developer using Microsoft Visual Basic might want to access this component. In order for Visual Basic to call the object, a proxy must be loaded into Visual Basic's address space. If the proxy code is statically linked into one client program, other clients (such as Visual Basic) will not be able to access it. Unless you don't care about this restriction,4 the proxy code for each custom interface that might be remoted should be implemented in a DLL.

There is no such requirement for stub code. The stub code that communicates with the proxy in the client's address space can be statically linked with the component. This is generally not as desirable as implementing the stub in a separate DLL, but we'll use this technique for the sake of simplicity. When you use standard marshaling (as described in Chapter 15), the proxy/stub code is always implemented as a separate in-process component.

Interprocess Communication

Marshaling requires a proxy object in the client's address space and, at least conceptually, a stub object in the component's address space. When the client calls the proxy, the proxy communicates with the stub and the stub makes the actual calls into the object. The proxy packs any method parameters for transmission to the stub; the stub unpacks the parameters and calls the object. Everything goes in the reverse order on the way back: the object returns to the stub, the stub to the proxy, and the proxy to the client. Of course, the stub is responsible for packing any outgoing parameters and the method's return value for transmission back to the proxy; the proxy unpacks the arguments and returns them to the client. This process is illustrated in Figure 14-2.

Click to view at full size.

Figure 14-2. Communication between a client and a component via the proxy/stub mechanism.

The form and format of the interprocess communication between the proxy and the stub is private to these objects. If the client and the component run in different processes on one machine, shared memory can be used. If they run on different machines, a networking interface such as named pipes is required. COM+ does not define the format of the interprocess communication between a proxy and a stub, so you must determine the most efficient and appropriate way to implement this communication. If interfaces are remoted to other machines over a network, the transmitted data should conform to a published standard. The proxy/stub marshaling code generated by the Microsoft IDL (MIDL) compiler, for example, adheres to the Network Data Representation (NDR) standard.

In previous chapters, we dealt with the marshaling needs of custom interfaces using either type library or MIDL-generated marshaling code. Recall that type library marshaling relies on the Automation proxy/stub DLL to perform the necessary magic. Using MIDL-generated code is nearly as simple; you simply build and register a proxy/stub DLL. Again, the marshaling problem is solved. In most cases one of these two prepackaged solutions will suffice, but some situations call for a higher degree of control over the marshaling process.