COM Tutorial Samples |
Tutorial Home |
Previous Lesson |
Lesson List |
Next Lesson |
The MARSHAL2 sample shows an alternate and more advanced technique for building a proxy/stub marshaling DLL for the ICar, IUtility, and ICruise custom interfaces. The previous MARSHAL sample illustrated the system default method of producing the marshaling DLL. That method controlled the conditional compilation of the DLLDATA.C file to generate default definitions for the DllMain, DllRegisterServer, and DllUnregisterServer functions in the DLL. MARSHAL2 does not generate these default functions. Instead, they coded explicitly. Thus, this lesson has more detail on the content of these functions.
Although the default definitions of these functions are adequate for most programming, there may be occasions when you want more control over the content of the marshaling DLL. For example, you might want to perform some action within DllMain during DLL_PROCESS_ATTACH, you might want to code explicit control over the registration and unregistration of the marshaling server, or you might want to add standard module version information to the DLL resources. This sample covers these areas.
As in the MARSHAL sample, the Microsoft® Interface Definition Language compiler (MIDL.EXE) is used to compile the interface definitions (expressed using the MIDL language in the MICARS.IDL file). MIDL.EXE is a utility provided as part of the Microsoft Platform Software Development Kit (SDK). The MIDL compilation of MICARS.IDL generates additional source files: MICARS.H, MICARS_I.C, MICARS_P.C, and DLLDATA.C.
In the series of COM Tutorial code samples, MARSHAL2 is an alternate marshaling DLL that can replace MARSHAL.DLL produced in the MARSHAL sample and can work with later samples in the series to provide standard marshaling for the custom ICar, IUtility, and ICruise interfaces. To achieve this, just register MARSHAL2.DLL. This will override any previous registration of MARSHAL.DLL. Since both of these DLLs provide proxy/stub marshaling for the same interfaces, the one registered most recently will replace the other if it is registered.
For functional descriptions and a tutorial code tour of MARSHAL2, see the Code Tour section in MARSHAL2.HTM. For details on setting up the programmatic usage of MARSHAL2, see the Usage section in MARSHAL2.HTM. To read MARSHAL2.HTM, run TUTORIAL.EXE in the main tutorial directory and click the MARSHAL2 lesson in the table of lessons. You can also do the same thing by double-clicking the MARSHAL2.HTM file after locating the main tutorial directory in Windows Explorer. For a simpler way to provide the same standard marshaling functionality that MARHSAL2 provides, see MARSHAL.HTM.
The subsequent samples in the series that require marshaling of the ICar, IUtility, and ICruise interfaces normally rely on the registration of MARSHAL.DLL as the marshaling DLL. MARSHAL.DLL is produced in the previous MARSHAL sample. MARSHAL2's makefile does not automatically register MARSHAL2.DLL as the marshaling DLL for those car-related interfaces. However, this registration can be enabled in MARSHAL2's makefile by uncommenting the appropriate lines. Registering MARSHAL2 will replace any prior registration of MARSHAL.DLL. Although, these two marshaling DLLs are functionally equivalent, there are subtle differences in how they perform their self-registration. For more details on how MARSHAL2.DLL performs self-registration see below.
If enabled in the makefile, MARSHAL2's self-registration is done using the REGISTER.EXE utility built in the REGISTER sample. To build or run MARSHAL2, you should build the REGISTER code sample first.
For details on setting up your system to build and test the code samples in this COM Tutorial series, see Building the Code Samples. The supplied makefile (MAKEFILE) is Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE command in the Command Prompt window.
For convenient use in Microsoft's Visual Studio, a project file is provided for each sample. To load the project for the MARSHAL2 sample, you can run Visual Studio at the Command Prompt in the sample's directory as follows:
MSDEV MARSHAL2.DSP
You can also simply double-click the MARSHAL2.DSP file in the Windows Explorer to load a sample's project into Visual Studio. From within Visual Studio you can then browse the C++ classes of the sample source and generally perform the other edit-compile-debug operations. Note that, as part of the Platform SDK, the compilation of these samples from within Visual Studio requires the proper setting of directory paths in Visual Studio. For more details, see Building the Code Samples.
MARSHAL2 is a DLL that is built solely as a marshaling DLL for the specified interfaces. Although it can be implicitly loaded by linking to its associated .LIB file, it is normally used on behalf of a COM client that functions the server across apartment, process, or machine boundaries. COM loads this DLL automatically in these situations. Before COM can find and load MARSHAL2.DLL to support marshaling of its supported interfaces, the MARSHAL2 server must be registered in the registry as the marshaling server for those interfaces. MARSHAL2 is a self-registering in-process server. If enabled (see above), the makefile that builds this sample automatically registers the server in the registry. You can manually initiate its self-registration by issuing the following command at the command prompt in the MARSHAL2 directory:
nmake register
This assumes that you have a compilation environment set up. If not, you can also directly invoke the REGISTER.EXE command at the command prompt while in the MARSHAL2 directory.
..\register\register.exe marshal2.dll
These registration commands require a prior build of both the REGISTER sample and the MARSHAL2 sample.
In this series, the makefiles use the REGISTER.EXE utility from the REGISTER sample. Recent releases of the Microsoft Platform SDK and Visual C++® include a utility, REGSVR32.EXE, which can be used in a similar fashion to register in-process servers and marshaling DLLs.
Files Description MARSHAL2.TXT Short description of the sample. MAKEFILE The generic makefile for building the MARSHAL2.DLL code sample. MARSHAL2.CPP The main implementation file for MARSHAL2.DLL. Has DllMain and the self-registration functions. MARSHAL2.DEF The module definition file. Exports server housing functions. MARSHAL2.RC The DLL resource definition file for the executable. MARSHAL2.ICO The icon resource for the executable. MICARS.IDL The MIDL interface definitions for ICar, IUtility, and ICruise. MICARS.H Generated by compiling MICARS.IDL. The interface include file for the defined interfaces. MICARS_I.C Generated by compiling MICARS.IDL. The data definitions of the GUIDs for the marshaled interfaces. MICARS_P.C Generated by compiling MICARS.IDL. The actual proxy and stub functions for the interface methods. DLLDATA.C Generated by compiling MICARS.IDL. DLL data routines for the proxies. MARSHAL2.DSP Microsoft Visual Studio Project file.
This code sample covers explicit definitions of the DllMain, DllRegisterServer, and DllUnregisterServer functions in a standard marshaling server which duplicates the functionality of the MARSHAL sample by providing standard marshaling for the same ICar, IUtility, and ICruise custom interfaces. For more details on the general topic of standard marshaling, see the MARSHAL sample. This sample focuses primarily on coding an explicit DLL framework for the marshaling server.
MARSHAL2 uses many of the utility classes and services provided by APPUTIL. For more details on APPUTIL, study the APPUTIL library's source code in the sibling APPUTIL directory and APPUTIL.HTM in the main tutorial directory.
MARSHAL2.DLL is very similar to the in-process COM servers produced in earlier samples and is a COM server that offers standard marshaling services for the interfaces it is registered to handle. You create MARSHAL2 DLL by compiling some source files that are automatically generated by the MIDL compiler when it processes the primary source for this sample, MICARS.IDL. This file defines the custom interfaces.
The MARSHAL2 DLL is coded in much the same way as the DLLSERVE server from an earlier lesson. A good place to start looking at the code is the DLL framework code in MARSHAL2.CPP. The following files are included by MARSHAL2.CPP:
#include <windows.h> #include <ole2.h> #include <apputil.h> #include "micars.h"
The MIDL compiler generates MICARS.H when it compiles MICARS.IDL. MICARS.H looks very similar to the ICARS.H interface declaration file from previous samples in this series. MICARS.H is used as an include file when compiling the clients and servers that require the interface marshaling it supports. It is included by MARSHAL2.CPP when MARSHAL2.DLL is compiled by the C++ compiler. In the LOCCLIEN sample, LOCCLIEN.CPP also includes this file for its declarations of the ICar, IUtility, and ICruise interfaces. Because the later samples like LOCSERVE and LOCCLIEN rely on MARSHAL.DLL, the MARSHAL2 makefile copies the generated MICARS.H file to the common INC directory.
The rest of MARSHAL2.CPP should be familiar. The DllMain function and the self-registration functions DllRegisterServer and DllUnregisterServer were presented in earlier lessons. The DllMain coded explicitly in this sample allows you to honor DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH.
Server registration and unregistration is different now, because interfaces, rather than components, are being registered and unregistered. The following is the DllRegisterServer function from MARSHAL2.CPP:
STDAPI DllRegisterServer(void) { HRESULT hr = NOERROR; TCHAR szID[GUID_SIZE+1]; TCHAR szIFace[GUID_SIZE+32]; TCHAR szCLSID[GUID_SIZE+32]; TCHAR szModulePath[MAX_PATH]; // Obtain the path to this module's executable file for later use. GetModuleFileName( g_hDllInst, szModulePath, sizeof(szModulePath)/sizeof(TCHAR)); /*--------------------------------------------------------------------- Create registry entries for the ICar Interface. ---------------------------------------------------------------------*/ // Create some base key strings. StringFromGUID2(IID_ICar, szID, GUID_SIZE); lstrcpy(szIFace, TEXT("Interface\\")); lstrcat(szIFace, szID); lstrcpy(szCLSID, TEXT("CLSID\\")); lstrcat(szCLSID, szID); // Create the HKEY_CLASSES_ROOT\Interface entries. SetRegKeyValue( szIFace, NULL, TEXT("ICar")); SetRegKeyValue( szIFace, TEXT("ProxyStubClsid32"), szID); SetRegKeyValue( szIFace, TEXT("NumMethods"), TEXT("7")); // Create the HKEY_CLASSES_ROOT\CLSID entries. SetRegKeyValue( szCLSID, NULL, TEXT("ICar Proxy/Stub Factory")); SetRegKeyValue( szCLSID, TEXT("InprocServer32"), szModulePath); ... ... // Similar code for the IUtility and ICruise interfaces. ... ... return hr; }
The ICar interface registration appears under two main registry keys: HKEY_CLASSES_ROOT\Interface\[guid] and HKEY_CLASSES_ROOT\CLSID\[guid]. Note that this sample assumes that all registration is under the main registry key of HKEY_CLASSES_ROOT. The content of the HKEY_CLASSES_ROOT branch is replicated under HKEY_LOCAL_MACHINE\SOFTWARE\Classes. These two branches of the Registry can be used interchangeably.
The CLSIDs used above in DllRegisterServer are the same as the GUIDS defined for the interfaces. In this case, they are defined in MICARS.IDL, and the MIDL compiler places them in the generated MICARS.H file. The reference above to IID_ICar is thus given meaning by MICARS.H which is an include file at the beginning of MARSHAL2.CPP. The double use of the interface GUIDs as CLSIDs is permitted because the interface IID will not be confused with the server's CLSID--the same GUID can be safely used for completely different things.
The registration code above differs from the default registration code used in the previous MARSHAL sample, which used the first interface GUID (IID_ICar) as the CLSID. In that sample, the ICar, IUtility, and ICruise proxy/stub marshaling components were all registered under that single CLSID. However, in DllRegisterServer above, each interface marshaling component is registered with a different CLSID. These CLSIDs are IID_ICar, IID_IUtility, and IID_ICruise. Thus, in this sample there is a separate CLSID entry for each marshaled interface. This variation is possible here because of the explicit control over the coding of DllRegisterServer and DllUnregisterServer. Of course, your explicit coding of these functions could just as well mimic the single CLSID technique used in the default functions of the MARSHAL sample.
The value of the NumMethods entry is a count that includes both the IUnknown methods and the methods unique to the interface. For the ICar interface entry above, the count is 7, including three methods for IUnknown (QueryInterface, AddRef, Release) and four for ICar (Shift, Clutch, Speed, Steer).
After you enable registration in the makefile (see above) and build this code sample, you can view the registered entries using the Registry Editor (REGEDT32.EXE for Windows NT, REGEDIT.EXE for Windows 95). MARSHAL2's registry entries for the ICar interface look like the following:
HKEY_CLASSES_ROOT \CLSID \{0002DA00-0000-0000-C000-000000000046} = "ICar Proxy/Stub Factory" \InprocServer32 = "D:\TUTSAMP\MARSHAL2\MARSHAL2.DLL" HKEY_CLASSES_ROOT \Interface \{0002DA00-0000-0000-C000-000000000046} = "ICar" \ProxyStubClsid32 = "{0002DA00-0000-0000-C000-000000000046}" \NumMethods = "7"
Under HKEY_CLASSES_ROOT\CLSID, this marshaling DLL appears with the in-process COM server entry, InprocServer32. COM treats this standard marshaling server as an in-process server.
As in earlier lessons, the DllUnregisterServer function in MARSHAL2.CPP simply removes the registry entries created by the DllRegisterServer function. For example, see DllUnregisterServer in MARSHAL2.CPP.
One final note about the self-registration of MARSHAL2.DLL. As in other self-registering COM servers in this tutorial series, for instructional purposes we have written our own support for registration and unregistration. You, however, can forgo writing a main module file like MARSHAL2.CPP and a main module resource file like MARSHAL2.RC, and instead easily obtain a default implementation for DllMain, DllRegisterServer, and DllUnregisterServer by conditionally compiling your code. You do this by setting a compilation switch, -DREGISTER_PROXY_DLL, when you compile DLLDATA.C. For more details see the MARSHAL sample.
You may have noticed that MARSHAL2.CPP lacks the familiar COM in-process server functions DllGetClassObject and DllCanUnloadNow. As in the MARSHAL standard marshaling sample, explicit definitions of these functions are not needed because compiling DLLDATA.C generates default implementations for you. Those implementations, however, must still be exported from this DLL. Standard marshaling DLLs require the DllGetClassObject, DllCanUnloadNow, and GetProxyDllInfo exports. You need only export these three functions; you don't need to implement them within this standard marshaling DLL. Of course, you must also export the DllRegisterServer and DllUnregisterServer functions if you have defined them in the DLL.
From the makefile, the following linker switches that direct the Link of MARSHAL2 DLL:
# Link the object and resource binaries into the target DLL binary. # Build the import LIB file so apps can link to and use this DLL. $(DLL).dll: $(DLLOBJS) $(TDIR)\$(DLL).res $(LINK) @<< $(LINKFLAGS) $(dlllflags) -out:$@ -base:0x1C000000 -def:$*.def -implib:$*.lib -map:$(TDIR)\$*.map $(DLLOBJS) $(TDIR)\$*.res $(olelibsdll) $(APPLIBS) <<
As used in the MARSHAL sample, the Link of MARSHAL2.DLL uses a -def command line switch to specify a MARSHAL2.DEF file to use for the export of the necessary marshaling server functions. The following is MARSHAL2.DEF:
LIBRARY MARSHAL2 DESCRIPTION 'MARSHAL2: COM Tutorial Sample. Copyright Microsoft Corp., 1997' EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE GetProxyDllInfo PRIVATE
The requied exports for the default functions in this server DLL are marked for internal, private use only by COM.
A .RES resource file is linked into the final exeutable. Unlike MARSHAL.DLL and like earlier in-process servers in this series, this marshaling server DLL contains such resources as standard version information. For more details, see MARSHAL2.RC.