[Previous] [Contents] [Next]

An Introduction to Marshaling

When the InsideCOM coclass is run in the surrogate as described previously, the object implements the ISum interface, which has the methods QueryInterface, AddRef, Release, and Sum. Did you pause to marvel at how the parameters to and from these methods happen to pass successfully between the client and the component? In earlier chapters, we built in-process components, which ran in the address space of the client, obviating the issue of parameter passing. However, in the surrogate process example, our in-process component ran first in the address space of the surrogate process and then on another computer altogether. How did the parameter passing work, then? This whole issue of how parameters are passed between processes, called marshaling, is an area of great importance.

In-process components don't have to worry about marshaling because all the action takes place in a single address space. The function parameters are simply pushed onto the stack for a function call and then popped off the stack when the function executes. Executable components, whether they're running on the same machine as the client or on a remote computer, must concern themselves with marshaling. While marshaling is a concern for executable components, cross-computer marshaling has added network bandwidth issues, making marshaling an even more important concern. Figure 12-3 illustrates how marshaling is used to send data to and from a component.

Click to view at full size.

Figure 12-3. The architecture for cross-computer communication between a client and a component.

To put it more precisely, marshaling is the process of packaging method calls and their parameters into a transmittable packet and then sending that packet to a component. This process can be quite simple or complex. For example, marshaling an integer parameter involves simply copying its value into a transmission buffer and sending it off. Marshaling an array of characters, however, is more complicated. How does the marshaler know the size of the array? Even if the size of the entire array can be determined, perhaps only the first several bytes are in use; transmitting the entire array would be a waste of bandwidth. Finally, what if you have a pointer to an element in a doubly linked list? How can the marshaler possibly know how to package this complex data structure for transmission?

In the case of most standard interfaces, marshaling is handled by objects instantiated from the system ole32.dll component. The main issue for most developers is what to do about custom interfaces. Marshaling custom interfaces is too often explained away as black magic that requires various incantations to work smoothly. Here we'll expose it for what it really is.

COM+ offers three basic marshaling options, which we'll discuss in detail in the sections that follow:

Standard Marshaling

Don't confuse the term standard marshaling with standard interfaces. Remember that standard interfaces are interfaces defined by Microsoft as part of COM+. For most of these interfaces, Microsoft has already built marshaling code. Standard marshaling is used for custom interfaces such as ISum. The easiest way to take advantage of standard marshaling is to use the Microsoft IDL (MIDL) compiler. You simply create an IDL file describing your custom interfaces and then compile the IDL file using the MIDL compiler to generate the standard marshaling code. The following table lists the files created by the MIDL compiler when it is used to compile interface definitions.

Filename Description
idlname.h Header file for the interface definitions
idlname_i.c Definitions of the IID and CLSID constants
idlname_p.c Marshaling code
idlname.tlb Type library (only if the IDL contains a library statement)
dlldata.c DLL entry points for the marshaling code

By compiling and linking these files, you can produce a proxy/stub DLL that correctly marshals your interfaces. A proxy/stub DLL is a library that can be loaded by both the client and the component to properly marshal data back and forth. In order for the system to automatically load this DLL when needed, the proxy/stub DLL must be registered as the ISum interface marshaler. Luckily, the DLL built from the code generated by the MIDL compiler exports the DllRegisterServer function, which knows how to correctly register the proxy/stub DLL. You simply compile the proxy/stub DLL with the REGISTER_PROXY_DLL symbol defined, and the necessary code is included in the component. When the DllRegisterServer function is called, it creates all the necessary registry entries. Recall that this self-registration feature allows the proxy/stub component to be registered using the RegSvr32 utility. In sum, the advantage of standard marshaling is that you have to write only the IDL—the rest is automatic.

Building a Proxy/Stub DLL for Standard Marshaling

To build a proxy/stub DLL when you use standard marshaling, follow these steps:

  1. Compile your IDL file (component.idl) using MIDL.
  2. Open Visual C++ and choose the File/New command.
  3. Select the Projects tab, and then select Win32 Dynamic-Link Library.
  4. In the Project Name text box, type ProxyStub, and then click OK.
  5. When asked by the wizard, create an empty DLL project and click Finish. Then click OK.
  6. Choose the Project/Add To Project/Files command.
  7. Select the dlldata.c, component_i.c, and component_p.c files generated by MIDL, and then click OK.
  8. Choose the File/New command, select the Files tab, and then select Text File.
  9. In the File Name text box, type ProxyStub.def, and then click OK.
  10. Enter the following module definition file:
  11. ; ProxyStub.def
    LIBRARY                  ProxyStub.dll
    DESCRIPTION              'Proxy/Stub DLL'
    EXPORTS
        DllGetClassObject    @1 PRIVATE
        DllCanUnloadNow      @2 PRIVATE
        DllRegisterServer    @3 PRIVATE
        DllUnregisterServer  @4 PRIVATE
    

  12. Choose the File/Save command, and then choose Project/Settings.
  13. In the Settings For list box, select All Configurations.
  14. Select the C/C++ tab, and in the Category list box, select General.
  15. In the Preprocessor Definitions box, add REGISTER_PROXY_DLL and _WIN32_DCOM, separated by commas.
  16. Select the Link tab, and in the Category list box, select General.
  17. In the Object/Library Modules box, add rpcndr.lib, rpcns4.lib, and rpcrt4.lib, separated by spaces, and then click OK.
  18. Choose the Build/Build ProxyStub.dll command.
  19. Assuming that all has gone well, choose the Tools/Register Control command.

Self-registering proxy/stub DLLs should generally include a version information resource containing the OLESelfRegister value. (See Chapter 2 for details.)

Type Library Marshaling

Type library marshaling uses the Automation (IDispatch) marshaler. Normally, components implement the IDispatch interface to work with scripting languages such as VBScript or JScript since these languages require that a component support the IDispatch interface. A component implementing the IDispatch interface need not worry about marshaling since this is a standard interface and the system has a built-in marshaler for IDispatch in oleaut32.dll, which is included with every 32-bit Windows system.

While the IDispatch interface has its uses, it also has its problems (as described in Chapter 5). Thus, many components opt for custom interfaces rather than IDispatch. You can, however, use the marshaling infrastructure developed for the IDispatch interface without implementing this interface in your component. This option is intriguing, and you can use it only if your custom interfaces restrict themselves to Automation-compatible data types.3 In order for the Automation marshaler to obtain the information it needs to correctly marshal your custom interfaces, you must also register a type library for the component. You must declare these interfaces as Automation-compatible in the IDL file used to build the type library. You do this by including the oleautomation flag, as shown here:

[ object, uuid(10000001-0000-0000-0000-000000000001),
  oleautomation ]
interface ISum : IUnknown
{
    HRESULT Sum(int x, int y, [out, retval] int* retval);
}

Setting the oleautomation flag does not mean that you're using the IDispatch interface—only that your interface is compatible with IDispatch. Because the Automation marshaler is generic, it is not as efficient as the marshaling code generated by MIDL. Moreover, it imposes a slight performance penalty due to the time consumed by the type library lookup. However, because the Automation marshaler is flexible and easy to use, it is often an excellent choice. As an added benefit, you don't need to build a proxy/stub DLL and register it on each machine—the Automation marshaler is automatically available in Windows. To enable type library marshaling for your components, you simply register each custom interface by setting the ProxyStubClsid32 value to {00020424-0000-0000-C000-000000000046}, as shown here:

REGEDIT4

[HKEY_CLASSES_ROOT\Interface\
{10000001-0000-0000-0000-000000000001}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"

At run time, the SCM locates this CLSID and performs a lookup in the HKEY_CLASSES_ROOT\CLSID section of the registry. The Automation marshaler CLSID has the following entries in the registry:

HKEY_CLASSES_ROOT\
    CLSID\
        {00020424-0000-0000-C000-000000000046}\
            (Default)="PSOAInterface"
            InprocServer32
                (Default)="oleaut32.dll"
                ThreadingModel="Both"

Notice that oleaut32.dll is listed as the proxy/stub Automation (PSOAInterface) coclass housed by oleaut32.dll. If you use type library marshaling, calling the LoadTypeLibEx or RegTypeLib function during the self-registration process automatically adds the necessary registry entries, as shown in the code fragment below. In the previous discussion of DLL surrogates, marshaling worked correctly because of the oleautomation attribute in the IDL file. Just imagine: you used type library marshaling without even realizing it!

ITypeLib* pTypeLib;
HRESULT hr = LoadTypeLibEx(L"mytypelib.tlb", REGKIND_DEFAULT, 
    &pTypeLib);
pTypeLib->Release();

Custom Marshaling

Custom marshaling, the fundamental marshaling architecture of COM+, gives you complete control over the marshaling process. Not surprisingly, it is also the most difficult marshaling technique to implement. Custom marshaling is the generic mechanism by which one object can specify exactly how it communicates with a proxy in another process. Custom marshaling involves taking an interface pointer in one process and making it accessible to another process, either on the same machine or remotely. Standard marshaling is simply one way to achieve this goal. The standard marshaling architecture is built on top of custom marshaling, and type library marshaling is built on top of standard marshaling. These relationships are shown in Figure 12-4.

Click to view at full size.

Figure 12-4. The relationship between the three marshaling options.

Marshaling an interface pointer is far more complex than marshaling simple Remote Procedure Calls (RPCs). With RPCs, marshaling is only a matter of packaging the parameters of a fixed set of functions in a data packet and then unpacking the data structure on the other side. In COM+, each interface specifies a different set of functions that must be marshaled uniquely. The client might not know in advance what interfaces it will use. Visual Basic and the Microsoft Java Virtual Machine (VM), for example, can connect to any custom interface you might dream up. How can these run-time interpreters possibly know how to marshal the parameters to methods in your custom interface? The custom marshaling architecture must somehow deal with the dynamic nature of COM+.

While you ponder possible solutions to this dilemma, we'll discuss executable components in Chapter 13 and custom marshaling in Chapter 14. These will lead up to the detailed discussion of standard marshaling in Chapter 15.