REMCLIEN -- Distributed COM (DCOM) Remote Client


SUMMARY
=======

The REMCLIEN sample shows how a client can access and control components
in an out-of-process remote server. In this lesson the previously studied
out-of-process server APTSERVE is used, but now it is accessed across
machine boundaries by REMCLIEN. In earlier lessons, APTCLIEN accessed
APTSERVE's components on the same machine by crossing only process and
thread boundaries.

No changes to APTSERVE are required for REMCLIEN to work. Distributed COM
(DCOM) supports local/remote transparency between client and server.

The REMCLIEN sample presents the car-related components that were studied
in previous lessons. These COM objects use the following interfaces: ICar,
IUtility, and ICruise. REMCLIEN works in conjunction with the separate
APTSERVE.EXE, which provides the COCar, COUtilityCar, and COCruiseCar
COM objects.

REMCLIEN.EXE creates its own COUtilityCruiseCar COM object 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 a composite--that is, it reuses an inner COCar object by
containment--REMCLIEN illustrates nested reuse of COM objects. The
composite COUtilityCruiseCar object reuses COCruiseCar, another composite
COM object, by containment. COCruiseCar further reuses the COCar COM
object by containment.

The composition of COUtilityCruiseCar is also interesting because
the COUtilityCruiseCar object's containment of the COCruiseCar object
crosses the machine boundary between REMCLIEN and the out-of-process
remote server APTSERVE.EXE. REMCLIEN uses 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 REMCLIEN, see the
Code Tour section below. See also APTSERVE.TXT in the sibling \APTSERVE
directory for more details on how APTSERVE works and exposes its services
to REMCLIEN. The makefile for APTSERVE automatically registers that
server in the registry of the host machine, so you must build APTSERVE on
the remote machine before attempting to run REMCLIEN. For details on the
external user operation of REMCLIEN, see the Operation section below.

Because client and server reside in separate processes on different
machines, both REMCLIEN and APTSERVE 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 on both machines prior to building and running REMCLIEN with
APTSERVE.

To set up for the proper operation of REMCLIEN on one machine to control
APTSERVE on the other, both machines must be running Windows NT
Workstation 4.0 or above. Earlier versions do not support Distributed COM
(DCOM). The two machine must be connected in a properly configured
network. For details on setting up a network, see your Windows NT product
documentation or the Windows NT Resource Kit. Also see the "Network and
Setup Issues" section at the end of this tutorial text for more details on
machine and network setup for running REMCLIEN.

For general details on setting up your system to build and test Win32 code
samples such as those in this OLE Tutorial series, see TUTORIAL.TXT. The
supplied MAKEFILE is Microsoft NMAKE-compatible. To create a debug build,
issue the NMAKE command in the Command Prompt window.

Usage
-----

REMCLIEN 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 REMCLIEN. REMCLIEN will not currently run on
the Windows 95 operating system until the necessary Distributed COM is
supported there.


OPERATION
=========

The REMCLIEN.EXE application provides the user interface for this lesson.
It exercises the associated, but independent, APTSERVE.EXE out-of-process
remote server. Here is a summary of operation from the standpoint of
REMCLIEN.EXE as a client of APTSERVE.EXE.

The REMCLIEN and APTSERVE samples are directly analogous to the APTCLIEN
and APTSERVE samples presented in earlier lessons. The same components are
used, and almost 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 server for the interfaces that reside across
machine boundaries rather than across process boundaries. This standard
marshaling is largely transparent if you use the Microsoft Interface
Definition Language (MIDL) compiler to build a standard marshaling DLL.

Since two machines are involved in this lesson, we will refer to the
machine where APTSERVE runs as Machine-S (server) and to the machine where
REMCLIEN runs as Machine-C (client).

To run REMCLIEN and obtain the log listings for this tutorial, Machine-C
was set up to compile Win32 applications, and all of the COM tutorial code
samples were compiled there under Windows NT 4.x. Among other things, this
build process registered the necessary interfaces in the registry on
Machine-C for standard marshaling using the MARSHAL marshaling server on
that machine. Using the C$ drive share predefined on Machine-S, a complete
copy of the built \MSTOOLS\SAMPLES\OLE\COM branch was copied across the
network from Machine-C to Machine-S.

With all the sample executables on Machine-S, the REGALL batch file was
run from the COM directory there. This registered the APTSERVE server and
its interfaces in the registry on Machine-S. The MARSHAL standard
marshaling server located on that machine is used to register the
interfaces there.

Alternately, you can copy the uncompiled source of the COM branch from
Machine-C to Machine-S and run MAKEALL.BAT on Machine-S. This will perform
a complete build of all the executables and automatically register the
APTSERVE and MARSHAL servers on Machine-S. However, it is quicker to
perform a complete build on one machine and copy the COM branch (with
built executables) to the other machine and run REGALL.BAT on the other
machine.

With the executables present and the servers properly registered on both
machines, the REMCLIEN application can be run on Machine-C, relying on COM
to automatically locate and load the remote APTSERVE server on Machine-S.
You must be logged on under the same user name and password on both
machines, and the accounts on both machines must have Administrator
privilege.

As in the APTCLIEN code sample, support is provided in REMCLIEN for a
trace logging facility.  However, REMCLIEN displays only the trace log of
its own internal client behavior. Because the APTSERVE server is running
on a remote machine, APTSERVE's own trace logging facility is used to
display its internal behavior on the remote Machine-S display.

To run client and server so that both trace logs are displayed on each
machine, start APTCLIEN on Machine-S before running REMCLIEN on Machine-C.
On Machine-S, from the APTCLIEN Car menu, choose Create. This command
will load the APTSERVE out-of-process server on Machine-S. As in the
APTCLIEN and LOCCLIEN lessons, loading APTSERVE will provide an integrated
logging display of the internal behavior of both APTCLIEN and APTSERVE on
Machine-S.

You can then start REMCLIEN on Machine-C, where its internal client
behavior will be logged in its own separate trace log display on
Machine-C. From the REMCLIEN File menu, choose Set Remote Machine to set
the remote machine's name (for example, Machine-S) in the Remote Server
Info dialog box. This name is the network machine name of the computer
where the remote APTSERVE server is located and registered. You must
specify this name before you can run REMCLIEN. DCOM permits the machine
name to be specified in a valid UNC format (for example, Machine-S or
\\Machine-S) or DNS format (for example, www.mynode.com or 135.5.33.19).

If you then use REMCLIEN's Car menu to create a car object (from
Machine-C), COM will find APTSERVE already loaded on Machine-S and will
access this loaded instance. You will then see the log (on Machine-S) of
APTSERVE's internal server behavior as you control it from either APTCLIEN
on Machine-S or from REMCLIEN on machine-C. This example illustrates how a
single COM server can be controlled simultaneously from multiple clients,
in this case one local and one remote. APTSERVE supports such multiple
client access to it by partitioning the COM server into separate apartment
model threads and ensuring mutually exclusive access to shared server and
class factory data.

By configuring the server trace log in the manner used above, you can see
the inner behavior of a multithread server with multiple clients. On
Machine-S you can freely create and manipulate components in APTSERVE,
treating it as an out-of-process local server from APTCLIEN (on the same
machine). At the same time, on Machine-C you can freely create and
manipulate other component instances in APTSERVE, treating it as an
out-of-process remote server from REMCLIEN.  In both cases you can watch
internal APTSERVE behavior in the APTCLIEN trace log display on Machine-S.

Although REMCLIEN functions much like APTCLIEN, LOCCLIEN, DLLCLIEN and
COMUSER, 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 REMCLIEN
and APTSERVE 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.

REMCLIEN.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 APTSERVE.EXE. For details, see the
APTSERVE lesson. COCruiseCar is constructed using containment and is also
implemented in APTSERVE.EXE. COUtilityCruiseCar is constructed using
containment and is implemented in REMCLIEN.EXE. In this sample,
COUtilityCruiseCar reuses COCruiseCar by containment to illustrate nested
COM object reuse that spans thread and machine boundaries.

REMCLIEN.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 REMCLIEN and APTSERVE) have trace
message log statements throughout. When you exercise the objects from
REMCLIEN.EXE, the main REMCLIEN window will display a log of only the
internal client activity on Machine-C.  As described above, the trace log
of internal APTSERVE server activity will be displayed on Machine-S.

Menu Selection: File/Exit
Exits REMCLIEN.

Menu Selection: File/Set Remote Machine...
Displays the Remote Server Info dialog box, in which you specify the
network machine name of the computer where the remote APTSERVE server is
located. You must specify this name in a valid UNC or DNS format before
you can run REMCLIEN.

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/Read REMCLIEN.TXT
Opens the REMCLIEN.TXT file (this file) in the Windows Notepad.

Menu Selection: Help/Read APTSERVE.TXT
Opens the APTSERVE.TXT file from the sibling \APTSERVE directory in the
Windows Notepad.

Menu Selection: Help/Read MARSHAL.TXT
Opens the MARSHAL.TXT file from the sibling \MARSHAL directory in the
Windows Notepad.

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 REMCLIEN
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.


CODE TOUR
=========

Files          Description

REMCLIEN.TXT   This file.
MAKEFILE       The generic makefile for building the code sample
               application of this tutorial lesson.
REMCLIEN.H     The include file for the REMCLIEN application. Contains
               class declarations, function prototypes, and resource
               identifiers.
REMCLIEN.CPP   The main implementation file for REMCLIEN.EXE. Has WinMain
               and CMainWindow implementation, as well as the main menu
               dispatching.
REMCLIEN.RC    The application resource definition file.
REMCLIEN.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, REMCLIEN uses many of the utility
classes and services provided by APPUTIL. For more details on APPUTIL,
study the APPUTIL library source code and APPUTIL.TXT, which are located
in the sibling \APPUTIL directory.

REMCLIEN is consistent with earlier lessons to highlight the comparison of
out-of-process remote servers with out-of-process local servers. REMCLIEN
retains the same set of COM objects as the previous LOCCLIEN 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 LOCCLIEN, LOCSERVE, APTCLIEN, and APTSERVE.

This lesson examines the client code needed to support cross-machine
remote manipulation of the components in the multi-apartment threaded
APTSERVE server. We will tour the client source code and its internal
behavior using representative trace logs.

No changes to the APTSERVE server sample need to be made, and only minimal
changes are needed to transform the APTCLIEN local client into the
REMCLIEN remote client. This emphasizes COM's local/remote transparency.
The following code tour will thus focus on evolving the APTCLIEN code into
the REMCLIEN code.

The main changes center on a different way of using COM to create
instances of the components housed in the remote server.  The
CoGetClassObject call is made with appropriate parameters to direct COM to
locate, load, and set up standard marshaling for components in the remote
server.

The most important new information that CoGetClassObject needs is defined
in a COSERVERINFO structure.  Here is the code for this in REMCLIEN.CPP.

  // Here is a structure for storing the remote server info.
  COSERVERINFO ServerInfo;
  // Storage for the user entered remote machine name.
  TCHAR szMachineName[MAX_PATH] = TEXT("");
  OLECHAR wszMachineName[MAX_PATH];

The important member of this structure is a pointer to string storage for
the network name of the machine where the remote COM server resides. Above
are two such string variables: szMachineName is used for normal user input
string storage from a dialog box, and wszMachineName is the actual storage
pointed to by the ServerInfo structure.  wszMachineName is used by COM and
must be of Unicode type OLECHAR.  Depending on whether REMCLIEN is
compiled for Unicode, a string conversion may be required to obtain the
Unicode version of the string.  The ServerInfo structure must be
initialized with the pointer to wszMachineName.  This is done in the
application's InitApplication function in REMCLIEN.CPP as follows

    ...
    // Zero the ServerInfo structure.
    memset(&ServerInfo, 0, sizeof(COSERVERINFO));

    // Init the remote machine name.
    wszMachineName[0] = 0;
  #ifdef UNICODE
    ServerInfo.pwszName = &szMachineName[0];
  #else
    ServerInfo.pwszName = &wszMachineName[0];
  #endif
    ...

First, a memset call is used to zero the entire ServerInfo structure
contents. If the code is compiled for Unicode, no conversions are needed,
and the ServerInfo.pwszName pointer is initialized to the TCHAR string
array, szMachineName. Otherwise, it is initialized to point to a string
array that is explicitly defined as type OLECHAR, wszMachineName.

We will examine later how the string is assigned a value. For now, we
assume this string has the correct machine name. The ServerInfo structure
is used when component instances are created at the request of the
REMCLIEN user: Car, UtilityCar, and CruiseCar in source file REMCLIEN.CPP;
UtilityCruiseCar in source file UTCRUCAR.CPP. For example, when the user
requests creation of a CruiseCar component, the following code in
REMCLIEN'S DoMenu function creates the remote COCruiseCar COM object
residing in the APTSERVE server. The following code is found in
REMCLIEN.CPP.

    ...
    case IDM_CCAR_CREATE:
      LOG("C: === CruiseCar Menu: Create.");
      if (NULL == m_pCruiseCar)
      {
        // Call OLE services to create a remote instance.
        hr = CreateRemote(
               &ServerInfo,
               CLSID_AptCruiseCar,
               (PPVOID)&m_pCruiseCar);
        if (SUCCEEDED(hr))
        {
          ::CheckMenuItem(
              hMenu,
              IDM_CCAR_CREATE,
              MF_BYCOMMAND | MF_CHECKED);
          LOG("C: CruiseCar creation succeeded.");
        }
        else
          LOG("C: ???? CruiseCar creation failed.");
      }
      else
        LOG("C: ???? CruiseCar already exists.");
      break;
    ...

A common CreateRemote function is used in several such creation calls in
DoMenu. Here is its definition in REMCLIEN.CPP.

  HRESULT CreateRemote(
            COSERVERINFO* pServerInfo,
            REFCLSID rclsid,
            PPVOID ppv)
  {
    HRESULT hr = E_FAIL;
    IClassFactory* pICF = NULL;
    HCURSOR hCurPrev;
    HCURSOR hCurWait = LoadCursor(NULL, IDC_WAIT);

    // Ensure a remote machine name was specified by user.
    if (pServerInfo->pszName[0])
    {
      // Change cursor to the hour glass. Things could take awhile working
      // across the network.
      hCurPrev = SetCursor(hCurWait);

      // Get the COM Object Class Factory.
      hr = CoGetClassObject(
             rclsid,
             CLSCTX_REMOTE_SERVER,
             pServerInfo,
             IID_IClassFactory,
             (PPVOID)&pICF);
      LOGERROR("C:CoGetClassObject.",hr);
      if (SUCCEEDED(hr))
      {
        // Use Class Factory to create an instance of the COM object.
        hr = pICF->CreateInstance(
                     NULL,
                     IID_IUnknown,
                     ppv);
        pICF->Release();
        LOGERROR("C:CreateInstance.",hr);
      }

      // Set Cursor back to what it was.
      SetCursor(hCurPrev);
    }
    else
    {
      LOG("C: ???? Specify remote machine name first.");
    }

    return hr;
  }

This is a familiar sequence. A call to CoGetClassObject is used to obtain
a class factory interface for a component. The class factory is then
called to create an instance of the COM object and to return an interface
pointer for the new object.  The significant difference in REMCLIEN is how
the CLSCTX_REMOTE_SERVER parameter is used to direct COM to find the
server remotely.  A pointer to the ServerInfo structure is also passed to
provide the necessary network machine name of the computer where the
remote server resides.

The LOGERROR macro is used to check an HRESULT for error and if necessary,
fetch the associated human-readable error message string from the system
tables and log that message for display.  See APPUTIL.TXT for more details
on the use of the LOGERROR macro.

In this code sample, a remote server is explicitly requested. Here are
some of the important CLSCTX_* values that can be passed for the execution
context. Others are also honored. See the Win32 SDK documentation for more
details.

  CLSCTX_INPROC_SERVER
    Load the in-process server (DLL).
  CLSCTX_LOCAL_SERVER
    Load the out-of-process local server (EXE).
  CLSCTX_REMOTE_SERVER
    Load the out-of-process remote server (EXE).
  CLSCTX_SERVER
    Load in-process, out-of-process local, or out-of-process remote server
    with COM making the attempt in that order.

For example, if the code above had specified CLSCTX_SERVER, then COM would
consult the Registry and find the requested CLSID_AptCruiseCar component
in the APTSERVE server on the local machine first, rather than attempting
to find the one on the remote machine.

DCOM supports other mechanisms not covered in this lesson for designating
how and where a remote component is instantiated. There are facilities for
launch security, access security, and call-level security.

The REMCLIEN client could be configured to run the server remotely at a
particular machine whenever the CLSCTX_REMOTE_SERVER flag is passed to an
activation function without a COSERVERINFO structure. This is done by
adding an appropriate \HKEY_CLASSES_ROOT\CLSID\<clsid>\RemoteServerName
key for the CLSID of the component in the registry of the client machine.
However, if a subsequent activation function is called (for example,
CoCreateInstance) that passes a COSERVERINFO structure, it will override
the RemoteServerName setting of the component's CLSID in the registry.

Once the new COM object is created, REMCLIEN can use its interfaces, as in
the previous APTCLIEN code sample.  For example, the ICar::Shift method on
the remote COM object can be called across machine boundaries. The
standard marshaling provided by the MARSHAL server takes care of
marshaling the ICar, IUtility, and ICruise interfaces across process,
thread, and machine boundaries.  But the MARSHAL server must be built and
registered on both the client machine, where REMCLIEN runs, and on the
server machine, where APTSERVE runs.

The following code from the DialogProc procedure of the Remote Server Info
dialog box (in REMCLIEN.CPP) assigns the machine name supplied by the user
through the Set Remote Machine command on the File menu.

    ...
    case WM_COMMAND:
      {
        WORD wCmd = LOWORD(wParam);

        if (wCmd == IDOK)
        {
          // Obtain the machine name from the edit control.
          GetDlgItemText(hWndDlg, IDC_EDIT_MACHINE, szMachineName, MAX_PATH);
  #ifndef UNICODE
          // Convert to WideChar if we are NOT compiled for Unicode.
          MultiByteToWideChar(
            CP_ACP,
            MB_PRECOMPOSED,
            szMachineName,
            -1,
            wszMachineName,
            sizeof(wszMachineName)/sizeof(WCHAR));
  #endif
          ::EndDialog(hWndDlg, TRUE);
        }
        else if (wCmd == IDCANCEL)
          ::EndDialog(hWndDlg, TRUE);
      }
      break;
    ...

If REMCLIEN is not compiled for Unicode, the ANSI string obtained in the
dialog box must be converted to a wide character array (WCHAR or OLECHAR)
for later use by COM. This conversion is performed by calling the Win32
function MultiByteToWideChar.

We will now tour internal behavior in both APTSERVE and REMCLIEN by
looking at some representative trace logs. Trace lines that begin with
"C:" mark behavior reported in the REMCLIEN.EXE client. Lines that begin
with "L:" mark behavior reported in the remote APTSERVE.EXE local server.
The internal APTSERVE trace lines will also show the thread ID of the
action after the "L". These trace logs were obtained from machines that
were connected in a network as described in the Summary section above.
APTCLIEN is used on the remote machine to display the APTSERVE internal
behavior.

First run APTCLIEN on remote Machine-S. Choose the Car menu's Create choice.
Here is the resulting trace log in APTCLIEN.

  C: === Car Menu: Create.
  L: APTSERVE now logging to client.
  L<3B>: CmdLine Switches= -Embedding
  L<3B>: CServer::OpenFactories. Begin.
  L: CServer::OwnThis. Thread <3B> waiting to own CServer.
  L: CServer::OwnThis. CServer now owned by Thread <3B>.
  L<3B>: CFCar::CImpIClassFactory Constructor. Non-Aggregating.
  L<3B>: CFCar Constructor. m_pUnkOuter=0x0.
  L<3B>: CFUtilityCar::CImpIClassFactory Constructor. Non-Aggregating.
  L<3B>: CFUtilityCar Constructor. m_pUnkOuter=0x0.
  L<3B>: CFCruiseCar::CImpIClassFactory Constructor. Non-Aggregating.
  L<3B>: CFCruiseCar Constructor. m_pUnkOuter=0x0.
  L<3B>: CServer::OpenFactories. AptCar.
  L<3B>: CFCar::AddRef. New cRefs=1.
  L<3B>: CFCar::AddRef. New cRefs=2.
  L<3B>: CServer::OpenFactories. AptUtilityCar.
  L<3B>: CFUtilityCar::AddRef. New cRefs=1.
  L<3B>: CFUtilityCar::AddRef. New cRefs=2.
  L: AptThreadProc. Starting Apartment Thread <65>.
  L<65>: CFCar::AddRef. New cRefs=3.
  L<3B>: CServer::OpenFactories. AptCruiseCar.
  L<3B>: CFCruiseCar::AddRef. New cRefs=1.
  L<65>: CFCar::QueryInterface. pIClassFactory returned.
  L: AptThreadProc. Starting Apartment Thread <7D>.
  L<3B>: CFCruiseCar::AddRef. New cRefs=2.
  L<65>: CFCar::CImpIClassFactory::Addref. Delegating. New cI=1.
  L<7D>: CFUtilityCar::AddRef. New cRefs=3.
  L: CServer::UnOwnThis. Ownership relinquished by <3B>.
  L<65>: CFCar::AddRef. New cRefs=4.
  L: AptThreadProc. Starting Apartment Thread <34>.
  L<3B>: CServer::OpenFactories. End.
  L<65>: CFCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x0.
  L<34>: CFCruiseCar::AddRef. New cRefs=3.
  L<65>: COCar::CImpICar Constructor. Non-Aggregating.
  L<65>: COCar Constructor. m_pUnkOuter=0x0.
  L: CServer::OwnThis. Thread <65> waiting to own CServer.
  L: CServer::OwnThis. CServer now owned by Thread <65>.
  L<65>: CServer::ObjectsUp. New cObjects=1.
  L: CServer::UnOwnThis. Ownership relinquished by <65>.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=1.
  L<65>: CFCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x771680.
  L<65>: COCar::AddRef. New cRefs=2.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=3.
  L<65>: COCar::AddRef. New cRefs=4.
  L<65>: COCar::Release. New cRefs=3.
  L<65>: COCar::Release. New cRefs=2.
  L<65>: COCar::Release. New cRefs=1.
  L<65>: CFCar::CImpIClassFactory::Release. Delegating. New cI=0.
  L<65>: CFCar::Release. New cRefs=3.

Our main goal here is to establish the integrated logging in the client
log display of both client and server behavior. We will not cover the rest
of this APTCLIEN-driven log in detail because we did so in the APTCLIEN
lesson. Our main focus here is the DCOM behavior driven by REMCLIEN.

As in the APTCLIEN lesson, when the following threads are started, each of
the server's component class factories is registered with COM on
Machine-S:

  <3B> - Main thread for the APTSERVE local server.
  <65> - AptCar component apartment thread.
  <7D> - AptUtilityCar component apartment thread.
  <34> - AptCruiseCar component apartment thread.

The main thread is also an apartment in the strict sense. It is not
exploited as such in this code sample.

As in the APTSERVE lesson, starting of the apartment threads is reported
in the same order that class factories are created, but the entries are
scattered later in the log. This is because this trace log reflects
asynchronous multi-tasking, in which internal operating system scheduling
determines when the processor resource is given to the various threads.
Though deterministic, this behavior can appear somewhat random at the
application level.

To retain symmetry with the APTCLIEN sample we'll issue the Car menu
ICar::Shift method.  Here is the APTCLIEN log.

  C: === Car Menu: ICar::Shift
  C: --Obtaining Interface Pointer.
  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=1.
  L<65>: COCar::AddRef. New cRefs=2.
  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=2.
  L<65>: COCar::AddRef. New cRefs=3.
  L<65>: COCar::CImpICar::Release. Delegating. New cI=1.
  L<65>: COCar::Release. New cRefs=2.
  C: Interface obtained. *ppv=0x14C8DC
  C: --Calling pICar->Shift
  L<65>: COCar::CImpICar::Shift. Called. ICar calls=1.
  C: --Releasing pICar

We see that the ICar interface call counter registers 1 for this created
instance of the COCar COM object.

We now start REMCLIEN on Machine-C. When it is running, click the Set
Remote Machine command on the File menu and enter the remote machine name
in the dialog box, and then click the Create command from the Car menu.

In the REMCLIEN log we see the following.

  C: === Car Menu: Create.
  C: Car creation succeeded.

In the server log (actually the APTCLIEN log) we see the following.

  L<65>: CFCar::QueryInterface. pIClassFactory returned.
  L<65>: CFCar::CImpIClassFactory::Addref. Delegating. New cI=1.
  L<65>: CFCar::AddRef. New cRefs=4.
  L<65>: CFCar::CImpIClassFactory::QueryInterface. Delegating.
  L<65>: CFCar::CImpIClassFactory::QueryInterface. Delegating.
  L<65>: CFCar::CImpIClassFactory::QueryInterface. Delegating.
  L<65>: CFCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: CFCar::AddRef. New cRefs=5.
  L<65>: CFCar::AddRef. New cRefs=6.
  L<65>: CFCar::Release. New cRefs=5.
  L<65>: CFCar::QueryInterface. pIClassFactory returned.
  L<65>: CFCar::CImpIClassFactory::Addref. Delegating. New cI=2.
  L<65>: CFCar::AddRef. New cRefs=6.
  L<65>: CFCar::QueryInterface. pIClassFactory returned.
  L<65>: CFCar::CImpIClassFactory::Addref. Delegating. New cI=3.
  L<65>: CFCar::AddRef. New cRefs=7.
  L<65>: CFCar::CImpIClassFactory::Release. Delegating. New cI=2.
  L<65>: CFCar::Release. New cRefs=6.
  L<65>: CFCar::CImpIClassFactory::Release. Delegating. New cI=1.
  L<65>: CFCar::Release. New cRefs=5.
  L<65>: CFCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x0.
  L<65>: COCar::CImpICar Constructor. Non-Aggregating.
  L<65>: COCar Constructor. m_pUnkOuter=0x0.
  L: CServer::OwnThis. Thread <65> waiting to own CServer.
  L: CServer::OwnThis. CServer now owned by Thread <65>.
  L<65>: CServer::ObjectsUp. New cObjects=2.
  L: CServer::UnOwnThis. Ownership relinquished by <65>.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=1.
  L<65>: CFCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x7716B0.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=2.
  L<65>: COCar::AddRef. New cRefs=3.
  L<65>: COCar::Release. New cRefs=2.
  L<65>: COCar::Release. New cRefs=1.
  L<65>: CFCar::CImpIClassFactory::Release. Delegating. New cI=0.
  L<65>: CFCar::Release. New cRefs=4.
  L<65>: CFCar::Release. New cRefs=3.

These actions create a new instance of COCar as indicated by the server's
cObjects count showing a value of 2.  The first object was the COCar
created by APTCLIEN above. Here is a call to ICar::Shift (still in
REMCLIEN).

In the REMCLIEN log:

  C: === Car Menu: ICar::Shift
  C: --Obtaining Interface Pointer.
  C: Interface obtained. *ppv=0x14D81C
  C: --Calling pICar->Shift
  C: --Releasing pICar

In the APTCLIEN log:

  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=1.
  L<65>: COCar::AddRef. New cRefs=2.
  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=2.
  L<65>: COCar::AddRef. New cRefs=3.
  L<65>: COCar::CImpICar::Release. Delegating. New cI=1.
  L<65>: COCar::Release. New cRefs=2.
  L<65>: COCar::CImpICar::Shift. Called. ICar calls=1.

Now, from the REMCLIEN CruiseCar menu, click Create.

In the REMCLIEN log:

  C: === CruiseCar Menu: Create.
  C: CruiseCar creation succeeded.

In the APTCLIEN log:

  L<34>: CFCruiseCar::QueryInterface. pIClassFactory returned.
  L<34>: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=1.
  L<34>: CFCruiseCar::AddRef. New cRefs=4.
  L<34>: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating.
  L<34>: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating.
  L<34>: CFCruiseCar::CImpIClassFactory::QueryInterface. Delegating.
  L<34>: CFCruiseCar::QueryInterface. 'this' pIUnknown returned.
  L<34>: CFCruiseCar::AddRef. New cRefs=5.
  L<34>: CFCruiseCar::AddRef. New cRefs=6.
  L<34>: CFCruiseCar::Release. New cRefs=5.
  L<34>: CFCruiseCar::QueryInterface. pIClassFactory returned.
  L<34>: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=2.
  L<34>: CFCruiseCar::AddRef. New cRefs=6.
  L<34>: CFCruiseCar::QueryInterface. pIClassFactory returned.
  L<34>: CFCruiseCar::CImpIClassFactory::Addref. Delegating. New cI=3.
  L<34>: CFCruiseCar::AddRef. New cRefs=7.
  L<34>: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=2.
  L<34>: CFCruiseCar::Release. New cRefs=6.
  L<34>: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=1.
  L<34>: CFCruiseCar::Release. New cRefs=5.
  L<34>: CFCruiseCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x0.
  L<34>: COCruiseCar::CImpICar Constructor. Non-Aggregating
  L<34>: COCruiseCar::CImpICruise Constructor. Non-Aggregating.
  L<34>: COCruiseCar Constructor. m_pUnkOuter=0x0.
  L: CServer::OwnThis. Thread <34> waiting to own CServer.
  L: CServer::OwnThis. CServer now owned by Thread <34>.
  L<34>: CServer::ObjectsUp. New cObjects=3.
  L: CServer::UnOwnThis. Ownership relinquished by <34>.
  L<34>: COCruiseCar::Init.
  L<65>: CFCar::QueryInterface. pIClassFactory returned.
  L<65>: CFCar::CImpIClassFactory::Addref. Delegating. New cI=1.
  L<65>: CFCar::AddRef. New cRefs=4.
  L<65>: CFCar::CImpIClassFactory::CreateInstance. pUnkOuter=0x0.
  L<65>: COCar::CImpICar Constructor. Non-Aggregating.
  L<65>: COCar Constructor. m_pUnkOuter=0x0.
  L: CServer::OwnThis. Thread <65> waiting to own CServer.
  L: CServer::OwnThis. CServer now owned by Thread <65>.
  L<65>: CServer::ObjectsUp. New cObjects=4.
  L: CServer::UnOwnThis. Ownership relinquished by <65>.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=1.
  L<65>: CFCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x771720.
  L<65>: COCar::AddRef. New cRefs=2.
  L<65>: COCar::QueryInterface. 'this' pIUnknown returned.
  L<65>: COCar::AddRef. New cRefs=3.
  L<65>: COCar::AddRef. New cRefs=4.
  L<65>: COCar::Release. New cRefs=3.
  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=1.
  L<65>: COCar::AddRef. New cRefs=4.
  L<65>: COCar::QueryInterface. pICar returned.
  L<65>: COCar::CImpICar::Addref. Delegating. New cI=2.
  L<65>: COCar::AddRef. New cRefs=5.
  L<65>: COCar::CImpICar::Release. Delegating. New cI=1.
  L<65>: COCar::Release. New cRefs=4.
  L<65>: COCar::Release. New cRefs=3.
  L<65>: COCar::Release. New cRefs=2.
  L<65>: CFCar::CImpIClassFactory::Release. Delegating. New cI=0.
  L<65>: CFCar::Release. New cRefs=3.
  L<34>: COCruiseCar::Init (New Containment of COCar) Succeeded.
  L<34>: COCruiseCar::QueryInterface. 'this' pIUnknown returned.
  L<34>: COCruiseCar::AddRef. New cRefs=1.
  L<34>: CFCruiseCar::CImpIClassFactory::CreateInstance Succeeded. *ppv=0x7716E0.
  L<34>: COCruiseCar::QueryInterface. 'this' pIUnknown returned.
  L<34>: COCruiseCar::AddRef. New cRefs=2.
  L<34>: COCruiseCar::AddRef. New cRefs=3.
  L<34>: COCruiseCar::Release. New cRefs=2.
  L<34>: COCruiseCar::Release. New cRefs=1.
  L<34>: CFCruiseCar::CImpIClassFactory::Release. Delegating. New cI=0.
  L<34>: CFCruiseCar::Release. New cRefs=4.
  L<34>: CFCruiseCar::Release. New cRefs=3.

The server has now created two new object instances, a COCruiseCar and
COCar object, with the COCar object reused by containment to make the
COCruiseCar.  The cObjects=4 thus indicates four outstanding object
instances for the server. COCruiseCar is managed by a newly encountered
apartment thread, <34>. The three COCar objects remain managed by a single
apartment thread, <65>, even though one is held by APTCLIEN, one is held
by REMCLIEN, and one is held by APTCLIEN as a contained part of a
composite COCruiseCar.

Using the CruiseCar menu we can now call ICar::Shift.

In the REMCLIEN log:

  C: === CruiseCar Menu: ICar::Shift
  C: --Obtaining Interface Pointer.
  C: Interface obtained. *ppv=0x14D284
  C: --Calling pICar->Shift
  C: --Releasing pICar

In the APTCLIEN log:

  L<34>: COCruiseCar::QueryInterface. pICar returned
  L<34>: COCruiseCar::CImpICar::Addref. Delegating. New cI=1.
  L<34>: COCruiseCar::AddRef. New cRefs=2.
  L<34>: COCruiseCar::QueryInterface. pICar returned
  L<34>: COCruiseCar::CImpICar::Addref. Delegating. New cI=2.
  L<34>: COCruiseCar::AddRef. New cRefs=3.
  L<34>: COCruiseCar::CImpICar::Release. Delegating. New cI=1.
  L<34>: COCruiseCar::Release. New cRefs=2.
  L<34>: COCruiseCar::CImpICar::Shift. Delegating. nGear=4.
  L<65>: COCar::CImpICar::Shift. Called. ICar calls=1.

The ICar interface call counter indicates only one call, providing
additional proof that this is a newly created instance of COCar managed by
the apartment thread <65>. The call had to be delegated within the server
from COCruiseCar object's apartment thread <34> to the contained COCar
object's apartment thread <65>. This means that the interface call had to
be marshaled at least twice: First, from the main thread of REMCLIEN
through DCOM (and thus RPC) across the network to the remote server
CruiseCar apartment thread <34>, then from the CruiseCar apartment thread
<34> within the same APTSERVE server process to the Car apartment thread
<65>.

This illustrates component composition within the same process but across
thread boundaries, as well as the DCOM functioning of this composite
object remotely across machine boundaries.

The release of the CruiseCar and Car objects is essentially the same as
seen in the APTCLIEN lesson, even though these Release calls are marshaled
across machine boundaries.

To shut down the samples on both machines, first click Release on the
REMCLIEN CruiseCar menu. This shuts down the COCruiseCar (with its
contained COCar instance) held by the REMCLIEN client. Then click Release
on the APTCLIEN Car menu. This shuts down the original COCar held by the
APTCLIEN client and leaves the server with an object count of 1. Finally,
click Release on the REMCLIEN Car menu. As seen in the APTCLIEN lesson,
this causes an orderly termination of the apartment threads during
execution of the CloseFactories function in APTSERVE, after which the
APTSERVE server application exits and unloads.


NETWORK AND SETUP ISSUES
========================

The test setup used during development of REMCLIEN consisted of the
following two machines: Machine-S (server) running Windows NT 4.0
Workstation with an Ethernet network adapter installed, and Machine-C
(client) running Windows NT 4.0 Workstation with an Ethernet network
adapter installed.  The two machines were connected using shielded twisted
pair cable and a IEEE 802.3 10Base-T multiport repeater.  This is called a
peer-to-peer network, and Windows NT Workstation supports this out of the
box. The same network workgroup was created for both machines.  The same
user account was created on each machine for this workgroup.  Each of
these accounts must have Administrator privilege. After logging on to each
machine with this account, the predefined C$ share name on Machine-S was
used to copy files from Machine-C's drive C across the network to
Machine-S. See your Windows NT product documentation or the Windows NT
Resource Kit for details on using the predefined C$ and D$ shares or on
creating shared access to a drive among networked machines.

Your network setup might vary. For example, you might have the two
machines connected to a client-server network, and your machines might be
configured in the same network domain rather than in the same workgroup on
a peer-to-peer network. In this case, to capture the log displays in
APTCLIEN as used above, each machine must have a domain user account with
the same name and password. These accounts should have Administrator
privilege.

The Windows NT 4.x operating system includes a DCOM configuration utility
that you can use to configure 32-bit COM and DCOM applications. To run
this tool, click Start, click Run, and then type DCOMCNFG. Before you can
use an application with DCOM, you can use DCOM Configuration to set
application properties, such as security and location. On the computer
running the client application you can specify the location of the server
application that will be accessed or started. For the server application
you can specify the user account that will have permission to access or
start the application, and the user account that will be used to run the
application.

For DCOM operation, Windows NT 4.x must be installed on each machine. The
TCP/IP network protocol, installed and configured with IP address, subnet
mask, and default gateway IP address, is recommended but not required. For
example, if your network uses NetBEUI instead of TCP/IP, Distributed COM
will still work. If other protocols in addition to TCP/IP are installed,
then DCOM will currently use TCP/IP.

If the computers you use for this lesson are part of a Windows NT Server
domain, then it is likely that a DHCP server is available on that network
which can be used to automatically assign an IP address for your TCP/IP
protocol operation.

If the computers you use for this lesson are configured as an isolated
peer-to-peer network, you may need to manually configure the TCP/IP
protocol with an IP address if a DHCP server is not available. In this
case, you can use the Network option of the Windows NT Control Panel to
configure TCP/IP. In the Network dialog box, choose the Protocols tab and
select the TCP/IP protocol in the list box. Click the Properties button.
In the Microsoft TCP/IP Properties dialog box, click the Specify An IP
Address radio button and enter values in the IP Address, Subnet Mask, and
Default Gateway boxes. The values to specify depend on your network. Your
network administrator may be responsible for dispensing static IP
addresses for machines on the network. If you intend the two machines to
remain in an isolated peer-to-peer network, you can assign your own IP
address values.

For example you can use values similar to the following.

Machine-S                              Machine-C
  IP Address= 111.22.33.1                IP Address= 111.22.33.2
  Subnet Mask= 255.255.254.0             Subnet Mask= 255.255.254.0
  Default Gateway= 111.22.33.1           Default Gateway= 111.22.33.2

For these makeshift values, the IP address and default gateway are the
same on each machine but must differ on different machines.  The subnet
mask above will work for most situations.

If you manually specify such values and later wish to connect either
machine to an existing client-server network, you should first delete this
IP address information. In the Network option of the Windows NT Control
Panel, choose the Protocols tab and select the TCP/IP protocol in the list
box. In the Microsoft TCP/IP Properties dialog box, click the Obtain An IP
Address From a DHCP Server button. Save this change, and then restart
Windows NT.

DCOM may not work if TCP/IP is installed but the machine has no assigned
IP address. This situation may show up as error 0x80070776 (The object
exporter specified was not found) returned from a failed CoGetClassObject
or CoCreateInstance call.

The operation of DCOM uses some entries in the registry to enable DCOM.
You can use the REGEDIT or OLEVIEWER tools to manually view and edit these
entries. DCOM can be configured by default so that only machine accounts
with Administrator privilege can remotely start objects of existing COM
component classes. This restriction is set as a configurable machine-wide
default registry entry on the remote machine.  To enable such a
machine-wide default permisstion the key value is set to "Y" and has the
following location in the registry:

  \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\OLE\DefaultLaunchPermission

This permission can be overridden for individual component classes by
specifying another key, LaunchPermission, under the CLSID entry for the
COM component in the registry of the remote COM server machine. The key
can be given either a "Y" or "N" value indicating whether there is a
default permission to launch. For example, here is such an entry in the
Registry for the CLSID_AptCruiseCar used above:

\HKEY_CLASSES_ROOT\CLSID\0002DA0F-0000-0000-C000-000000000046\LaunchPermission

You can view this key in the remote machine's registry using REGEDIT. In
these code samples, this value is set by the APTSERVE server during
self-registration.  To achieve this, certain statements are used in
APTSERVE's RegisterServer method for each component being registered. Here
is a fragment of the registration code for the AptCruiseCar component (in
file APTSERVE.CPP).

  ...
  ...
  // Create entries under CLSID.
  SetRegKeyValue(
    szCLSID,
    NULL,
    TEXT("AptCruiseCar Component - APTSERVE Code Sample"));
  SetRegKeyValue(
    szCLSID,
    TEXT("ProgID"),
    TEXT("AptCruiseCar1.0"));
  SetRegKeyValue(
    szCLSID,
    TEXT("VersionIndependentProgID"),
    TEXT("AptCruiseCar"));
  SetRegKeyValue(
    szCLSID,
    TEXT("NotInsertable"),
    NULL);
  SetRegKeyValue(
    szCLSID,
    TEXT("LaunchPermission"),
    TEXT("Y"));
  SetRegKeyValue(
    szCLSID,
    TEXT("LocalServer32"),
    szModulePath);
  ...
  ...

Likewise, some matching unregistration code is used in the
UnregisterServer method. Here is the unregistration code for the
AptCruiseCar component (in file APTSERVE.CPP).

  ...
  ...
  /*-----------------------------------------------------------------------
    Delete registry entries for the AptCruiseCar Component.
  -----------------------------------------------------------------------*/
  //Create some base key strings.
  StringFromGUID2(CLSID_AptCruiseCar, szID, GUID_SIZE);
  lstrcpy(szCLSID, TEXT("CLSID\\"));
  lstrcat(szCLSID, szID);

  RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AptCruiseCar\\CurVer"));
  RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AptCruiseCar\\CLSID"));
  RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AptCruiseCar"));

  RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AptCruiseCar1.0\\CLSID"));
  RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("AptCruiseCar1.0"));

  wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("ProgID"));
  RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

  wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("VersionIndependentProgID"));
  RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

  wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("NotInsertable"));
  RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

  wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("LaunchPermission"));
  RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

  wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("LocalServer32"));
  RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

  RegDeleteKey(HKEY_CLASSES_ROOT, szCLSID);
  ...
  ...

In these tutorial code samples all components permit themselves to be
launched. However, real applications may choose to be more restrictive.
For example, the DefaultLaunchPermission might be "N" and only those COM
servers that self-register with LaunchPermission = "Y" will permit
themselves to be freely launched. Program logic within the component class
factory could also contain additional launch security for each component.

You do not need to run APTCLIEN on Machine-S to obtain a logging display
of server behavior.  Prior to running REMCLIEN on Machine-C, you can
initially run APTSERVE on Machine-S explicitly using the -Embedding
command line switch. This will run APTSERVE with a visible client area
that will contain the trace log of its internal behavior. When REMCLIEN is
subsequently run, this running instance of APTSERVE will log any internal
server behavior caused by REMCLIEN.

Of course you can also simply run REMCLIEN on Machine-C without explicitly
running APTSERVE or APTCLIEN on Machine-S. COM will simply locate and run
APTSERVE on Machine-S. This is the preferred transparent behavior under
normal application conditions.
