CONCLIEN - Client of Connectable Components


SUMMARY
=======

The CONCLIEN sample creates and uses the connectable COBall COM object
that is housed in the CONSERVE server as the DllSndBall component. The
CONCLIEN client creates one COBall object and controls it through the
IBall interface that the object exposes. CONCLIEN sets up a system timer
to use IBall to periodically move the ball and obtain updates of data
describing the COBall object's position, size, and color. It uses that
data, obtained by calling the IBall::GetBall method, to display graphical
snapshot images of the ball in the client's main window.

CONCLIEN also implements a COBallSink COM object and connects it to an
appropriate connection point in the server's COBall object. In response to
the system timer's WM_TIMER messages, which are sent periodically to the
display window, CONCLIEN uses the IBall interface to move and paint images
of the ball. COBall also sends notifications back to CONCLIEN, using the
IBallSink interface, when the ball bounces off of a side of its bounding
rectangle. CONCLIEN responds to these event notifications to produce a
different sound when the ball collides with a Top, Side, or Bottom
boundary of the display window.

For functional descriptions and a tutorial code tour of CONCLIEN, see the
Code Tour section below. See also CONSERVE.TXT in the sibling \CONSERVE
directory for more details on how CONSERVE works and exposes its services
to CONCLIEN. You must build the CONSERVE DLL before building CONCLIEN. The
makefile for CONSERVE automatically registers that server in the system
registry, so you must build CONSERVE before attempting to run CONCLIEN.
For details on the external user operation of CONCLIEN, see the Operation
section below.

For details on setting up your system to build and test the code samples
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
-----

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

The Win32 Beep function is used in this sample to produce ball bounce
sounds of various frequencies. This works under Windows NT 4.0 and later.
Under the Windows 95 operating system, the Win32 Beep function currently
uses whatever is assigned in the Control Panel's Sound dialog for the
default sound and thus ignores the frequency values passed to Beep. Under
current releases of Windows 95, the sound will not be different when the
ball collides with the top, bottom, or side of the display window.


OPERATION
=========

The CONCLIEN.EXE application provides the user interface for this lesson.
It exercises the associated, but independent, CONSERVE.DLL to demonstrate
connectable objects.

Here is a summary of operation from the standpoint of CONCLIEN.EXE as a
client of the CONSERVE.DLL COM server.

There is a minimal menu in CONCLIEN. Most of the ball moving operation is
automatic. The main application window's client area is used for visual
display of the moving ball.

Menu Selection: File/Exit
Exits CONCLIEN.

Menu Selection: Sound/Connect Ball Sound
Connects the COBallSink object's IBallSink interface to the COBall
object's BallSink connection point. Ball bounce sounds are the result.
A check mark on this menu choice indicates that the sound events are
connected.

Menu Selection: Sound/Disconnect Ball Sound
Disconnects the COBallSink object's IBallSink interface from the COBall
object's BallSink connection point. Ball bounce sounds are disabled. A
check mark on this menu choice indicates that the sound events are
disconnected.

Menu Selection: Help/Read CONCLIEN.TXT
Opens the CONCLIEN.TXT file (this file) in the Windows Notepad.

Menu Selection: Help/Read CONSERVE.TXT
Opens the CONSERVE.TXT file from the sibling \CONSERVE 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 CONCLIEN
Displays the About dialog box for this application, a standard part of
this series of code samples.


CODE TOUR
=========

Files          Description

CONCLIEN.TXT   This file.
MAKEFILE       The generic makefile for building the code sample
               application of this tutorial lesson.
CONCLIEN.H     The include file for the CONCLIEN application. Contains
               class declarations, function prototypes, and resource
               identifiers.
CONCLIEN.CPP   The main implementation file for CONCLIEN.EXE. Has WinMain
               and CMainWindow implementation, as well as the main menu
               dispatching.
CONCLIEN.RC    The application resource definition file.
CONCLIEN.ICO   The application icon resource.
SINK.H         The class declaration for the COBallSink class.
SINK.CPP       Implementation file for the COBallSink class.
GUIBALL.H      The class declaration for the CGuiBall C++ class.
GUIBALL.CPP    Implementation file for the CGuiBall C++ class.


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

The program logic governing the ball's motion is encapsulated in the
COBall object. See the CONSERVE sample and CONSERVE.TXT for details.

The major topics covered in this code tour are:

  An overview of how CONCLIEN works as a client of connectable objects
  CGuiBall's support for connectable objects
  The COBallSink object
  The IBallSink interface
  Use of the system timer
  Shutdown of the client and server

When you first start CONCLIEN, the moving ball starts automatically.
CONCLIEN issues various sounds when the ball bounces against a boundary of
the display window. The sounds can be disabled using the Sound menu's
Disconnect Ball Sound command. The sounds can be enabled using the Sound
menu's Connect Ball Sound command. These commands connect or disconnect a
sink object in CONCLIEN to or from an associated connection point in the
server's COBall object. The COBallSink object in CONCLIEN implements the
custom IBallSink interface designed specifically for ball bounce events.
To connect the sound events, a pointer to this interface is given to a
connection point object in CONSERVE's COBall object. The COBall object
determines when the bounce events occur. When such an event occurs, COBall
calls the proper IBallSink method to notify the client of the bounce
event. The IBall interface is regarded as an incoming interface of the
COBall object; the IBallSink interface can be regarded as an outgoing
interface of the COBall object.

As in the FRECLIEN sample, CONCLIEN uses a CGuiBall C++ class to
encapsulate the data and behavior of the client's graphical user interface
(GUI) moving ball image. This class was presented in detail in the
FRECLIEN sample. We'll focus below on the sink and connection related code
that must be added to CGuiBall to use the COBall connectable object in
CONSERVE.

Here is the declaration of the CONCLIEN's CGuiBall class from GUIBALL.H.

  class CGuiBall
  {
    public:
      CGuiBall(void);
      ~CGuiBall(void);
      BOOL Init(HWND hWnd);
      void PaintBall(void);
      void Restart(void);
      void PaintWin(void);
      void BounceBottom(void);
      void BounceSide(void);
      void BounceTop(void);
      HRESULT ConnectBallSink(void);
      HRESULT DisconnectBallSink(void);

    private:
      HWND       m_hWnd;
      IBall*     m_pIBall;
      COLORREF   m_crColor;
      POINT      m_OldPos;
      POINT      m_OldExt;
      IUnknown*  m_pCOBallSink;
      DWORD      m_dwBallSink;
      DWORD      m_dwBounceSndDur;
      DWORD      m_dwBounceBotFreq;
      DWORD      m_dwBounceSideFreq;
      DWORD      m_dwBounceTopFreq;

      IConnectionPoint* GetConnectionPoint(REFIID riid);
  };

This C++ object encapsulates all the GUI ball behavior in the client.
Three methods are provided to handle bounce events: BounceBottom,
BounceSide, and BounceTop. A private method, GetConnectionPoint, is used
internally to obtain the COBall object's connection point interface that
corresponds to a specified sink interface IID. ConnectBallSink and
DisconnectBallSink methods are also provided to connect and disconnect the
client sink to or from its matching connection point in the server's
COBall object.

Here is ConnectBallSink from GUIBALL.CPP.

  HRESULT CGuiBall::ConnectBallSink(void)
  {
    HRESULT hr = E_FAIL;
    DWORD dwKey;
    IConnectionPoint* pConnPoint;

    if (!m_dwBallSink)
    {
      // Get the Ball Sink connection point.
      pConnPoint = GetConnectionPoint(IID_IBallSink);
      if (NULL != pConnPoint)
      {
        // Connect the object in the server to the Ball Sink in this client.
        hr = pConnPoint->Advise(m_pCOBallSink, &dwKey);
        if (SUCCEEDED(hr))
          m_dwBallSink = dwKey;

        // Release the connection point. We're done with it.
        RELEASE_INTERFACE(pConnPoint);
      }
    }

    return hr;
  }

The connection point is obtained and passed in an Advise call to
establish the connection. The Advise method is presented in the CONSERVE
code sample. The pointer to the connection point is obtained temporarily
and is released above after the Advise method returns.

After CGuiBall is first created in CONCLIEN, ConnectBallSink is called to
connect CONCLIEN's sink to enable ball sounds. See
CMainWindow::InitInstance in CONCLIEN.CPP.

The main process thread uses CGuiBall by calling the public methods Init,
Restart, PaintBall, and PaintWin. The server's COBall object is accessed
through an IBall interface pointer, m_pIBall, privately held in CGuiBall.
Thus client calls through the IBall interface on CONSERVE's COBall object
are encapsulated within the methods of CGuiBall.

CGuiBall has an Init method, in which an instance of CONSERVE's COBall
object is created. Here is the CGuiBall::Init method definition from
GUIBALL.CPP.

  BOOL CGuiBall::Init(
         HWND hWnd)
  {
    BOOL bOk = FALSE;
    HRESULT hr;
    COBallSink* pCob = NULL;

    if (hWnd)
    {
      m_hWnd = hWnd;

      // Call OLE service to create a COBall instance. We are not
      // aggregating it so we ask for its IBall interface directly.
      hr = CoCreateInstance(
             CLSID_DllSndBall,
             NULL,
             CLSCTX_INPROC_SERVER,
             IID_IBall,
             (PPVOID)&m_pIBall);
      if (SUCCEEDED(hr))
      {
        // Create the COBallSink Sink object to receive COBall events.
        pCob = new COBallSink(NULL, this);
        if (NULL != pCob)
        {
          // Save a pointer to the COBall IUnknown interface. AddRef
          // because of this saved copy.
          m_pCOBallSink = pCob;
          m_pCOBallSink->AddRef();
        }
        else
          hr = E_OUTOFMEMORY;

        if (SUCCEEDED(hr))
        {
          // Set up the client process to periodically move & paint the ball
          // thru WM_TIMER messages to the specified hWnd's Window proc.
          SetTimer(hWnd, 1, BALL_PAINT_DELAY, NULL);
          bOk = TRUE;
        }
      }
    }

    return (bOk);
  }

The COM function CoCreateInstance is called to create an instance of the
CONSERVE server's COBall object specified by its CLSID, CLSID_DllSndBall.
This CLSID is defined in BALLGUID.H, located in the sibling \INC
directory. Because aggregation is not required, CoCreateInstance directly
requests the IBall interface, which on return is stored in CGuiBall's
m_pIBall member. Once the server has created a COBall object, a COBallSink
object is created here in the client. A pointer to this sink object's
IUnknown is assigned to CGuiBall's m_pCOBallSink member. CGuiBall's Init
method also starts a system timer that will periodically send WM_TIMER
messages to the display window's window procedure. In this case the
procedure is CMainWindow::WindowProc.

The COBallSink COM object is created locally in the client. This object
implements a custom sink event interface, IBallSink. The COBall object
calls the methods of IBallSink to notify CONCLIEN of events that occur in
COBall. Here is the declaration of IBallSink from IBALL.H in the sibling
\INC directory.

  DECLARE_INTERFACE_(IBallSink, IUnknown)
  {
    // IUnknown methods.
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    // IBallSink methods.
    STDMETHOD(BounceBottom)   (THIS) PURE;
    STDMETHOD(BounceLeft)     (THIS) PURE;
    STDMETHOD(BounceRight)    (THIS) PURE;
    STDMETHOD(BounceTop)      (THIS) PURE;
  };

This custom interface declares methods for ball bounce events. A method is
provided for each of the four boundaries that the ball can collide with in
a bounding rectangle: the bottom, left, right, or top.

The COBallSink object implements the IBallSink interface and its methods.
This COM object is created solely in the client to receive IBallSink event
calls from the COBall server object. The COBallSink object is coded in
SINK.H and SINK.CPP. Here is the COBallSink class declaration from SINK.H.

  class COBallSink : public IUnknown
  {
    public:
      // Main Object Constructor & Destructor.
      COBallSink(IUnknown* pUnkOuter, CGuiBall* pGuiBall);
      ~COBallSink(void);

      // IUnknown methods. Main object, non-delegating.
      STDMETHODIMP         QueryInterface(REFIID, PPVOID);
      STDMETHODIMP_(ULONG) AddRef(void);
      STDMETHODIMP_(ULONG) Release(void);

    private:
      // We declare nested class interface implementations here.

      class CImpIBallSink : public IBallSink
      {
        public:
          // Interface Implementation Constructor & Destructor.
          CImpIBallSink(COBallSink* pBackObj, IUnknown* pUnkOuter);
          ~CImpIBallSink(void);

          // IUnknown methods.
          STDMETHODIMP         QueryInterface(REFIID, PPVOID);
          STDMETHODIMP_(ULONG) AddRef(void);
          STDMETHODIMP_(ULONG) Release(void);

          // IBallSink methods.
          STDMETHODIMP         BounceBottom(void);
          STDMETHODIMP         BounceLeft(void);
          STDMETHODIMP         BounceRight(void);
          STDMETHODIMP         BounceTop(void);

        private:
          // Data private to this interface implementation of IBall.
          COBallSink*  m_pBackObj;     // Parent Object back pointer.
          IUnknown*    m_pUnkOuter;    // Outer unknown for Delegation.
      };

      // Make the otherwise private and nested IBall interface
      // implementation a friend to COM object instantiations of this
      // COBall COM object class.
      friend CImpIBallSink;

      // Private data of COBallSink COM objects.

      // Nested IBallSink implementation instantiation. This IBall interface
      // is instantiated inside this COBallSink object as a native interface.
      CImpIBallSink    m_ImpIBallSink;

      // Main Object reference count.
      ULONG            m_cRefs;

      // Outer unknown (aggregation delegation). Used when this COM object
      // is being aggregated.
      IUnknown*        m_pUnkOuter;

      // Pointer to the main object that can service the Sink events.
      CGuiBall*        m_pGuiBall;
  };

The IBallSink interface is coded as a nested interface within the
COBallSink COM object. A pointer to a C++ object that can handle the sink
events is maintained in the m_pGuiBall member variable. This value is
passed at creation time to the COBallSink constructor, as shown earlier in
the CGuiBall::Init method, where COBallSink's constructor was passed
CGuiBall's 'this' pointer.

IBallSink's BounceRight method implementation is representative of the
others. Here it is, from SINK.CPP.

  STDMETHODIMP COBallSink::CImpIBallSink::BounceRight(
                 void)
  {
    HRESULT hr = NOERROR;

    // Ask the GUI Ball to issue an appropriate sound indicating the ball
    // bouncing off the right boundary.
    m_pBackObj->m_pGuiBall->BounceSide();

    return hr;
  }

In this nested interface implementation, the m_pBackObj pointer is used to
refer back to COBallSink's m_pGuiBall pointer, which is used, in turn, to
call CGuiBall's BounceSide method, which issues a sound. Both the Left and
Right bounce event notifications that are sent through IBallSink calls are
dispatched in COBallSink to the same CGuiBall::BounceSide method. Here it
is from GUIBALL.CPP.

  void CGuiBall::BounceSide(void)
  {
    // Use the Win32 Beep call.
    Beep(m_dwBounceSideFreq, m_dwBounceSndDur);

    return;
  }

This implementation is also representative of CGuiBall's other
event-handling methods, BounceTop and BounceBottom. The Win32 Beep
function is called to produce a different sound when the ball collides
with the top, side, or bottom boundary.

The main process relies on the recurrent system timer's WM_TIMER messages.
This was set up within the Init method of CGuiBall shown earlier. These
periodic timer messages drive the client's display process. Each WM_TIMER
message sent to the main window procedure is honored by a call to the
COBall::PaintBall method to move the ball, obtain the ball's resultant
display data, and paint an image of the ball. Here is the main window
procedure from CONCLIEN.CPP.

  LRESULT CMainWindow::WindowProc(
            UINT uMsg,
            WPARAM wParam,
            LPARAM lParam)
  {
    LRESULT lResult = FALSE;

    switch (uMsg)
    {
      case WM_CREATE:
        break;

      case WM_SIZE:
        // Handle a resize of this window.
        // Clear window and Restart the ball from upper left.
        m_wWidth = LOWORD(lParam);
        m_wHeight = HIWORD(lParam);
        m_pGuiBall->Restart();
        // Turn off sounds if app is minimized.
        if (SIZE_MINIMIZED == wParam)
          DoMenu(IDM_SOUND_BALL_OFF, 0);
        break;

      case WM_TIMER:
        // This is our timed attempt to continuously move & paint the ball.
        // In this app PaintBall both moves and paints the ball.
        m_pGuiBall->PaintBall();
        break;

      case WM_COMMAND:
        // Dispatch and handle any Menu command messages received.
        lResult = DoMenu(wParam, lParam);
        break;

      case WM_CHAR:
        if (wParam == 0x1b)
        {
          // Exit this app if user hits ESC key.
          PostMessage(m_hWnd,WM_CLOSE,0,0);
          break;
        }
      case WM_LBUTTONUP:
      case WM_PAINT:
        // If something major happened or user clicks or hits key then
        // repaint the whole window.
        m_pGuiBall->PaintWin();
        break;

      case WM_CLOSE:
        // The user selected Close on the main window's System menu.
      case WM_QUIT:
        // If the app is quit by the File/Exit main menu then close
        // any associated help windows too.
        ::WinHelp(m_hWnd, m_szHelpFile, HELP_QUIT, 0);
      default:
        // Defer all messages NOT handled here to the Default Window Proc.
        lResult = ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
        break;
    }

    return(lResult);
  }

The CGuiBall::PaintBall method is called to paint the ball when the
WM_TIMER messages arrive. Other main client application messages of
interest are WM_SIZE, WM_PAINT, and WM_LBUTTONUP. For WM_SIZE, if the user
resizes the client application's main window, CGuiBall::Restart is called.
Restart calls COBall::Reset, which relocates the ball to the upper left
corner of the window and also resets the ball size. If WM_SIZE was sent
because the CONCLIEN application was minimized, then ball sounds are
disabled. If the user clicks the left mouse button (causing WM_LBUTTONUP)
or a general WM_PAINT condition occurs, the window is repainted, but the
ball is not reset. In response to WM_CHAR, if the user pressed the ESC
key, the application is sent the WM_CLOSE message to shut down CONCLIEN
and CONSERVE.

The operating system responds to a WM_CLOSE message by destroying the
window. It sends a WM_DESTROY message to CMainWidow::WindowProc where it
causes a delete of the CMainWindow object. The delete causes CMainWindow's
destructor to run. Here is the destructor from CONCLIEN.CPP.

  CMainWindow::~CMainWindow()
  {
    // CMainWindow is derived from CVirWindow which traps the WM_DESTROY
    // message and causes a delete of CMainWindow which in turn causes this
    // destructor to run. The WM_DESTROY results when the window is destoyed
    // after a close of the window. Prior to exiting the main message loop:

    // We delete the CGuiBall and CMsgBox objects that were made in
    // Initinstance.
    DELETE_POINTER(m_pGuiBall);
    DELETE_POINTER(m_pMsgBox);

    // We then post a WM_QUIT message to cause an exit of the main thread's
    // message loop and an exit of this instance of the application.
    PostQuitMessage(0);
  }

The destructor, in turn, deletes the application's instance of CGuiBall.
The deletion of m_pGuiBall executes the CGuiBall destructor. This
destructor shuts down the client's sink object, COBallSink, and the
server's COBall object with its connectable object support. Here is the
CGuiBall destructor from file GUIBALL.CPP.

  CGuiBall::~CGuiBall(void)
  {
    BOOL bOk = TRUE;

    // Kill the client's app timer that drives repaints. Stops the WM_TIMER
    // messages to the display window.
    KillTimer(m_hWnd, 1);

    if (m_pIBall)
    {
      // Call down to the server's COBall and tell it about the shutdown.
      // This sets COBall's m_bAlive to FALSE and thus neutralizes all
      // subsequent Move calls and thus prevents any more NotifySinks calls
      // from within the Move method.
      m_pIBall->Move(FALSE);

      // Disconnect all Sinks--currently only one: BallSink. This officially
      // stops all BallSink notifications.
      DisconnectBallSink();

      // Release the reference to the BallSink object.
      RELEASE_INTERFACE(m_pCOBallSink);

      // Release the main interface pointer copy held in CGuiBall.
      RELEASE_INTERFACE(m_pIBall);
    }
  }

The system timer that sends WM_TIMER messages is killed. COBall's
IBall::Move method is told to kill any future ball movement. This prevents
any future NotifySinks calls from within the Move method. The connection
between the BallSink and the COBall's connection point is severed by a
call to CGuiBall::DisconnectBallSink. This method obtains the interface
pointer to the associated connection point in COBall and calls the
connection point's Unadvise method to terminate the connection. Finally,
the remaining interface references to the COBallSink and the COBall are
released. These final releases cause the destructors of these objects to
run. The COBall destructor does final releases on the connection points in
COBall, which destroys those objects in the server.

CMainWindow's destructor finally posts a WM_QUIT message to the
application's main messgae loop. This causes an exit of the loop. Here is
the message loop from the WinMain function in CONCLIEN.CPP.

  ...
  while (GetMessage(&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator(pWin->GetHwnd(), hAccel, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  // We also ask COM to unload any unused COM Servers, including our
  // friend, CONSERVE.
  CoFreeUnusedLibraries();
  ...

After all this shutdown activity, a call to COM's CoFreeUnusedLibraries
function ensures that all unused COM server's that were loaded for
CONCLIEN are unloaded. The CONCLIEN application is then exited and is
itself unloaded.
