STOCLIEN - Client of structured storage server


SUMMARY
=======

The STOCLIEN sample introduces a simple drawing application. The user can
use a mouse or tablet device to do free-form drawing in the client window.
The color and width of the electronic ink can be chosen, and the drawings
can be saved in files.

The functionality is externally similar to other "scribble" C++ tutorial
samples. The difference in the STOCLIEN/STOSERVE samples is an internal
architecture based on OLE technology. A clear architectural distinction is
kept between COM client and COM server. A COPaper COM object encapsulates
only the server-based storage of the drawing paper data: No graphical user
interface (GUI) behavior is provided on the server side. All GUI behavior
is isolated in the client. The data managment and storage features of
COPaper objects are available only through an OLE custom interface,
IPaper.

STOCLIEN can load and save its drawings in the structured storage of OLE
compound files. The principal focus of the STOCLIEN sample is how the
client uses this structured storage and how it directs a server component
to use this storage. The programming of structured storage services is
shown in the sample.

The STOCLIEN sample creates and uses the connectable COPaper COM object
that is provided as the CLSID_DllPaper component in the STOSERVE server.
The STOCLIEN client creates a COPaper object and controls it through the
IPaper interface that the object exposes. STOCLIEN obtains drawing data
from the user and graphically represents it in a window that it manages.
STOCLIEN uses COPaper's IPaper interface to save the drawing data in
COPaper and to direct file storage operations on this data.

STOCLIEN cooperates with the COPaper to load and save COPaper's drawing
data. STOCLIEN obtains an IStorage interface for the storage object in a
compound file. In its load and save operations, STOCLIEN passes a pointer
to this IStorage interface to COPaper in the server. COPaper uses the
provided IStorage to create streams in the storage. COPaper can then use
the standard IStream interface for reading and writing the drawing data it
manages.

COPaper only manages the drawing data; it performs no GUI actions.
STOCLIEN provides the GUI for the drawing application. It encapsulates
this in a central CGuiPaper C++ object.

STOCLIEN also implements the custom IPaperSink interface in a COPaperSink
COM object and connects this interface to an appropriate connection point
in the server's COPaper object. COPaper uses the connected IPaperSink
interface to send notifications back to STOCLIEN. The normal GUI
repainting of COPaper's drawing data is done in STOCLIEN using COPaper's
connectable object technology.

For functional descriptions and a tutorial code tour of STOCLIEN, see the
Code Tour section below. See also STOSERVE.TXT in the sibling STOSERVE
directory for more details on how STOSERVE works and exposes its services
to STOCLIEN. You must build the STOSERVE DLL before building STOCLIEN. The
makefile for STOSERVE automatically registers that server in the system
registry, so you must build STOSERVE before attempting to run STOCLIEN.
For details on the external user operation of STOCLIEN, 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
-----

STOCLIEN is an application that you can execute directly from Windows in
the normal manner or from the Command Prompt window. STOCLIEN accepts an
optional file name parameter on the command line. For example:

  STOCLIEN c:\drawings\drawing.pap

Where drawing.pap is a compound file containing DllPaper-compatible
structured storage of drawing data. If no command line file name parameter
is specified, STOCLIEN uses the default file name STOCLIEN.PAP and
attempts to open it in the same directory as the executing STOCLIEN.EXE.


OPERATION
=========

The STOCLIEN.EXE application provides the user interface for this lesson.
It exercises the associated, but independent, STOSERVE.DLL to demonstrate
both client and server use of OLE structured storage in compound files.

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

The STOCLIEN application window's client area is used for visual display
of freeform drawing created with a mouse or tablet device. To draw with
the mouse, press and hold the left mouse button while moving the mouse.
Releasing the left mouse button ends the drawing of a line.

The following menu is provided.

Menu Selection: File/Open
Shows the Open dialog box to obtain a name and path for an existing paper
drawing file to open. A default .PAP file extension for these files is
assumed. If changes were made to the existing drawing when this menu item
is chosen, a separate dialog will first be shown asking the user if the
current drawing should be saved into its associated compound file.

Menu Selection: File/Save
Saves the current drawing into its associated compound file.

Menu Selection: File/Save As
Shows the Save As dialog box to obtain a name and path for a new paper
drawing file to create. The current drawing becomes the saved content of
the new file, and the new file becomes the new associated compound file
for the drawing.

Menu Selection: File/Exit
Exits STOCLIEN.

Menu Selection: Draw/Drawing On
Turns on drawing between the STOCLIEN client and the COPaper object in
the server. This command locks the COPaper object for exclusive use by
this client and prevents other clients from accessing the same COPaper
instance in the server.

Menu Selection: Draw/Drawing Off
Turns off drawing between the STOCLIEN client and the COPaper object in
the server. This command unlocks the COPaper for exclusive use by this
client and allows other clients to access the same COPaper instance in the
server.

Menu Selection: Draw/Redraw
Redraws in the client the current drawing data held in the COPaper object
in the server.

Menu Selection: Draw/Erase
Erases the current drawing content and clears the window image.

Menu Selection: Pen/Color
Shows the Choose Color dialog box to obtain a new pen color for drawing.

Menu Selection: Pen/Thin
Chooses the thin width for drawing. A check mark on this menu choice
indicates that thin is the current pen width.

Menu Selection: Pen/Medium
Chooses the medium width for drawing. A check mark on this menu choice
indicates that medium is the current pen width.

Menu Selection: Pen/Thick
Chooses the thick width for drawing. A check mark on this menu choice
indicates that thick is the current pen width.

Menu Selection: Sink/Connect
Connects the COPaperSink's IPaperSink interface to the COPaper object's
PaperSink connection point. Repainting of the current drawing image in
STOCLIEN relies on the sink connection. A check mark on this menu choice
indicates that repainting is connected.

Menu Selection: Sink/Disconnect
Disconnects the COPaperSink's IPaperSink interface from the COPaper
object's PaperSink connection point. Repainting of the current drawing
image in STOCLIEN relies on the sink connection. A check mark on this menu
choice indicates that repainting is disconnected.

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

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

Menu Selection: Help/Read Source File
Displays the Open dialog box so you can open a source file from this
lesson or another one in the Windows Notepad.

Menu Selection: Help/About STOCLIEN
Displays the About dialog box for this application.


CODE TOUR
=========

Files          Description

STOCLIEN.TXT   This file.
MAKEFILE       The generic makefile for building the code sample
               application of this tutorial lesson.
STOCLIEN.H     The include file for the STOCLIEN application. Contains
               class declarations, function prototypes, and resource
               identifiers.
STOCLIEN.CPP   The main implementation file for STOCLIEN.EXE. Has WinMain
               and CMainWindow implementation, as well as the main menu
               dispatching.
STOCLIEN.RC    The application resource definition file.
STOCLIEN.ICO   The application icon resource.
STOCLIEN.PAP   A default paper drawing file for the application.
PENCIL.CUR     A pencil image for the client window cursor.
SINK.H         The class declaration for the COPaperSink COM object class.
SINK.CPP       Implementation file for the COPaperSink COM object class.
PAPFILE.H      The class declaration for the CPapFile C++ class.
PAPFILE.CPP    Implementation file for the CPapFile C++ class.
GUIPAPER.H     The class declaration for the CGuiPaper C++ class.
GUIPAPER.CPP   Implementation file for the CGuiPaper C++ class.


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

STOCLIEN works in a cooperative fashion with a COPaper object in a COM
server to achieve persistent storage of drawings in OLE compound files.
See the STOSERVE sample and STOSERVE.TXT for details on COPaper's use of
streams in the compound file that is provided to COPaper by STOCLIEN.
COPaper's construction and its IPaper interface are also covered in the
STOSERVE sample.

The major topics covered in this code tour are:

  An overview of how CGuiPaper encapsulates the GUI behavior of
    STOCLIEN's electronic drawing paper
  How STOCLIEN captures and displays interactive drawing activity
  How the CGuiPaper object uses COPaper to record drawing data
  How an IPaperSink connection is used in repainting

Most importantly for this sample, this code tour explains how the CPapFile
Load and Save methods use the structured storage in OLE compound files.

As the CGuiBall class used in the FRECLIEN and CONCLIEN samples
encapsulated the behavior of a bouncing ball, STOCLIEN uses a CGuiPaper
C++ class to encapsulate the data and GUI behavior of electronic drawing
paper.

Here is the CGuiPaper class declaration from GUIPAPER.H.

  class CGuiPaper
  {
    public:
      CGuiPaper(void);
      ~CGuiPaper(void);
      BOOL Init(HINSTANCE hInst, HWND hWnd, TCHAR* pszCmdLineFile);
      HRESULT DrawOn(void);
      HRESULT DrawOff(void);
      HRESULT ClearWin(void);
      HRESULT PaintWin(void);
      HRESULT Erase(void);
      HRESULT Resize(WORD wWidth, WORD wHeight);
      HRESULT InkWidth(SHORT nInkWidth);
      HRESULT InkColor(COLORREF crInkColor);
      HRESULT InkSaving(BOOL bInkSaving);
      HRESULT InkStart(SHORT nX, SHORT nY);
      HRESULT InkDraw(SHORT nX, SHORT nY);
      HRESULT InkStop(SHORT nX, SHORT nY);
      HRESULT ConnectPaperSink(void);
      HRESULT DisconnectPaperSink(void);
      HRESULT Load(void);
      HRESULT Save(void);
      int     AskSave(void);
      HRESULT Open(void);
      HRESULT SaveAs(void);
      COLORREF PickColor(void);

    private:
      HINSTANCE  m_hInst;
      HWND       m_hWnd;
      HDC        m_hDC;
      RECT       m_WinRect;
      IPaper*    m_pIPaper;
      SHORT      m_nLockKey;
      HPEN       m_hPen;
      SHORT      m_nInkWidth;
      COLORREF   m_crInkColor;
      BOOL       m_bInkSaving;
      BOOL       m_bInking;
      BOOL       m_bPainting;
      POINT      m_OldPos;
      IUnknown*  m_pCOPaperSink;
      DWORD      m_dwPaperSink;
      BOOL       m_bDirty;
      CPapFile*    m_pPapFile;
      OPENFILENAME m_ofnFile;
      TCHAR        m_szFileFilter[MAX_PATH];
      TCHAR        m_szFileName[MAX_PATH];
      TCHAR        m_szFileTitle[MAX_PATH];
      CHOOSECOLOR  m_ChooseColor;
      COLORREF     m_acrCustColors[16];

      IConnectionPoint* GetConnectionPoint(REFIID riid);
  };

There is much here that is not relevant to the main focus of this sample
and will not be covered in detail. COM connection technology is used
significantly in the STOCLIEN/STOSERVE samples but is not covered here in
detail. See the CONCLIEN/CONSERVE samples for more on this technology.

CGuiPaper maintains the current GUI properties of the drawing paper.
Members m_crInkColor, m_crInkWidth, m_WinRect contain values for the
current ink color, ink width, and drawing rectangle. Of course, a handle
is needed for the window where painting is done. It is stored in member
m_hWnd. The actual painting of images is done using a handle to a device
context held in member m_hDC. A handle to the current drawing pen is kept
in member m_hPen. The pen is destroyed and recreated when its color or
width is changed by the user. Members m_pCOPaperSink and m_dwPaperSink
hold values necessary for connecting with COPaper to receive incoming
notifications through the IPaperSink interface. Member m_bDirty holds a
flag indicating that the user has changed the drawing and that it no
longer reflects the data stored in its file.

Member m_pIPaper holds the main interface pointer to the COPaper object
instantiated in the server. It is through this pointer that all of the
COPaper functionality is accessed.

The m_nLockKey member is used to support a client locking scheme that is
used with multiple clients to allow a client exclusive access to a shared
COPaper object. The m_nLockKey is assigned by COPaper during a IPaper Lock
call and is passed as a parameter by the client in subsequent calls to
COPaper. COPaper will perform the work in those calls only if the lock key
that is passed matches the key last handed out to a client by COPaper.
This locking scheme and the multiclient scenarios it supports are not
covered in this tour.

Member m_pPapFile holds a pointer to a CPapFile object. This object will
be examined in detail below. It is a C++ object that encapsulates load and
save operations on an OLE structured storage compound file.  CPapFile
works with the underlying server-based COPaper object to load and save
COPaper's drawing data.

CGuiPaper's methods are summarized as follows.

  BOOL Init(HINSTANCE hInst, HWND hWnd, TCHAR* pszCmdLineFile);
    Initializes the GuiPaper. Asks server to create a COPaper object.

  HRESULT DrawOn(void);
    Locks paper for drawing exclusively by this client.

  HRESULT DrawOff(void);
    Unlocks paper to allow other clients to draw.

  HRESULT ClearWin(void);
    Clears display window but retains ink data.

  HRESULT PaintWin(void);
    Clears window and repaints with current ink data.

  HRESULT Erase(void);
    Erases current drawing content and clears display window.

  HRESULT Resize(WORD wWidth, WORD wHeight);
    Resizes the display window.

  HRESULT InkWidth(SHORT nInkWidth);
    Sets current ink width for drawing.

  HRESULT InkColor(COLORREF crInkColor);
    Sets current ink color for drawing.

  HRESULT InkSaving(BOOL bInkSaving);
    Turns ink data saving in COPaper on and off.

  HRESULT InkStart(SHORT nX, SHORT nY);
    Starts ink drawing sequence.

  HRESULT InkDraw(SHORT nX, SHORT nY);
    Draws ink sequence data.

  HRESULT InkStop(SHORT nX, SHORT nY);
    Stops ink drawing sequence.

  HRESULT ConnectPaperSink(void);
    Connects the client PaperSink object to the server COPaper source.

  HRESULT DisconnectPaperSink(void);
    Disconnect the client PaperSink object from the server COPaper source.

  HRESULT Load(void);
    Loads ink data from current compound file.

  HRESULT Save(void);
    Saves existing ink data to current compound file.

  HRESULT AskSave(void);
    Checks if drawing changed. If so, displays dialog box asking
    user whether to save changes and responds appropriately.

  HRESULT Open(void);
    Shows Win32 common dialog box. Opens existing paper data compound file.

  HRESULT SaveAs(void);
    Shows Win32 common dialog box. Saves current paper data in renamed file.

  COLORREF PickColor(void);
    Shows Win32 ommon dialog box. Asks user to choose new pen color.

These methods are all implemented in GUIPAPER.CPP.

The Init method creates the server-based COPaper object and assigns
CGuiPaper's m_pIPaper member.

The AskSave, Open, SaveAs, and PickColor methods provide familiar GUI
behavior using Win32 common dialogs. For example, the Open method uses the
Win32 Open File Name dialog box to ask the user to specify a file name for
opening.

The Load and Save methods will be covered in detail later in this tour.

InkSaving, InkStart, InkDraw, and InkStop are the central methods for the
drawing functionality of the STOCLIEN application. STOCLIEN uses these
CGuiPaper methods to capture, display, and store the interactive drawing
data as it occurs under user control. They perform a dual role of painting
the drawn image to the client window as well as passing the drawing data
to COPaper in the server. COPaper translates the drawing data into ink
data packets for storage.

The user presses the left mouse button (or the pen tip switch in tablet
devices) to initiate a line drawing sequence. The user holds down the
button and moves the mouse to draw a line. The sequence is ended when the
left mouse button is released. The Windows operating system translates
these actions into standard window messages and sends them to STOCLIEN's
main window procedure. Here is CMainWindow::WindowProc from STOCLIEN.CPP.

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

    switch (uMsg)
    {
      case WM_CREATE:
        break;

      case WM_ACTIVATE:
        // If we were newly activated by a mouse click then don't just sit
        // there--re-paint. This is needed when another window was topped
        // over part of STOCLIEN and the user then topped STOCLIEN using
        // a mouse click on the visible part of STOCLIEN. In any case let
        // the windows default WindowProc handle this message as well.
        if (WA_CLICKACTIVE == LOWORD(wParam))
          m_pGuiPaper->PaintWin();
        lResult = ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
        break;

      case WM_SIZE:
        // Handle a resize of this window.
        m_wWidth = LOWORD(lParam);
        m_wHeight = HIWORD(lParam);
        // Inform CGuiPaper of the change.
        m_pGuiPaper->Resize(m_wWidth, m_wHeight);
        break;

      case WM_PAINT:
        // If something major happened repaint the whole window.
        {
          PAINTSTRUCT ps;

          if(BeginPaint(m_hWnd, &ps))
            EndPaint(m_hWnd, &ps);

          m_pGuiPaper->PaintWin();
        }
        break;

      case WM_LBUTTONDOWN:
        // Start sequence of ink drawing to the paper.
        m_pGuiPaper->InkStart(LOWORD(lParam), HIWORD(lParam));
        break;

      case WM_MOUSEMOVE:
        // Draw inking sequence data.
        m_pGuiPaper->InkDraw(LOWORD(lParam), HIWORD(lParam));
        break;

      case WM_LBUTTONUP:
        // Stop an ink drawing sequence.
        m_pGuiPaper->InkStop(LOWORD(lParam), HIWORD(lParam));
        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_CLOSE:
        // The user selected Close on the main window's System menu
        // or Exit on the File menu.
        // If there is ink data that has not been saved then ask user
        // if it should be saved. If user cancels then cancel the exit.
        if (IDCANCEL == m_pGuiPaper->AskSave())
          break;
      case WM_QUIT:
        // If the app is being quit then close any associated help windows.
        // ::WinHelp(m_hWnd, m_szHelpFile, HELP_QUIT, 0);
      default:
        // Defer all messages NOT handled above to the Default Window Proc.
        lResult = ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
        break;
    }

    return(lResult);
  }

A line drawing sequence is started when the WM_LBUTTONDOWN message is
received with mouse position data. CMainWindow has a pointer to the
CGuiPaper object and calls the CGuiPaper::InkStart method to start the
line drawing sequence. As the mouse is moved to draw, a sequence of
separate WM_MOUSEMOVE messages is received with mouse position data.
CGuiPaper's InkDraw method is called with this data. When the left mouse
button is released, the WM_LBUTTONUP message is received. CGuiPaper's
InkStop method is then called to stop the line drawing sequence.

Here is the InkStart method from GUIPAPER.CPP.

  HRESULT CGuiPaper::InkStart(
                       SHORT nX,
                       SHORT nY)
  {
    HRESULT hr = E_FAIL;

    if (m_nLockKey || (!m_nLockKey && !m_bInkSaving))
    {
      // Start an ink drawing sequence only if one is not in progress.
      if (!m_bInking)
      {
        // Remember start position.
        m_OldPos.x = nX;
        m_OldPos.y = nY;

        // Delete old pen.
        if (m_hPen)
          DeleteObject(m_hPen);

        // Create and select the new drawing pen.
        m_hPen = CreatePen(PS_SOLID, m_nInkWidth, m_crInkColor);
        SelectObject(m_hDC, m_hPen);

        hr = NOERROR;

        // Ask the Paper object to mark the start of the ink drawing
        // sequence in the current ink color.
        if (m_pIPaper && m_bInkSaving)
        {
          hr = m_pIPaper->InkStart(
                            m_nLockKey,
                            nX,
                            nY,
                            m_nInkWidth,
                            m_crInkColor);
          // Capture the Mouse.
          SetCapture(m_hWnd);

          // We've modified the ink data--it is now "dirty" with
          // respect to the compound file image. Set dirty flag.
          m_bDirty = TRUE;
        }

        // Set inking flag to TRUE.
        m_bInking = TRUE;
      }
    }

    return hr;
  }

InkStart, InkDraw, and InkStop all use Win32 GUI constructs such as device
contexts and pen objects. This illustrates why CGuiPaper is needed as a
separate level of encapsulation. The GUI aspects of the drawing paper are
handled in CGuiPaper. The COPaper object is sent only ink data.

Another design reason for the CGuiPaper level of encapsulation is the dual
nature of its InkStart, InkDraw, and InkStop methods. They not only call
on COPaper to record the ink data as it occurs, they also paint a visual
image of the drawing as it occurs. CGuiPaper keeps an m_bInkSaving flag to
manage this dual nature. When m_bInkSaving is FALSE, the image is drawn on
screen but the data is not sent to COPaper for recording. This scheme is
used in repainting when the user is not moving the mouse but COPaper's ink
data is being resent to CGuiPaper for automatic repainting. CGuiPaper can
be told to set the m_bInkSaving flag by calling its InkSaving method.

CGuiPaper also keeps an m_bInking flag. InkStart sets it to TRUE to signal
that a drawing sequence is in process. For example, the InkDraw method
uses this flag to determine whether it should paint and save ink data.
Here is the InkDraw method from GUIPAPER.CPP.

  HRESULT CGuiPaper::InkDraw(
                       SHORT nX,
                       SHORT nY)
  {
    if (m_bInking)
    {
      // Start this ink line at previous old position.
      MoveToEx(m_hDC, m_OldPos.x, m_OldPos.y, NULL);

      // Assign new old position and draw the new line.
      LineTo(m_hDC, m_OldPos.x = nX, m_OldPos.y = nY);

      // Ask the Paper object to save this data.
      if (m_bInkSaving)
        m_pIPaper->InkDraw(m_nLockKey, nX, nY);
    }

    return NOERROR;
  }

This method does nothing if m_bInking is FALSE. This is the condition when
the user is simply moving the mouse over the client window without
pressing the left mouse button.

InkDraw clearly has a dual responsibility. The Win32 MoveToEx and LineTo
calls are made to draw line images on the GUI screen (using the device
context handle kept in m_hDC). The ink data is also passed to the COPaper
object for recording using the IPaper interface's InkDraw method. When
m_bInkSaving is FALSE, InkDraw paints the line image but does not store
the data in COPaper. This condition is used during repainting.

Here is CGuiPaper's PaintWin method from GUIPAPER.CPP.

  HRESULT CGuiPaper::PaintWin(void)
  {
    HRESULT hr = E_FAIL;
    COLORREF crInkColor;
    SHORT nInkWidth;

    if (m_pIPaper && !m_bPainting && !m_bInking)
    {
      m_bPainting = TRUE;
      // Save and restore ink color and width since redraw otherwise
      // ends up changing these values in CGuiPaper.
      crInkColor = m_crInkColor;
      nInkWidth = m_nInkWidth;
      hr = m_pIPaper->Redraw(m_nLockKey);
      m_nInkWidth = nInkWidth;
      m_crInkColor = crInkColor;
      m_bPainting = FALSE;
    }

    return hr;
  }

PaintWin essentially calls COPaper's Redraw method. In the STOSERVE
sample, the Redraw method was shown to broadcast COPaper's entire ink data
array to all connected sinks. PaintWin calls an object on the server side
to send back the drawing data to the client. STOCLIEN receives this data
in the form of calls to the connected IPaperSink interface in its
COPaperSink object. These methods correspond to the similar InkStart,
InkDraw, and InkStop methods in CGuiPaper. For example, here is
IPaperSink's InkStart method from SINK.CPP.

  STDMETHODIMP COPaperSink::CImpIPaperSink::InkStart(
                                              SHORT nX,
                                              SHORT nY,
                                              SHORT nWidth,
                                              COLORREF crInkColor)
  {
    // Turn off ink saving to the COPaper object.
    m_pBackObj->m_pGuiPaper->InkSaving(FALSE);

    // Play the data back to the CGuiPaper for display only.
    m_pBackObj->m_pGuiPaper->InkWidth(nWidth);
    m_pBackObj->m_pGuiPaper->InkColor(crInkColor);
    m_pBackObj->m_pGuiPaper->InkStart(nX, nY);

    return NOERROR;
  }

When InkStart is called in the sink, it calls CGuiPaper to turn off ink
saving to COPaper. COPaper does not need to receive an echoed copy of the
data it is sending. When InkDraw is called in the sink, it simply passes
the call on to CGuiPaper::InkDraw. When InkStop is called in the sink,
CGuiPaper is called to turn ink saving back on. The result is a playback
of COPaper's ink data to CGuiPaper for display only.

You can test the behavior of STOCLIEN when its IPaperSink is disconnected
by choosing the Disconnect choice on the Sink menu. As an experiment,
after disconnecting the sink, choose About from the Help menu. This will
show the About dialog box, which will cover part of the STOCLIEN's
drawing. Click OK in the About dialog box.  Notice that the portion of the
drawing that was covered is now gone. The drawing data is not lost, but
part of the image is. The client received the WM_PAINT message and issued
the IPaper Redraw method. But because the sink was not connected, it did
not receive the IPaperSink calls to repaint the drawing.

You can also test this behavior by minimizing STOCLIEN and then restoring
it. In this case, the entire drawing image is lost and needs repainting.
To repaint the drawing image after either of these tests, use the Sink
menu to reconnect, and then choose Redraw from the Draw menu.

STOCLIEN relies on COPaper to record drawing data. It also relies on
COPaper to store the data in a compound file. However, in a typical
division of labor between OLE client and server, STOCLIEN shares part of
the responsibility for file storage. This division of labor is important
in OLE applications where the client is a container and the server is an
embedded object. In this arrangement, the client is responsible for
creating or opening a structured storage file, while the server object is
responsible for using that storage for its own data storage purposes. This
may involve the server object creating substorages in the storage that is
given to it. It usually involves the server object creating stream objects
in the storage. COPaper's use of storage streams is detailed in the
STOSERVE sample.

The IStorage interface is used by both client and server object to perform
file operations. The OLE compound files implementation of the Structured
Storage architecture is used. OLE standard service functions are used for
operations on compound files. For example, the StgCreateDocFile function
initially creates a compound file and passes back an IStorage pointer that
can be used to manipulate the file. This particular function is called in
STOCLIEN. The IStorage interface it obtains is passed as a parameter to
COPaper for its use. The COPaper object does not create or open compound
files on its own: It uses the IStorage and IStream interfaces to work in
compound files that are given to it.

These IStorage and IStream interfaces are not implemented within STOCLIEN
or STOSERVE: They are implemented within the OLE libraries. When a pointer
to one of these interfaces is obtained, their methods are essentially used
as a set of services to operate on a compound file.

STOCLIEN encapsulates its compound file operations in a CPapFile C++
object. Here is the CPapFile class declaration from PAPFILE.H.

  class CPapFile
  {
    public:
      CPapFile(void);
      ~CPapFile(void);
      HRESULT Init(TCHAR* pszFileName, IPaper* pIPaper);
      HRESULT Load(SHORT nLockKey, TCHAR* pszFileName);
      HRESULT Save(SHORT nLockKey, TCHAR* pszFileName);

    private:
      TCHAR          m_szCurFileName[MAX_PATH];
      IPaper*        m_pIPaper;
      IStorage*      m_pIStorage;
  };

The CPapFile object keeps a current file name in member m_szCurFileName.
This file name is used as a default in the Load and Save methods when they
do not explicitly receive a file name.

Member m_pIPaper keeps an interface pointer to the COPaper's IPaper
interface.

Member m_pIStorage keeps a pointer to the IStorage interface for
the current compound file that STOCLIEN is using for structured storage.

Here is a summary of CPapFile's methods.

  HRESULT Init(TCHAR* pszFileName, IPaper* pIPaper);
    Initializes CPapFile.

  HRESULT Load(SHORT nLockKey, TCHAR* pszFileName);
    Loads default paper data file or a specified paper data file.

  HRESULT Save(SHORT nLockKey, TCHAR* pszFileName);
    Saves drawing data to the current paper data file or to a specified
    paper data file.

Here is CPapFile's Init method from PAPFILE.CPP.

  HRESULT CPapFile::Init(
                      TCHAR* pszAppFileName,
                      IPaper* pIPaper)
  {
    HRESULT hr = E_FAIL;

    if (NULL != pIPaper)
    {
      // Keep CPapFile copy of pIPaper. Thus AddRef it.
      // Released in Destructor.
      m_pIPaper = pIPaper;
      m_pIPaper->AddRef();

      if (NULL != pszAppFileName)
      {
        // Set the default current file name.
        lstrcpy(m_szCurFileName, pszAppFileName);

        hr = NOERROR;
      }
    }

    return (hr);
  }

The pszAppFileName parameter is used to initialize CPapFile with a default
file name for any subsequent load or save operations. A copy is kept in
member m_szCurFileName for the first load. In STOCLIEN, such a load is
attempted when the application first starts after CPapFile has been
initialized with pszAppFileName assigned "STOCLIEN.PAP". This file name
was constructed in CGuiPaper's constructor by obtaining the current
executing module's name. (The Win32 GetModuleFileName function is called).

The pIPaper parameter is needed by CPapFile because it uses this interface
on the COPaper object to perform its load and save operations. AddRef is
called on this stored copy of pIPaper, according to COM rules. The matching
Release is called in the CPapFile destructor.

Once CPapFile is initialized, it can be used to save the current drawing
data that is held in the COPaper object. Here is CPapFile's Save method
from PAPFILE.CPP.

  HRESULT CPapFile::Save(
                      SHORT nLockKey,
                      TCHAR* pszFileName)
  {
    HRESULT hr = E_FAIL;
    BOOL bNewFile = (NULL != pszFileName);
    TCHAR* pszFile;

    // If NULL file name passed then use current file name.
    pszFile = bNewFile ? pszFileName : m_szCurFileName;

    // Use OLE service to re-open (or newly create) the compound file.
    hr = StgCreateDocfile(
           pszFile,
           STGM_CREATE | STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
           0,
           &m_pIStorage);
    if (SUCCEEDED(hr))
    {
      // We've got the IStorage and the compound file is open.
      // Now tell the COPaper object on the server side to save its
      // paper data into the compound file using the IStorage of our
      // client-provided compound file.
      hr = m_pIPaper->Save(nLockKey, m_pIStorage);
      if (SUCCEEDED(hr))
      {
        // The paper data was saved and we have a new current compound
        // file name. Copy it for later use in a saves and loads.
        if (bNewFile)
          lstrcpy(m_szCurFileName, pszFileName);
      }

      // We are done with the Storage for now. We don't hold the file
      // open. We re-obtain the IStorage again later (and thus re-open
      // the compound file) when we need it for another save or load.
      RELEASE_INTERFACE(m_pIStorage);
    }

    return (hr);
  }

If NULL is passed for the pszFileName parameter, the stored content of
member m_szCurFileName is used for the file name. The nLockKey is the
client's lock key, which was obtained in a previous call to COPaper's Lock
method in the IPaper interface.

The OLE standard StgCreateDocFile service function is called to create the
compound file in which the drawing data will be saved.

The name of the compound file to create is passed as the first parameter.
This is expected by StgCreateDocFile as a Unicode string. When STOCLIEN is
compiled for ANSI strings (UNICODE is not defined, which is the default in
the makefile), this call actually reduces to a call to an internal APPUTIL
function, A_StgCreateDocFile. This function performs the proper conversion
of the ANSI pszFile parameter to a Unicode version before calling
StgCreateDocFile. See APPUTIL.H and STOSERVE.TXT for more details on how
this works.

The access mode flags are passed as the second parameter and determine
what access modes are permitted on the new file. The STGM_CREATE access
mode flag creates a new compound file or overwrites an existing one of the
same name. STGM_READWRITE opens the file with read-write permission.
STGM_DIRECT opens the file for direct access, as opposed to transacted
access. STGM_SHARE_EXCLUSIVE opens the file for exclusive, non-shared use
by the caller.

The third parameter is reserved and must be 0.

The address of pointer variable m_pIStorage is passed to StgCreateDocFile
as the fourth parameter. When the call successfully returns, m_pIStorage
contains an interface pointer to an IStorage interface. This is the
interface that is used for any subsequent operations on the file.

The important operation in this case is to have the COPaper object store
its drawing data in the file. This is done above using the Save method of
COPaper's IPaper interface. If COPaper succeeds in saving its data in the
storage object provided, the name of the compound file is copied into
m_szCurFileName as the new current file name.

When these operations are successfully completed, the IStorage interface
that was obtained is released. This closes the file and means that the
file is not held open during other client operations. It will be reopened
when needed.

One such case is CPapFile's Load method. Here it is from PAPFILE.CPP.

  HRESULT CPapFile::Load(
                      SHORT nLockKey,
                      TCHAR* pszFileName)
  {
    HRESULT hr = E_FAIL;
    BOOL bNewFile = (NULL != pszFileName);
    TCHAR* pszFile;

    // If NULL file name passed then use current file name.
    pszFile = bNewFile ? pszFileName : m_szCurFileName;

    // First check if the file is out there using APPUTIL function.
    if (FileExist(pszFile))
    {
      // Use OLE service to next check if the file is actually a valid
      // compound file.
      hr = StgIsStorageFile(pszFile);
      if (SUCCEEDED(hr))
      {
        // We're go. Use OLE service to open the compound file and
        // obtain a IStorage interface.
        hr = StgOpenStorage(
               pszFile,
               NULL,
               STGM_DIRECT | STGM_READ | STGM_SHARE_EXCLUSIVE,
               NULL,
               0,
               &m_pIStorage);
        if (SUCCEEDED(hr))
        {
          // We have an IStorage. Now ask the COPaper object on the server
          // side to load into itself the file's paper data using the
          // IStorage for our client-provided compound file.
          hr = m_pIPaper->Load(nLockKey, m_pIStorage);
          if (SUCCEEDED(hr))
          {
            // The paper data was loaded and we have a current compound
            // file name. Copy it for later use in a saves and loads.
            if (bNewFile)
              lstrcpy(m_szCurFileName, pszFileName);
          }

          // We are done with the Storage for now. We don't hold the file
          // open. We re-obtain the IStorage again later (and thus re-open
          // the compound file) when we need it for another save or load.
          RELEASE_INTERFACE(m_pIStorage);
        }
      }
    }

    return (hr);
  }

As with the Save method, if NULL is passed for the pszFileName parameter,
the stored content of member m_szCurFileName is used for the file name.
Since an existing file may be opened, APPUTIL's FileExist function is
first used to check if the file exists.  If it exists, the OLE standard
StgIsStorageFile service function is called to check the format of the
file to determine if it is a valid compound file.

If the file is valid, the OLE standard StgOpenStorage service function is
called to open the file and return a pointer to an IStorage interface for
it.

The first parameter is the name of the compound file to open. As with the
StgCreateDocFile call, this string is expected in the Unicode form, and
during ANSI compilation an APPUTIL macro is used to ensure the ANSI
parameter is converted to the expected Unicode.

The second priority storage parameter is NULL, indicating it is not used
in this sample.

The access mode flags are passed as the third parameter to specify what
access modes are permitted on the opened file. STGM_READ opens the file
with read-only permission. STGM_DIRECT opens the file for direct access,
as opposed to transacted access. STGM_SHARE_EXCLUSIVE opens the file for
exclusive, non-shared use by the caller.

The fourth element exclusion parameter in NULL, indicating it is not used
in this sample.

The fifth parameter is reserved for future use and must be 0.

The address of pointer variable m_pIStorage is passed as the sixth
parameter. When the call successfully returns, m_pIStorage contains a
pointer to an IStorage interface. This is the interface that is used for
any subsequent operations on the opened file.

The important operation in this case is to have the COPaper object load
its drawing data from the file. This is done above using the Load method
of COPaper's IPaper interface. If COPaper succeeds in loading its data
using the IStorage provided, the name of the compound file is copied into
m_szCurFileName as the new current file name.

When these operations are successfully completed, the IStorage interface
that was obtained is released. This closes the file and means that the
file is not held open during other client operations. It will be reopened
when needed.
