Figure 3    DCOM Whiteboard Interfaces

IScribbleServer Interface Methods
RegisterClient At startup, the client creates an instance of the server object and calls this method. The client passes a pointer to its IUnknown interface so that it can receive callbacks from the server. The server returns a cookie that the client uses to identify itself in subsequent calls to the server.
UnregisterClient Before being destroyed, the client calls this method to remove itself from the server's notification list.
AddStroke When a stroke is drawn, the client (who receives the stroke) calls this method to pass the data—stored as a SafeArray—to the server, which then broadcasts the stroke to each of the other clients.
RemoveAllStrokes When the Erase button is clicked, the client calls this method to tell the server to clear the whiteboard.
IScribbleClient Interface Methods
OnNewStroke Called by the server when another client adds a stroke to the whiteboard.
OnRemoveAll Called by the server when any client submits a RemoveAllStrokes request.


Figure 4    Scribble Client


 #import "../ScribSvr/ScribSvr.tlb" no_namespace raw_interfaces_only
 
 class ScribbleCtrl : public CActiveXDocControl
 {
     friend class CScribServDoc;
 
 public:
     ScribbleCtrl();
     virtual void OnClose(DWORD dwSaveOption);
 
     DECLARE_INTERFACE_MAP()
     BEGIN_INTERFACE_PART(Client, IScribbleClient)
         STDMETHOD(OnRemoveAll)();
         STDMETHOD(OnNewStroke)(VARIANT vtStroke);
     END_INTERFACE_PART(Client)
 
     enum {
     //{{AFX_DISP_ID(ScribbleCtrl)
     //}}AFX_DISP_ID
     };
 
 protected:
     long m_lCookie;
     IScribbleServerPtr m_pServer;
 
     ...
 
     //{{AFX_MSG(ScribbleCtrl)
     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
     //}}AFX_MSG
     //{{AFX_DISPATCH(ScribbleCtrl)
     //}}AFX_DISPATCH
     //{{AFX_EVENT(ScribbleCtrl)
     //}}AFX_EVENT
 
     DECLARE_DYNCREATE(ScribbleCtrl)
 
 };
 
 ScribbleCtrl::ScribbleCtrl()
 {
     InitializeIIDs(&IID_DScribble, &IID_DScribbleEvents);
     SetInitialSize(200, 200);
 
     AddDocTemplate(new CActiveXDocTemplate(
         RUNTIME_CLASS(CScribServDoc),
         RUNTIME_CLASS(CScribbleFrame),
         RUNTIME_CLASS(CScribbleView)));
 
     m_pServer.CreateInstance("ExtremeCpp.ScribbleServer");
 }
 
 int ScribbleCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) 
 {
     if (CActiveXDocControl::OnCreate(lpCreateStruct) == -1)
         return -1;
 
     if (m_pServer == NULL)
         AfxMessageBox("Could not locate Server.");
     else
     {
         IUnknown* lpUnknown = NULL;
         ExternalQueryInterface(&IID_IUnknown, (void**) &lpUnknown);
         m_pServer->RegisterClient(lpUnknown, &m_lCookie);
         lpUnknown->Release();
     }
 
     return 0;
 }
 
 void ScribbleCtrl::OnClose(DWORD dwSaveOption) 
 {
     if (m_pServer)
         m_pServer->UnregisterClient(m_lCookie);
     
     CActiveXDocControl::OnClose(dwSaveOption);
 }
 
 BEGIN_INTERFACE_MAP(ScribbleCtrl, CActiveXDocControl)
     INTERFACE_PART(ScribbleCtrl, IID_IScribbleClient, Client)
 END_INTERFACE_MAP()
 
 STDMETHODIMP ScribbleCtrl::XClient::OnRemoveAll()
 {
     METHOD_PROLOGUE(ScribbleCtrl, Client)
 
     CScribServDoc* pDoc = DYNAMIC_DOWNCAST(CScribServDoc,
         pThis->m_pFrameWnd->GetActiveDocument());
     pDoc->OnNewDocument();
     pDoc->UpdateAllViews(NULL);
 
     return S_OK;
 }
 
 STDMETHODIMP ScribbleCtrl::XClient::OnNewStroke(VARIANT vtStroke)
 {
     METHOD_PROLOGUE(ScribbleCtrl, Client)
 
     CScribServDoc* pDoc = DYNAMIC_DOWNCAST(CScribServDoc,
         pThis->m_pFrameWnd->GetActiveDocument());
     pDoc->AddExternalStroke(vtStroke);
 
     return S_OK;
 }

Figure 5   CObject to VARIANT Conversion


 void CScribServDoc::MakeVariantFromObject(CObject* pObject, VARIANT& vtArray)
 {
     ASSERT(pObject);
 
     CMemFile memFile;
     CArchive archive(&memFile, CArchive::store);
     pObject->Serialize(archive);
     archive.Close();
 
     char sTemp[20];
     UINT nLength = memFile.GetLength();
     UCHAR* pBuffer = memFile.Detach();
     long* pArray = (long*) pBuffer;
 
     CString strAscii = itoa(nLength, sTemp, 10);
     for (UINT nLoop = 0; nLoop < nLength / sizeof(long); nLoop++)
     {
         strAscii += '@';    // Separator character
         strAscii += ultoa(pArray[nLoop], sTemp, 16);
     }
 
     COleSafeArray safeArray;
     safeArray.CreateOneDim(VT_UI1, strAscii.GetLength(),
         strAscii.LockBuffer());
     vtArray = safeArray.Detach();
 
     delete [] pBuffer;
 }
 
 CObject* CScribServDoc::MakeObjectFromVariant(
     CRuntimeClass* pObjType, VARIANT& vtArray)
 {
     char* pNextNum;
     COleSafeArray safeArray;
     safeArray.Attach(vtArray);
     safeArray.AccessData((void**) &pNextNum);
 
     UINT nLength = (UINT) strtoul(pNextNum, &pNextNum, 10) / sizeof(long) + 1;
     ULONG* pArray = new ULONG[nLength];
 
     for (UINT nCount = 0; nCount < nLength; nCount++)
         pArray[nCount] = strtoul(++pNextNum, &pNextNum, 16);
 
     safeArray.UnaccessData();
     safeArray.Detach();
 
     CMemFile memFile((BYTE*) pArray, nLength * sizeof(long));
     CArchive archive(&memFile, CArchive::load);
     CObject* pObject = pObjType->CreateObject();
     pObject->Serialize(archive);
     archive.Close();
 
     delete [] pArray;
     return pObject;
 }

Figure 6   Whiteboard Server


 // Server.cpp : Implementation of CScribbleServer
 
 #include "stdafx.h"
 #include "ScribSvr.h"
 #include "Server.h"
 
 /////////////////////////////////////////////////////////////////////////////
 // CScribbleServer
 
 VariantVector CScribbleServer::m_strokes;
 ClientVector CScribbleServer::m_clients;
 long CScribbleServer::m_lCookieCount = 0;
 
 STDMETHODIMP CScribbleServer::RegisterClient(IUnknown* lpUnknown, long* pCookie)
 {
     if (lpUnknown == NULL || pCookie == NULL)
         return E_POINTER;
 
     IScribbleClient* lpClient = NULL;
     HRESULT hr = lpUnknown->QueryInterface(
         IID_IScribbleClient, (void**) &lpClient);
     if (!SUCCEEDED(hr))
         return hr;
 
     CClientInfo client(lpClient, m_lCookieCount++);
     m_clients.push_back(client);
     *pCookie = client.lCookie;
 
     VariantVector::iterator item;
     for (item = m_strokes.begin(); item != m_strokes.end(); item++)
         hr = lpClient->OnNewStroke(*item);
 
     return S_OK;
 }
 
 STDMETHODIMP CScribbleServer::UnregisterClient(long lCookie)
 {
     ClientVector::iterator item;
     for (item = m_clients.begin(); item != m_clients.end(); item++)
     {
         if (item->lCookie == lCookie)
         {
             item->lpClient->Release();
             m_clients.erase(item);
             return S_OK;
         }
     }
     
     return E_INVALIDARG;
 }
 
 STDMETHODIMP CScribbleServer::AddStroke(
     long lCookie, VARIANT vtStroke, long* pIndex)
 {
     ATLTRACE("Added new stroke\n");
     _ASSERTE(vtStroke.vt & VT_ARRAY);
     m_strokes.push_back(vtStroke);
 
     ClientVector::iterator item;
     for (item = m_clients.begin(); item != m_clients.end(); item++)
     {
         if (lCookie != item->lCookie)
         {
             try
             {
                 item->lpClient->OnNewStroke(vtStroke);
             }
             catch(...)
             {
             }
         }
     }
 
     return S_OK;
 }
 
 STDMETHODIMP CScribbleServer::RemoveAllStrokes()
 {
     ATLTRACE("Removed all strokes\n");
     m_strokes.clear();
 
     ClientVector::iterator item;
     for (item = m_clients.begin(); item != m_clients.end(); item++)
     {
         try
         {
             item->lpClient->OnRemoveAll();
         }
         catch(...)
         {
         }
     }
 
     return S_OK;
 }
}