LOCCLIEN - Client Application of Local Server |
Tutorial Home |
Previous Lesson |
Lesson List |
Next Lesson |
The LOCCLIEN sample presents the same series of car-related components that were studied in previous lessons. The LOCCLIEN/LOCSERVE client/server pair is analogous to the DLLCLIEN/DLLSERVE pair. The main difference is that LOCCLIEN must cross process boundaries to access components in the out-of-process local server LOCSERVE. In contrast, DLLCLIEN could directly access components in the in-process server DLLSERVE. The LOCCLIEN lesson illustrates how a client can access and control components in an out-of-process local server.
The COM objects that LOCCLIEN manipulates are the car-related ones of previous samples, with the following interfaces: ICar, IUtility, and ICruise. LOCCLIEN works in conjunction with the separate LOCSERVE.EXE, which provides the COCar, COUtilityCar, and COCruiseCar COM objects.
LOCCLIEN.EXE creates its own COUtilityCruiseCar COM object, which is constructed by reusing the COCruiseCar COM object by containment and augmenting it with a native implementation of the IUtility interface. Because the COCruiseCar COM object class is an aggregate--that is, it reuses an inner COCar object by aggregation--LOCCLIEN illustrates nesting COM objects by mixed reuse. The composite COUtilityCruiseCar object is constructed by containment reuse of COCruiseCar, another composite COM object. COCruiseCar is constructed by aggregation reuse of the COCar COM object. The composition of COUtilityCruiseCar is also interesting because the COUtilityCruiseCar object's containment of the COCruiseCar object crosses the process boundary between LOCCLIEN and the out-of-process local server LOCSERVE.EXE. LOCCLIEN thus relies on standard marshaling support for the custom interfaces it uses on the COCruiseCar object. This marshaling support was separately built as MARSHAL.DLL in the earlier MARSHAL code sample.
For functional descriptions and a tutorial code tour of LOCCLIEN, see the Code Tour section in LOCCLIEN.HTM. For details on the external user operation of LOCCLIEN, see both the Usage and Operation sections in LOCCLIEN.HTM. To read LOCCLIEN.HTM, run TUTORIAL.EXE in the main tutorial directory and click the LOCCLIEN lesson in the table of lessons. You can also achieve the same thing by clicking the LOCCLIEN.HTM file after locating the main tutorial directory in the Windows Explorer. See also LOCSERVE.HTM in the main tutorial directory for more details on how LOCSERVE works and exposes its services to LOCCLIEN. You must build LOCSERVE.EXE before building LOCCLIEN. The makefile for LOCSERVE automatically registers that server in the registry, so you must build LOCSERVE before attempting to run LOCCLIEN.
Because client and server reside in separate processes, both LOCCLIEN and LOCSERVE rely on marshaling for the ICar, IUtility, and ICruise interfaces. This support was provided by the previous MARSHAL code sample, so you must also build the MARSHAL code sample prior to building and running LOCCLIEN and LOCSERVE.
For details on setting up your system to build and test the code samples in this COM Tutorial series, see TUTORIAL.HTM. The supplied MAKEFILE is Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE command in the Command Prompt window.
LOCCLIEN is an application that you can execute directly from Windows in the normal manner or from the Command Prompt window. No command line parameters are recognized by LOCCLIEN.
The client sample and other related samples must be compiled before you can run the client. For more details on building the samples, see Building the Code Samples.
If you have already built the appropriate samples, LOCCLIEN.EXE is the client executable to run for this sample.
The LOCCLIEN.EXE application provides the user interface for this lesson. It exercises the associated, but independent, LOCSERVE.EXE out-of-process local server. Here is a summary of operation from the standpoint of LOCCLIEN.EXE as a COM client of the LOCSERVE.EXE COM server.
The LOCCLIEN and LOCSERVE samples are directly analagous to the DLLCLIEN and DLLSERVE samples. The same components are used, and the same menu system exercises those objects. The main difference is that the client must use the standard marshaling support provided by the MARSHAL.DLL handler for the interfaces that reside across process boundaries. This standard marshaling is largely transparent if you use the Microsoft Interface Definition Language (MIDL) compiler and build a standard marshaling DLL.
As in the LOCSERVE code sample, support is provided for a trace logging facility that will display in the client an integrated trace log of activity in both client and server. Logging activity in this lesson is complicated by the process boundary and the methods of achieving such logging are covered in this lesson.
Although LOCCLIEN functions much like the prior DLLCLIEN and COMUSER code samples, we'll give a short review for those readers who are visiting this code sample out of sequence. The COM objects that are used in the LOCCLIEN and LOCSERVE code samples represent sport utility vehicles. We invent some basic feature sets for modeling such car objects. These feature sets are implemented as interfaces to COM objects. The ICar interface provides some basic car behavior: Shift, Clutch, Speed, and Steer. The IUtility interface provides off-road utility systems: Offroad and Winch. The ICruise interface provides automatic cruise control facilities: Engage and Adjust.
LOCCLIEN.EXE provides menus for creating, releasing, and invoking methods for four COM objects: COCar, COUtilityCar, COCruiseCar, and COUtilityCruiseCar. These objects have combinations of the ICar, IUtility, and ICruise interfaces. COCar objects expose the ICar interface. COUtilityCar objects expose the ICar and IUtility interfaces. COCruiseCar objects expose the ICar and ICruise interfaces. COUtilityCruiseCar objects expose the ICar, ICruise, and IUtility interfaces. As a result, COCar objects have only the basic car behavior (ICar). COUtilityCar objects have basic car behavior (ICar) with sport utility systems (IUtility). COCruiseCar objects have basic car behavior (ICar) with an automatic cruise control system (ICruise). COUtilityCruiseCar objects have basic car behavior (ICar), a cruise control system (ICruise), and a sport utility system (IUtility).
COCar is constructed as an aggregatable COM object with a native implementation of the ICar interface. COUtilityCar is constructed using containment and is implemented in LOCSERVE.EXE. For details, see the LOCSERVE lesson. COCruiseCar is constructed using aggregation and is also implemented in LOCSERVE.EXE. COUtilityCruiseCar is constructed using containment and is implemented in LOCCLIEN.EXE. In this sample, COUtilityCruiseCar reuses COCruiseCar by containment to illustrate nested object reuse with a mix of aggregation and containment.
LOCCLIEN.EXE presents a menu for each of these four main COM objects. Each menu has commands that call the methods of the various available interfaces. The code samples (both LOCCLIEN and LOCSERVE) have trace message log statements throughout. When you exercise the objects from LOCCLIEN.EXE, the main LOCCLIEN window will display a log of internal activity in these COM objects.
Menu Selection: File/Exit
Exits LOCCLIEN.
Menu Selection: Car/Create
Creates a COCar COM object. A checkmark beside the menu item indicates that there is already an instance of the object.
Menu Selection: Car/Release
Releases the COCar COM object.
Menu Selection: Car/ICar::Shift
Calls the ICar::Shift method on the COCar object.
Menu Selection: Car/ICar::Clutch
Calls the ICar::Clutch method on the COCar object.
Menu Selection: Car/ICar::Speed
Calls the ICar::Speed method on the COCar object.
Menu Selection: Car/ICar::Steer
Calls the ICar::Steer method on the COCar object.
Menu Selection: UtilityCar/Create
Creates the COUtilityCar COM object. A checkmark beside the menu item indicates that there is already an instance of the object.
Menu Selection: UtilityCar/Release
Releases the COUtilityCar COM object.
Menu Selection: UtilityCar/ICar::Shift
Calls the ICar::Shift method on the COUtilityCar object.
Menu Selection: UtilityCar/ICar::Clutch
Calls the ICar::Clutch method on the COUtilityCar object.
Menu Selection: UtilityCar/ICar::Speed
Calls the ICar::Speed method on the COUtilityCar object.
Menu Selection: UtilityCar/ICar::Steer
Calls the ICar::Steer method on the COUtilityCar object.
Menu Selection: UtilityCar/IUtility::Offroad
Calls the IUtility::Offroad method on the COUtilityCar object.
Menu Selection: UtilityCar/IUtility::Winch
Calls the IUtility::Winch method on the COUtilityCar object.
Menu Selection: CruiseCar/Create
Creates the COCruiseCar COM object. A checkmark beside the menu item indicates that there is already an instance of the object.
Menu Selection: CruiseCar/Release
Releases the COCruiseCar COM object.
Menu Selection: CruiseCar/ICar::Shift
Calls the ICar::Shift method on the COCruiseCar object.
Menu Selection: CruiseCar/ICar::Clutch
Calls the ICar::Clutch method on the COCruiseCar object.
Menu Selection: CruiseCar/ICar::Speed
Calls the ICar::Speed method on the COCruiseCar object.
Menu Selection: CruiseCar/ICar::Steer
Calls the ICar::Steer method on the COCruiseCar object.
Menu Selection: CruiseCar/ICruise::Engage
Calls the ICruise::Engage method on the COCruiseCar object.
Menu Selection: CruiseCar/ICruise::Adjust
Calls the ICruise::Adjust method on the COCruiseCar object.
Menu Selection: UtilityCruiseCar/Create
Creates the COUtilityCruiseCar COM object. A checkmark beside the menu item indicates that there is already an instance of the object.
Menu Selection: UtilityCruiseCar/Release
Releases the COUtilityCruiseCar COM object.
Menu Selection: UtilityCruiseCar/ICar::Shift
Calls the ICar::Shift method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/ICar::Clutch
Calls the ICar::Clutch method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/ICar::Speed
Calls the ICar::Speed method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/ICar::Steer
Calls the ICar::Steer method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/ICruise::Engage
Calls the ICruise::Engage method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/ICruise::Adjust
Calls the ICruise::Adjust method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/IUtility::Offroad
Calls the IUtility::Offroad method on the COUtilityCruiseCar object.
Menu Selection: UtilityCruiseCar/IUtility::Winch
Calls the IUtility::Winch method on the COUtilityCruiseCar object.
Menu Selection: Log/Clear
Clears the trace message log display.
Menu Selection: Log/Logging
Toggles the trace message logging facility on or off. A checkmark beside the menu item indicates that logging is on. Logging can be engaged but simply turned on or off. Unchecking this command turns the trace message logging facility off but does not disengage the logging mechanisms.
Menu Selection: Log/Copy
Copies the current contents of the trace message log to the Windows Clipboard.
Menu Selection: Help/LOCCLIEN Tutorial
Opens the LOCCLIEN.HTM tutorial file in the Web browser.
Menu Selection: Help/LOCSERVE Tutorial
Opens the LOCSERVE.HTM tutorial file in the Web browser.
Menu Selection: Help/MARSHAL Tutorial
Opens the MARSHAL.HTM tutorial file in the Web browser.
Menu Selection: Help/Read Source File
Displays the Open common dialog box so you can open a source file from this lesson or another one in the Windows Notepad.
Menu Selection: Help/About LOCCLIEN
Displays the About dialog box for this application, a standard part of this series of code samples. The code illustrates how to program the use of the CAboutBox class provided by APPUTIL.LIB.
Menu Selection: Help/About LOCSERVE
Displays the About dialog box for LOCSERVE.EXE, which is used by this application. In this series of code samples, partner servers like LOCSERVE are given their own About dialog box in the native resources of the server. This menu item calls the server function that displays this dialog box.
Files Description LOCCLIEN.TXT Short sample description. MAKEFILE The generic makefile for building the code sample application of this tutorial lesson. LOCCLIEN.H The include file for the LOCCLIEN application. Contains class declarations, function prototypes, and resource identifiers. LOCCLIEN.CPP The main implementation file for LOCCLIEN.EXE. Has WinMain and CMainWindow implementation, as well as the main menu dispatching. LOCCLIEN.RC The application resource definition file. LOCCLIEN.ICO The application icon resource. UTCRUCAR.H The class declaration for the COUtilityCruiseCar COM object. UTCRUCAR.CPP Implementation file for the COUtilityCruiseCar COM object. Also has the definition of the CreateUtilityCruiseCar function.
Like all code samples in the series, LOCCLIEN uses many of the utility classes and services provided by APPUTIL. For more details on APPUTIL, study the APPUTIL library source code in the sibling APPUTIL directory and APPUTIL.HTM in the main tutorial directory.
LOCCLIEN is consistent with earlier lessons to highlight the comparison between out-of-process local servers with in-process servers. LOCCLIEN retains the same set of COM objects as the previous DLLCLIEN lesson. A matching menu system to manipulate these objects is also retained. For details on the implementation and functionality of these COM objects, see the code tours for DLLCLIEN and DLLSERVE.
The goal of this lesson is to examine the client code needed to support cross-process manipulation of the components in the LOCSERVE server. We will tour internal behavior and examine a representative trace log.
The client remains largely the same when we move the car-related COM objects from an in-process server housing (DLLSERVE) to an out-of-process server housing (LOCSERVE). The most important change, while also convenient for instructional variety, is actually dictated by the nature of the current COM marshaling support for aggregation. The native COUtilityCruiseCar COM object in the DLLCLIEN lesson illustrated three levels of nested aggregation. COUtilityCruiseCar aggregated a native IUtility interface implementation with the COCruiseCar object housed in DLLSERVE. The COCruiseCar object, in turn, aggregated an ICruise interface implementation with the COCar object housed in DLLSERVE. This nested aggregation worked well, even though the aggregation spanned an object in the client and an inner object in the server. Despite the client/server distinction, however, the server was in the same process as the client. (DLLSERVE was an in-process server.)
With LOCCLIEN, however, the aggregation that spans the client and server would have to span a process boundary. The standard marshaling currently provided for the CreateInstance method of IClassFactory does not accept a non-NULL pointer to the controlling IUnknown (pUnkOuter). The CLASS_E_NOAGGREGATION error is returned by the marshaling proxy created in the previous MARSHAL code sample. What this means is that while aggregation is a powerful, efficient, and recommended way to reuse COM objects, it is currently supported only for in-process COM objects. For composite COM objects that must span process, apartment model thread, or machine boundaries, COM objects must be reused by containment.
In containment reuse, the composite object implements the interfaces of the contained inner object, creates an instance of the contained object, and caches pointers to the contained object's interfaces. The outer object implements delegating interfaces for the contained object's interfaces. The methods in these interfaces delegate their calls to the corresponding methods in the interfaces of the contained object. This delegation is done using the cached pointers to those interfaces. Delegation was covered in earlier lessons, where the COUtilityCar COM object was studied. The technique is employed in a similar way in LOCCLIEN to construct the COUtilityCruiseCar COM object. We'll now examine this in detail.
Here is the class declaration for COUtilityCruiseCar from UTCRUCAR.H.
class COUtilityCruiseCar : public IUnknown { public: // Main Object Constructor & Destructor. COUtilityCruiseCar(IUnknown* pUnkOuter); ~COUtilityCruiseCar(void); // A general method for initializing a newly created UtilityCruiseCar. HRESULT Init(void); // IUnknown members. Main object, non-delegating. STDMETHODIMP QueryInterface(REFIID, PPVOID); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // We get this ICar interface pointer via containment reuse of the // ICar interface in an instantiated COCruiseCar. ICar* m_pICar; // We get this ICruise interface pointer via containment reuse of the // ICruise interface in an instantiated COCruisecar. ICruise* m_pICruise; private: // We declare nested class interface implementations here. // We implement the ICar interface in this COUtilityCruiseCar COM // object class. This is a delegating interface for the one // in the contained COCruiseCar COM object. class CImpICar : public ICar { public: // Interface Implementation Constructor & Destructor. CImpICar(COUtilityCruiseCar* pBackObj, IUnknown* pUnkOuter); ~CImpICar(void); // IUnknown methods. STDMETHODIMP QueryInterface(REFIID, PPVOID); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // ICar methods. STDMETHODIMP Shift(short nGear); STDMETHODIMP Clutch(short nEngaged); STDMETHODIMP Speed(short nMph); STDMETHODIMP Steer(short nAngle); private: // Data private to this interface implementation of ICar. ULONG m_cRefI; // Interface Ref Count. COUtilityCruiseCar* m_pBackObj; // Parent Object back pointer. IUnknown* m_pUnkOuter; // Outer unknown for Delegation. }; // We implement the ICruise interface in this COUtilityCruiseCar COM // object class. This ICruise implementation is a delegating // interface for the one provided in the contained COCruiseCar COM // object. class CImpICruise : public ICruise { public: // Interface Implementation Constructor & Destructor. CImpICruise(COUtilityCruiseCar* pBackObj, IUnknown* pUnkOuter); ~CImpICruise(void); // IUnknown methods. STDMETHODIMP QueryInterface(REFIID, PPVOID); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // ICruise methods. STDMETHODIMP Engage(BOOL bOnOff); STDMETHODIMP Adjust(BOOL bUpDown); private: // Data private to this interface implementation of ICruise. ULONG m_cRefI; // Interface Ref Count. COUtilityCruiseCar* m_pBackObj; // Parent Object back pointer. IUnknown* m_pUnkOuter; // Outer unknown for Delegation. }; // We implement the IUtility interface in this COUtilityCruiseCar COM // object class. This is the native interface that we are using as an // augmentation to the existing COCruiseCar COM object class. class CImpIUtility : public IUtility { public: // Interface Implementation Constructor & Destructor. CImpIUtility(COUtilityCruiseCar* pBackObj, IUnknown* pUnkOuter); ~CImpIUtility(void); // IUnknown members. STDMETHODIMP QueryInterface(REFIID, PPVOID); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IUtility members. STDMETHODIMP Offroad(short nGear); STDMETHODIMP Winch(short nRpm); private: // Data private to this interface implementation of IUtility. ULONG m_cRefI; // Interface Ref Count. COUtilityCruiseCar* m_pBackObj; // Parent Object back pointer. IUnknown* m_pUnkOuter; // Outer unknown for Delegation. }; // Make the otherwise private and nested IUtility, ICruise, and ICar // interface implementations friend instantiations of this class. friend CImpICar; friend CImpICruise; friend CImpIUtility; // Private data of COUtilityCruiseCar COM objects. // Nested ICar implementation instantiation. CImpICar m_ImpICar; // Nested ICruise implementation instantiation. CImpICruise m_ImpICruise; // Nested IUtility implementation instantiation. This IUtility // interface is implemented inside this COUtilityCruiseCar object // as a native interface. CImpIUtility m_ImpIUtility; // Main Object reference count. ULONG m_cRefs; // Outer unknown (aggregation & delegation). Used when this // COUtilityCruiseCar object is being aggregated. IUnknown* m_pUnkOuter; };
As in all these code samples, the multiple interfaces are implemented as nested classes. Here are nested implementations of ICar, ICruise, and IUtility. If we were aggregating the COCruiseCar COM object rather than containing it, we would not have to nest the ICruise and ICar interface implementations; we would need only the cached pointers to those interfaces (m_pICar and m_pICruise above). With aggregation, such pointers provide direct access to the interfaces. With containment, however, the containing COM object must implement delegating interfaces for the interfaces used on the contained object. In this code example, the COCruiseCar object is contained, and its ICar and ICruise interfaces are implemented in LOCCLIEN as delegating interfaces.
Thus, we have implementations for all three interfaces. But since IUtility is a native implementation in this COUtilityCruiseCar COM object in the client, we only cache pointers to the interfaces exposed by the contained COCruiseCar object (m_pICar and m_pICruise).
The COUtilityCruiseCar::Init method from UTCRUCAR.CPP assigns those pointers.
HRESULT COUtilityCruiseCar::Init(void) { HRESULT hr; IClassFactory* pICFCruiseCar; // Set up the right pIUnknown for delegation. If we are being // aggregated then we pass the pUnkOuter in turn to any COM objects // that we are aggregating. m_pUnkOuter was set in the Constructor. IUnknown* pUnkOuter = (NULL == m_pUnkOuter) ? this : m_pUnkOuter; LOGF1("C: COUtilityCruiseCar::Init. pUnkOuter=0x%X",pUnkOuter); // Get a class factory for LocCruiseCar and issue IClassFactory's // CreateInstance method to manufacture a COCruiseCar COM object. LOG("C: COUtilityCruiseCar::Init. Obtain CruiseCar Class Factory."); hr = CoGetClassObject( CLSID_LocCruiseCar, CLSCTX_LOCAL_SERVER, NULL, IID_IClassFactory, (PPVOID)&pICFCruiseCar); if (SUCCEEDED(hr)) { LOG("C: COUtilityCruiseCar::Init. CruiseCar Class Factory obtained."); LOG("C: COUtilityCruiseCar::Init. Calling CFCruiseCar::CreateInstance."); // A NULL pUnkOuter is passed indicating that we are creating a // COCruiseCar component via the Containment reuse method. We can // currently pass nothing other than this NULL pUnkOuter because // aggregation is not supported across process or machine boundaries. // Since we are not requesting a COCruiseCar create for aggregation we // can directly request for the ICruise interface on the new object. hr = pICFCruiseCar->CreateInstance( NULL, IID_ICruise, (PPVOID)&m_pICruise); LOG("C: COUtilityCruiseCar::Init. Releasing CruiseCar Class Factory."); pICFCruiseCar->Release(); if (SUCCEEDED(hr)) { // Now that we have a valid interface pointer (m_pICruise) on the // new object, we can obtain and cache a pointer to that contained // object's ICar interface as well. We must QI using the obtained // ICruise pointer on the object to ensure that the marshaling proxy // is involved in the QueryInterface call to provide the pointer to // the ICar interface proxy. hr = m_pICruise->QueryInterface(IID_ICar, (PPVOID)&m_pICar); } } if (SUCCEEDED(hr)) { LOG("C: COUtilityCruiseCar::Init (CruiseCar Containment) Succeeded."); } else { LOG("C: COUtilityCruiseCar::Init (CruiseCar Containment) Failed."); } return (hr); }
Instead of the separate calls to CoGetClassObject and CreateInstance, we could have made a single call to CoCreateInstance. The two-step sequence is shown here for instructional purposes. The CoGetClassObject call specifies that the server execution context is on the local machine (CLSCTX_LOCAL_SERVER), and the CreateInstance call specifies NULL for the pUnkOuter argument. Because execution context parameter is CLSCTX_LOCAL_SERVER, a non-NULL pUnkOuter would return a CLASS_E_NOAGGREGATION error without ever actually calling the IClassFactory::CreateInstance implementation in the server. The NULL pUnkOuter means we are creating a LocCruiseCar component, which is reused by containment.
The successful CreateInstance call will store the m_pICruise pointer for us. This pointer will actually point into the client's own process space--to the ICruise interface proxy provided by the MARSHAL.DLL marshaling server we built previously. COM will have loaded this DLL on our behalf automatically as part of its support for standard marshaling. COM loads MARSHAL.DLL because the CreateInstance call specified an interface (IID_ICar) that is registered as a marshaled interface. As seen in the MARSHAL lesson, the registry entry for the IID_ICar interface has the path to its associated marshaling handler. From the client's perspective, then, the marshaling is transparent, and the contained object appears as if it were in the client process.
Once we get the pointer to the ICruise interface (m_pICar), we use it to call QueryInterface on the ICar interface of the contained COCruiseCar object. As in the server, we include MICARS.H for the interfaces that are marshaled. MICARS.H is generated during the build of the MARSHAL code sample.
How do those cached interface pointers complete support for containment? Here's an example from UTCRUCAR.CPP.
STDMETHODIMP COUtilityCruiseCar::CImpICruise::Engage( BOOL bOnOff) { LOG("C: COUtilityCruiseCar::CImpICruise::Engage Delegating."); // Delegate this call to the contained COCruiseCar's ICruise::Engage. m_pBackObj->m_pICruise->Engage(bOnOff); return NOERROR; }
This is the delegating implementation of the ICruise::Engage method. The interface implementation is nested, so the m_pBackObj pointer provides access to the cached m_pICruise pointer. It is then used to make the delegated call to the real interface in the contained COCruiseCar object. When this call is made, it goes through the standard marshaling proxy for this method of the ICruise interface. We built this standard marshaling support in the MARSHAL code sample.
LOCCLIEN supports a Help/About LOCSERVE menu selection. Because the server is in a different process, we can't directly address the server's address space to display its About dialog box. Here's how it's done (from CMainWindow::DoMenu in LOCCLIEN.CPP).
case IDM_HELP_ABOUTSERVER: { // Post a message to the Server to show its AboutBox dialog box. LOG("C: === Help Menu: About LOCSERVE."); HWND hWnd = FindWindow(NULL, TEXT(SERVER_WINDOW_TITLE_STR)); if (NULL != hWnd) PostMessage(hWnd, WM_COMMAND, IDM_HELP_ABOUT, NULL); } break;
The FindWindow function is called to find the window handle of the running local server. It can get the window handle even if the server is running hidden. The appropriate WM_COMMAND message is posted to the server application, which honors it as though a user had selected it from the server's menu. For this logic to work, both server and client must have the same defintion for IDM_HELP_ABOUT. This menu item works only if LOCCLIEN has already created a COM object provided by the LOCSERVE server. Until then, the server is not loaded and so cannot receive the posted message and display its About dialog box.
For symmetry, we'll list and tour a trace log of the same sequence of menu actions that we studied in the DLLCLIEN and COMUSER lessons. Here is the trace log for the Create command from the UtilityCruiseCar menu. Trace lines that begin with 'C:' are from the LOCCLIEN.EXE client. Lines that begin with 'L:' are from the LOCSERVE.DLL server.
C: === UtilityCruiseCar Menu: Create. C: CreateUtilityCruiseCar. pUnkOuter=0x0. C: COUtilityCruiseCar::CImpICar Constructor. Non-Aggregating. C: COUtilityCruiseCar::CImpICruise Constructor. Non-Aggregating. C: COUtilityCruiseCar::CImpIUtility Constructor. Non-Aggregating. C: COUtilityCruiseCar Constructor. m_pUnkOuter=0x0. C: COUtilityCruiseCar::Init. pUnkOuter=0x870E2C. C: COUtilityCruiseCar::Init. Obtain CruiseCar Class Factory.
After the command is selected in the client, the CreateUtilityCruiseCar function is called. It creates a new COUtilityCruiseCar object. The interface constructors and the main object constructor are executed. The Init method is called to create any inner objects. In this case there is one, COCruiseCar. Init calls the COM function CoGetClassObject to obtain the CruiseCar class factory.
L: LOCSERVE now logging to client. L: CServer::OpenFactories. Begin. L: CFCar::CImpIClassFactory Constructor. Non-Aggregating. L: CFCar Constructor. m_pUnkOuter=0x0. L: CFCar::AddRef. New cRefs=1. L: CFCar::AddRef. New cRefs=2. L: CFUtilityCar::CImpIClassFactory Constructor. Non-Aggregating. L: CFUtilityCar Constructor. m_pUnkOuter=0x0. L: CFUtilityCar::AddRef. New cRefs=1. L: CFUtilityCar::AddRef. New cRefs=2. L: CFCruiseCar::CImpIClassFactory Constructor. Non-Aggregating. L: CFCruiseCar Constructor. m_pUnkOuter=0x0. L: CFCruiseCar::AddRef. New cRefs=1. L: CFCruiseCar::AddRef. New cRefs=2. L: CServer::OpenFactories. End.
COM runs the LOCSERVE.EXE server, because the CLSID passed with the class factory request is in the system registry. The server creates all of its supported class factories at once by calling the CServer::OpenFactories method. AddRef is called twice to increment the class factory's reference count, once by the server itself and once by COM when the OpenFactories function registers the class factory with COM.
L: CFCruiseCar::QueryInterface. pIClassFactory returned. L: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=1. L: CFCruiseCar::AddRef. New cRefs=3. L: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCruiseCar::QueryInterface. pIClassFactory returned. L: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=2. L: CFCruiseCar::AddRef. New cRefs=4. L: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCruiseCar::QueryInterface. 'this' pIUnknown returned. L: CFCruiseCar::AddRef. New cRefs=5. L: CFCruiseCar::AddRef. New cRefs=6. L: CFCruiseCar::Release. New cRefs=5. L: CFCruiseCar::AddRef. New cRefs=6. L: CFCruiseCar::QueryInterface. pIClassFactory returned. L: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=3. L: CFCruiseCar::AddRef. New cRefs=7. L: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=2. L: CFCruiseCar::Release. New cRefs=6. L: CFCruiseCar::QueryInterface. pIClassFactory returned. L: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=3. L: CFCruiseCar::AddRef. New cRefs=7. L: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=2. L: CFCruiseCar::Release. New cRefs=6. L: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=1. L: CFCruiseCar::Release. New cRefs=5. C: COUtilityCruiseCar::Init. CruiseCar Class Factory obtained.
This sequence begins with a client request for a specific class factory (CoGetClassObject). The server is then loaded, and all the server's class factories are subsequently created and registered. The log above is a record of COM and the default marshaling for IClassFactory making various QueryInterface and AddRef calls to the CFCruiseCar class factory. The class factory ends up with five outstanding references (cRefs = 5) when control returns successfully to the client and a class factory is obtained.
C: COUtilityCruiseCar::Init. Calling CFCruiseCar::CreateInstance. L: CFCruiseCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x0. L: COCruiseCar::CImpICruise Constructor. Non-Aggregating. L: COCruiseCar Constructor. m_pUnkOuter=0x0. L: CServer::ObjectsUp. New cObjects=1. L: COCruiseCar::Init. L: CFCar::QueryInterface. pIClassFactory returned. L: CFCar::CImpIClassFactory::Addref. Delegating. New cI=1. L: CFCar::AddRef. New cRefs=3. L: CFCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCar::QueryInterface. pIClassFactory returned. L: CFCar::CImpIClassFactory::Addref. Delegating. New cI=2. L: CFCar::AddRef. New cRefs=4. L: CFCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCar::CImpIClassFactory::QueryInterface. Delegating. L: CFCar::QueryInterface. 'this' pIUnknown returned. L: CFCar::AddRef. New cRefs=5. L: CFCar::AddRef. New cRefs=6. L: CFCar::Release. New cRefs=5. L: CFCar::AddRef. New cRefs=6. L: CFCar::QueryInterface. pIClassFactory returned. L: CFCar::CImpIClassFactory::Addref. Delegating. New cI=3. L: CFCar::AddRef. New cRefs=7. L: CFCar::CImpIClassFactory::Release. Delegating. New cI=2. L: CFCar::Release. New cRefs=6. L: CFCar::QueryInterface. pIClassFactory returned. L: CFCar::CImpIClassFactory::Addref. Delegating. New cI=3. L: CFCar::AddRef. New cRefs=7. L: CFCar::CImpIClassFactory::Release. Delegating. New cI=2. L: CFCar::Release. New cRefs=6. L: CFCar::CImpIClassFactory::Release. Delegating. New cI=1. L: CFCar::Release. New cRefs=5. L: CFCar::QueryInterface. 'this' pIUnknown returned. L: CFCar::AddRef. New cRefs=6. L: CFCar::CImpIClassFactory::Release. Delegating. New cI=0. L: CFCar::Release. New cRefs=5. L: CFCar::Release. New cRefs=4. L: CFCar::Release. New cRefs=3. L: CFCar::QueryInterface. pIClassFactory returned. L: CFCar::CImpIClassFactory::Addref. Delegating. New cI=1. L: CFCar::AddRef. New cRefs=4. L: CFCar::Release. New cRefs=3. L: CFCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x870F00. L: COCar::CImpICar Constructor. Aggregating. L: COCar Constructor. m_pUnkOuter=0x870F00. L: CServer::ObjectsUp. New cObjects=2. L: COCar::QueryInterface. 'this' pIUnknown returned. L: COCar::AddRef. New cRefs=1. L: CFCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x870F28. L: CFCar::CImpIClassFactory::Release. Delegating. New cI=0. L: CFCar::Release. New cRefs=2. L: COCruiseCar::Init (New Aggregation of COCar) Succeeded. L: COCruiseCar::QueryInterface. pICruise returned. L: COCruiseCar::CImpICruise::Addref. Delegating. New cI=1. L: COCruiseCar::AddRef. New cRefs=1. L: CFCruiseCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x870F04. L: COCruiseCar::CImpICruise::QueryInterface. Delegating. L: COCruiseCar::CImpICruise::QueryInterface. Delegating. L: COCruiseCar::QueryInterface. pICruise returned. L: COCruiseCar::CImpICruise::Addref. Delegating. New cI=2. L: COCruiseCar::AddRef. New cRefs=2. L: COCruiseCar::CImpICruise::QueryInterface. Delegating. L: COCruiseCar::CImpICruise::QueryInterface. Delegating. L: COCruiseCar::CImpICruise::QueryInterface. Delegating. L: COCruiseCar::QueryInterface. 'this' pIUnknown returned. L: COCruiseCar::AddRef. New cRefs=3. L: COCruiseCar::AddRef. New cRefs=4. L: COCruiseCar::Release. New cRefs=3. L: COCruiseCar::AddRef. New cRefs=4. L: COCruiseCar::QueryInterface. pICruise returned. L: COCruiseCar::CImpICruise::Addref. Delegating. New cI=3. L: COCruiseCar::AddRef. New cRefs=5. L: COCruiseCar::CImpICruise::Release. Delegating. New cI=2. L: COCruiseCar::Release. New cRefs=4. L: COCruiseCar::QueryInterface. pICruise returned. L: COCruiseCar::CImpICruise::Addref. Delegating. New cI=3. L: COCruiseCar::AddRef. New cRefs=5. L: COCruiseCar::CImpICruise::Release. Delegating. New cI=2. L: COCruiseCar::Release. New cRefs=4. L: COCruiseCar::CImpICruise::Release. Delegating. New cI=1. L: COCruiseCar::Release. New cRefs=3.
Most of the internal activity logged above is a history of COM and the server creating the COCruiseCar object by aggregating a COCar object, also in the server. The CFCar class factory was invoked to create and aggregate a COCar object for the new COCruiseCar. We also see the events that return the ICruise interface pointer originally requested in the call to CreateInstance. When this activity is completed, the object count for the LOCSERVE server (cObjects) is 2: one for the inner COCar object and one for the COCruiseCar object itself.
C: COUtilityCruiseCar::Init. Releasing CruiseCar Class Factory. L: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=0. L: CFCruiseCar::Release. New cRefs=4. L: CFCruiseCar::Release. New cRefs=3. L: CFCruiseCar::Release. New cRefs=2. L: COCruiseCar::QueryInterface. ICar delegating. L: COCar::QueryInterface. pICar returned. L: COCar::CImpICar::Addref. Delegating. New cI=1. L: COCruiseCar::AddRef. New cRefs=4. L: COCar::CImpICar::Release. Delegating. New cI=0. L: COCruiseCar::Release. New cRefs=3. L: COCruiseCar::QueryInterface. ICar delegating. L: COCar::QueryInterface. pICar returned. L: COCar::CImpICar::Addref. Delegating. New cI=1. L: COCruiseCar::AddRef. New cRefs=4. C: COUtilityCruiseCar::Init (CruiseCar Containment) Succeeded. C: COUtilityCruiseCar::QueryInterface. 'this' pIUnknown returned. C: COUtilityCruiseCar::AddRef. New cRefs=1. C: CreateUtilityCruiseCar Succeeded. *ppv=0x870E2C.
When the client (still inside COUtilityCruiseCar::Init) has successfully created the inner COCruiseCar object, it releases the class factory pointer it had obtained. This results in a cascade of other releases, some of which are done from inside COM. Init then calls QueryInterface for the ICar interface to cache its address. Control eventually returns to the client, where a COUtilityCruiseCar is successfully created. The client holds a pointer to the IUnknown of COUtilityCruiseCar, and we see the QueryInterface and AddRef calls that obtain this reference. The reference count on this client-side object (cRefs) is only 1. All of the preceding activity was due to one menu selection: UtilityCruiseCar/Create.
Next, we exercise one of the interfaces on this newly created composite object. This interface is actually provided by an inner object (COCar) that is aggregated in a COCruiseCar object, which in turn is contained in the COUtilityCruiseCar object. Here is the ICar::Shift command from the UtilityCruiseCar menu.
C: === UtilityCruiseCar Menu: ICar::Shift C: --Obtaining Interface Pointer. C: COUtilityCruiseCar::QueryInterface. pICar returned. C: COUtilityCruiseCar::CImpICar::Addref. Delegating. New cI=1. C: COUtilityCruiseCar::AddRef. New cRefs=2. C: Interface obtained. *ppv=0x870E38 C: --Calling pICar->Shift C: COUtilityCruiseCar::CImpICar::Shift. Delegating. nGear=1. L: COCar::CImpICar::Shift. Called. nGear=1. C: --Releasing pICar C: COUtilityCruiseCar::CImpICar::Release. Delegating. New cI=0. C: COUtilityCruiseCar::Release. New cRefs=1.
This is not too complicated because everything, including the marshaling proxies, is set up. The client's QueryInterface method obtains a pointer to ICar (pICar) and calls ICar::Shift. Because the composite object gets this interface from a contained inner object, the call to Shift makes its way through the delegation interface and is executed directly in the server. Once Shift is called, the client releases the pICar pointer. This call to Release is also delegated--in this case, to the controlling IUnknown's Release--leaving a final reference count on COUtilityCruiseCar of 1 (reflecting the IUnknown pointer the client has on this created object). The ICar interface is released; the COUtilityCruiseCar object exists until we choose the Release command from the UtilityCruiseCar menu. Here it is:
C: === UtilityCruiseCar Menu: Release. C: COUtilityCruiseCar::Release. New cRefs=0. C: COUtilityCruiseCar::Destructor. L: COCruiseCar::CImpICruise::Release. Delegating. New cI=0. L: COCruiseCar::Release. New cRefs=3. L: COCar::CImpICar::Release. Delegating. New cI=0. L: COCruiseCar::Release. New cRefs=2. L: COCruiseCar::Release. New cRefs=1. L: COCruiseCar::Release. New cRefs=0. L: CServer::ObjectsDown. New cObjects=1. L: COCruiseCar::Destructor. L: COCar::Release. New cRefs=0. L: CServer::ObjectsDown. New cObjects=0. L: CServer::ObjectsDown. Closing down LOCSERVE server. L: COCar::Destructor. L: COCar::CImpICar Destructor. L: COCruiseCar::CImpICruise Destructor.
In this scenario, COUtilityCruiseCar is the only object we created from the client. This is contrived so we can show how the out-of-process local server is unloaded. In the log entries above, we see the cascade of releases and destructors on the COUtilityCruiseCar object and its inner objects (COCruiseCar and COCar). Eventually, the server's object count (cObjects) is decremented to 0. The CServer::ObjectsDown method detects this event and posts a WM_CLOSE message to the local server itself. This message causes the server program to exit its message loop, and it calls g_pServer->CloseFactories.
L: CServer::CloseFactories. Begin. C: COUtilityCruiseCar::CImpIUtility Destructor. C: COUtilityCruiseCar::CImpICruise Destructor. C: COUtilityCruiseCar::CImpICar Destructor. L: CServer::CloseFactories. Revoke CFCar. L: CFCar::QueryInterface. 'this' pIUnknown returned. L: CFCar::AddRef. New cRefs=3. L: CFCar::Release. New cRefs=2. L: CFCar::Release. New cRefs=1. L: CServer::CloseFactories. Revoke CFUtilityCar. L: CFUtilityCar::QueryInterface. 'this' pIUnknown returned. L: CFUtilityCar::AddRef. New cRefs=3. L: CFUtilityCar::Release. New cRefs=2. L: CFUtilityCar::Release. New cRefs=1. L: CServer::CloseFactories. Revoke CFCruiseCar. L: CFCruiseCar::QueryInterface. 'this' pIUnknown returned. L: CFCruiseCar::AddRef. New cRefs=3. L: CFCruiseCar::Release. New cRefs=2. L: CFCruiseCar::Release. New cRefs=1. L: CServer::CloseFactories. Release all factory interfaces. L: CFCar::Release. New cRefs=0. L: CFCar::Destructor. L: CFCar::CImpIClassFactory Destructor. L: CFUtilityCar::Release. New cRefs=0. L: CFUtilityCar::Destructor. L: CFUtilityCar::CImpIClassFactory Destructor. L: CFCruiseCar::Release. New cRefs=0. L: CFCruiseCar::Destructor. L: CFCruiseCar::CImpIClassFactory Destructor. L: CServer::CloseFactories. End. L: Exiting LOCSERVE local server application.
CServer::CloseFactories makes a series of calls to COM (by means of CoRevokeClassObject) to revoke registration of the class factories for CFCar, CFUtilityCar, and CFCruiseCar. We see some resultant calls to Release on these class factories by COM itself--in each case leaving a final reference count on the class factory of 1. CloseFactories then calls Release on the remaining class factory pointers that the client holds, causing reference counts to reach 0, at which time the the class factory COM objects are destroyed. The LOCSERVE server application is finally exited and is unloaded by Windows.
One final note about logging. You can run the LOCSERVE server in a visible state, in which case it will log its internal behavior to its own log display. In this way both LOCCLIEN and LOCSERVE can be visible on screen at the same time, with separate log displays of their own activity. Here's how to do it: Run LOCSERVE first (before LOCCLIEN) with the -Embedding command line switch, and then run LOCCLIEN. Both the LOCSERVE server and the LOCCLIEN client will be present on screen at the same time. Each running EXE will log its internal behavior to its own separate logging display.