Developers Workshop to COM and ATL 3.0
| < Free Open Study > |
|
COM's Marshaling Options
While loading existing DLLs into a surrogate process is an interesting exercise, the $24,000 question is "How was information marshaled between the processes?" We have a number of different marshaling options as COM developers, so let's see the choices (for the record, we just leveraged the universal marshaler). You may choose between three basic marshaling options:
-
Custom marshaling: Implement IMarshal by hand.
-
Standard marshaling: Build and register your own custom stub/proxy DLL.
-
Universal marshaling: Leverage a system-supplied stub/proxy DLL.
As with any choice made in programming we must weigh the pros and cons. One of the biggest issues to contend with in development overall seems to be the "speed of program execution" versus "approaching deadline" debate. For example, we should all agree by now that a very sophisticated UI can be assembled in Visual Basic in a fraction of the time than one in MFC. Although this is true, if you want absolute speed and control, you should opt to drop straight to the Win32 API and the C language. Although we all have our favorite development environment, the smart developer is willing to put aside personal preferences to save a failing deadline.
Choosing between the COM marshaling options presents a similar issue. Beyond the "speed of execution" versus "approaching deadline" debate you will need to wrestle with the "Wow this is easy!" versus "You have to be kidding!" groaning debate. COM provides a number of techniques to move information between stubs and proxies, each with varying complexity.
However, given our DLL surrogate example, you should already feel relatively confident that marshaling can be quite simple (add a registry entry and change the CLSCTX flag). Let's begin by quickly discussing the most complex (and therefore most flexible) way one can marshal information between processes.
Custom Marshaling
This form of marshaling is the backbone for COM's other two marshaling techniques, standard and universal marshaling. Developers who wish to utilize custom marshaling begin by implementing the IMarshal interface on a given coclass. This standard COM interface contains a number of methods that allow you to define exactly how information is sent between the coclass and the proxies. When a client is activating your coclass, SCM will query for the IMarshal interface. IMarshal is defined as the following:
// When you want complete control of object communication. [local, object, uuid(00000003-0000-0000-C000-000000000046)] interface IMarshal : IUnknown { HRESULT GetUnmarshalClass ([in] REFIID riid, [in, unique] void *pv, [in] DWORD dwDestContext, [in, unique] void *pvDestContext, [in] DWORD mshlflags, [out] CLSID *pCid ); HRESULT GetMarshalSizeMax ( [in] REFIID riid, [in, unique] void *pv, [in] DWORD dwDestContext, [in, unique] void *pvDestContext, [in] DWORD mshlflags, [out] DWORD *pSize ); HRESULT MarshalInterface ( [in, unique] IStream *pStm, [in] REFIID riid, [in, unique] void *pv, [in] DWORD dwDestContext, [in, unique] void *pvDestContext, [in] DWORD mshlflags); HRESULT UnmarshalInterface ( [in, unique] IStream *pStm, [in] REFIID riid, [out] void **ppv ); HRESULT ReleaseMarshalData ( [in, unique] IStream *pStm ); HRESULT DisconnectObject ( [in] DWORD dwReserved); };
If your coclass returns an IMarshal interface pointer to SCM, it will be used to move information between processes. If SCM cannot extract an IMarshal pointer from your coclass, SCM assumes you do not support custom marshaling and instead will be making use of standard or universal marshaling.
When you implement custom marshaling, you get to decide what that transport layer will be (and leverage it yourself). Beyond the implementation of IMarshal on your coclass, you will code up the packaging and sending of your interface method parameters. If this is your wish, you must implement your own marshaling code (something we will not be doing in this book).
Standard Marshaling
A far simpler approach is to use what is called standard marshaling, which is based upon the ORPC protocol examined earlier in this chapter. If you decide to use COM's standard marshaling support, you need to build and register your own stub/proxy DLL (using those MIDL-generated files I keep referring to). Do note that stub and proxy DLLs are used to marshal your custom interfaces. Standard COM interfaces are marshaled by the system- provided ole32.dll.
Assume that the shapes.idl file (defined in Chapter 4) has been sent through the MIDL compiler. The generated shapes_p.c file contains stub and proxy definitions for each method of every interface defined in the IDL file. To illustrate, if you were to insert the shapes_p.c file into a project workspace, you will see the given stub and proxy functions appear in the Globals folder:
Recall that each interface method is defined by a _Proxy() and _Stub() method. From Figure 5-15, you can see that the IDraw interface has stub/proxy code defined in the IDraw_Draw_Proxy() and IDraw_Draw_Stub() method pair. Notice the stub methods take an IRpcStubBuffer interface pointer as a method parameter, allowing the stub object to communicate with the ORPC channel.
Beyond your *_p.c file, the other marshaling related file generated by MIDL is dlldata.c. Within this file you will see a listing for the following macro:
// This macro is listed in the MIDL-generated dlldata.c file. DLLDATA_ROUTINES( aProxyFileList, GET_DLL_CLSID )
The DLLDATA_ROUTINES macro expands into a handful of other macros, each bringing in support for the following core DLL exports as well as a few others used to define the required class factories (supporting the IPSFactoryBuffer interface). DLLDATA_ROU- TINES expands to the following macros:
-
DllRegisterServer: Via the DLLREGISTRY_ROUTINES macro.
-
DllUnregisterServer: Also via DLLREGISTRY_ROUTINES.
-
DllGetClassObject: Via the DLLGETCLASSOBJECTROUTINE macro.
-
DllCanUnloadNow: Via the DLLCANUNLOADNOW macro.
The good news is MIDL writes all of this standard marshaling code on your behalf (which you may ignore entirely), leaving building and registering the stub/proxy DLL as your only task.
Building a Custom Stub/Proxy DLL
To build your stub and proxy DLL, open up a brand new (simple) Win32 DLL project workspace (we will name our sample DLL ShapesPS.dll) and insert the MIDL-generated shapes_i.c, shapes.h, shapes_p.c, and dlldata.c into the current project. Recall these MIDL-generated files define the GUIDs, proxy code, and DLL exports, respectively. The next step is to create a standard DEF file for the DLL and insert it into the project workspace. If this looks familiar to you, it should. Stub/proxy DLLs are COM-based in-process servers, and therefore they must export the same set of methods as a custom in-proc COM server:
LIBRARY SHAPESPS.DLL EXPORTS DllGetClassObject @1 PRIVATE DllCanUnloadNow @2 PRIVATE DllRegisterServer @3 PRIVATE DllUnregisterServer @4 PRIVATE
Recall that dlldata.c defines a set of macros that supply self-registration support. This means we do not need to write custom REG files for our stub/proxy DLLs, as the DllRegisterServer() and DllUnregisterServer() functions will insert and delete all necessary registry entries.
For this self-registration code to be compiled into your project, you must go into the Project Settings dialog box (select Project|Settings), navigate to the C/C++ tab, and add the REGISTER_PROXY_DLL and _WIN32_DCOM flags to the Preprocessor Definitions field, as shown below:
Finally, before we can successfully build this project, we must link to the libraries that define all the RPC functions used by the MIDL-generated shapes_p.c file. Go into the Project Settings dialog box and select the Link tab. Specify linkage to rpcndr.lib, rpcns4.lib, and rpcrt4.lib, as shown in Figure 5-17:
You can now compile your DLL and register it using the Tools | Register Control command:
Building a Stub/Proxy DLL à la ATL
On a related note, when you use the ATL framework to build your COM servers, you will find that one of the ATL COM AppWizard generated files ends with the *.mk file extension. This file can be used to build your custom stub/proxy DLL with considerably less work than the by hand approach we have just examined.
To do so, navigate to your project directory using a command prompt window and send in the ATL generated *.mk file as a parameter to the nmake.exe utility. This will build the stub and proxy DLL in a single step, bypassing the need to create and configure a new Win32 DLL project workspace. From there, you can register your new stub/proxy DLL as usual.
Registry Entries for Stub/Proxy DLLs: HKEY_CLASSES_ROOT\Interface
So the next question is "What was registered?" In order for SCM to load and create the stubs and proxies for your custom interfaces, it needs to be able to locate the correct stub/proxy DLL. Allow me to introduce you to yet another core key under HKCR, the Interface key. This key maintains information for each interface on your machine that may be marshaled between process, machine, or apartment boundaries.
Recall that the COM servers we constructed in Chapters 3 and 4 did not require you to enter any information into the registry for the ICreateCar, IStats, and IEngine interfaces. The reason is simple: These COM servers were in-process, and therefore no stubs and proxies were necessary (therefore SCM did not need to locate these interfaces from HKCR\Interface\{<guid>}). As soon as you plan to allow your interfaces to be accessed from outside of the client's process, you will need to enter interface information into the registry. Here are some valid interface entry subkeys:
HKCR\Interface Subkey | Meaning in Life |
---|---|
BaseInterface | Lists the direct base interface from which an interface derives. |
NumMethods | The number of methods in the interface, including inherited methods. |
ProxyStubClsid32 | The CLSID of the stub/proxy DLL used to marshal this interface. |
ProxyStubClsid | 16-bit version of stub/proxy DLL (if any). |
TypeLib | The LIBID for the type library that describes this interface. |
When you opt to use standard marshaling, you will need to add in the correct registry entries for each and every custom interface defined by the server to allow SCM to find the necessary stub/proxy DLL. For example, here is the HKCR\Interface listing for a well-known workhorse in COM, IClassFactory:
The ProxyStubClsid32 subkey specifies the CLSID of the stub/proxy DLL. As these stub/proxy DLLs are loaded and maintained by the COM runtime, you will not find ProgID equivalents. The value stored under ProxyStubClsid32 for IClassFactory is {00000320-0000-0000-C000-000000000046}, which maps to a listing under HKCR\CLSID revealing ole32.dll (recall, standard COM interfaces have marshaling code defined in the system-provided ole32.dll):
Type Library Marshaling
This brings us to the final (and simplest) technique used to marshal data between stubs and proxies. Type library marshaling (sometimes called universal marshaling) is an approach that leverages another system-provided stub/proxy DLL named oleaut32.dll. Yes, the "_aut_" infix does suggest OLE automation; however, this does not imply what you may be thinking.
The universal marshaler was developed to provide automatic marshaling support for the IDispatch interface, which makes exclusive use of variant compliant data types we examined in the previous chapter. However, using type library marshaling does not imply in any way that the interface being marshaled must derive from IDispatch. You may leverage the system-provided universal marshaler for any custom interface you create (such as IStats, IDraw55, IShapeEdit) provided that all your interface method parameters are variant compatible.
Type library marshaling has very appealing side effects. You will have no need to compile your own custom stub/proxy DLL. This means you will have one less file to distribute and register on machines using your COM server. Configuring your custom interfaces to leverage the universal marshaler entails an understanding of the [oleautomation] IDL attribute.
Configuring Your Interfaces to Use the Universal Marshaler
If you wish to use oleaut32.dll as your marshaler, you have a small handful of steps to take in your server code. First off, you should mark each of your interfaces with the [oleautomation] attribute. This informs the MIDL compiler that all parameters in the interface are variant compatible. For example, here is IDraw, marked as "OLE-OK":
// Every custom interface which wishes to use the universal marshaler must // be marked with the [oleautomation] attribute. [ object, uuid(4B475690-DE06-11d2-AAF4-00A0C9312D57), oleautomation ] interface IDraw : IUnknown { [helpstring("Draw Partner!")] HRESULT Draw(); };
Another very critical step is to make sure that your type library information is registered in the system registry. The universal marshaler makes use of your type information in order to build the stub and proxy DLL on the fly, and therefore it needs to know where to find the server's type library (*.tlb) file. As we have seen in Chapter 4, LIBID entries end up under HKCR\TypeLib.
As well, you must register each interface defined in your IDL file under HKCR\Interface subkey. To mark your custom interfaces as a consumer of oleaut32.dll, they must have the value of ProxyStubClsid32 set to the CLSID of the universal marshaler: {00020424- 0000-0000-C000-000000000046}.
If you then examine the HKCR\CLSID listing for {00020424-0000-0000-C000- 000000000046}, you will see the correct mapping to oleaut32.dll:
As a final step to access the universal marshaler, each HKCR\Interface listing must provide a TypeLib subkey mapping to the type library that holds the interface definitions. Here is a listing under HKCR\Interface for an interface that should give you warm-fuzzies by now.
If you like the idea of using the system-supplied universal marshaler, but shudder at the thought of writing a massive REG file for each of your interfaces, help is just around the corner.
| < Free Open Study > |
|