/* --------------------------------------------------------------------------

Basic Forms example of a custom sendable form.  It is an EXE server
rather than a DLL.  It implements the minimum form interface required
to launch and send a form.

Copyright (C) 1995 Microsoft Corporation

-------------------------------------------------------------------------- */

//#define ALLOW_SUBCLASS_IPM // all other forms to subclass the reply behavior
                             // of this form (slower, but more correct)

#include <windows.h>
#include <windowsx.h>
#include <ole2.h>
#include <initguid.h>
#include <mapiform.h>
#define INITGUID
#include <initguid.h>
#include <mapix.h>
#include <mapiutil.h>
#include <mapinls.h>
#include "dbugit.h"
#include "check.h"
#include "form.h"
#include "dlg.h"

/*
 *  
 * Checkers form clsid.  This must match the configuration file.
 *  
 */

DEFINE_GUID(CLSID_MyFormsClsId,  0x86174010, 0x5030, 0x0076, 0x99, 0x12, 0x00, 0xaa, 0x00, 0x38, 0x90, 0x1b);

/*
 *  HrStartOleAndRegisterClassFactory
 *
 *  Purpose:
 *      Initialize OLE, MAPI, and the Forms Interface
 *      Should be called from WinMain() or InitApplication() in an SDI app
 *
 *      This function LoadLibraries the neccessary DLLs rather than
 *      linking with them.  This permits the form to run as a stand-
 *      alone executable even when MAPI and OLE are not installed.
 *
 *  Returns:
 *      HRESULT
 */

#ifdef WIN32
#define szOleDll  "ole32.dll"
#define szMapiDll "mapi32.dll"
#else
#define szOleDll  "compobj.dll"
#define szMapiDll "mapi.dll"
#endif

HINSTANCE hinstOle   = NULL;
HINSTANCE hinstMapi  = NULL;


typedef HRESULT (FAR PASCAL *LPFNCOREGISTERCLASSOBJECT)(REFCLSID rclsid,
    IUnknown FAR * pUnk, DWORD dwClsContext, DWORD flags, LPDWORD lpdwRegister);
#ifdef WIN16
typedef BOOL (FAR PASCAL *LPFNISEQUALGUID)(REFGUID rguid1, REFGUID rguid2);
#undef IsEqualIID
#define IsEqualIID(riid1, riid2) (*lpfnIsEqualGUID)(riid1, riid2)
#endif
typedef HRESULT (FAR PASCAL *LPFNHRQUERYALLROWS)(LPMAPITABLE ptable, 
                        LPSPropTagArray ptaga, LPSRestriction pres,
                        LPSSortOrderSet psos, LONG crowsMax,
                        LPSRowSet FAR *pprows);
typedef ULONG   (FAR PASCAL *LPFNMAPIFREEBUFFER)(LPVOID pv);
typedef HRESULT (FAR PASCAL *LPFNMAPIINITIALIZE)(LPVOID lpvReserved);
typedef void    (FAR PASCAL *LPFNMAPIUNINITIALIZE)(VOID);

LPFNCOREGISTERCLASSOBJECT lpfnCoRegisterClassObject;
#ifdef WIN16
LPFNISEQUALGUID           lpfnIsEqualGUID;
#endif
LPFNHRQUERYALLROWS        lpfnHrQueryAllRows       ;
LPFNMAPIFREEBUFFER        lpfnMAPIFreeBuffer       ;
LPFNMAPIINITIALIZE        lpfnMAPIInitialize       ;
LPFNMAPIUNINITIALIZE      lpfnMAPIUninitialize     ;

HRESULT
HrStartOleAndRegisterClassFactory(void)
{
    FRMFMR *    pfrmfmr = NULL;
    HRESULT     hr;

    TraceTag(tagFormFunc,"HrStartOleAndRegisterClassFactory");

    // ----- LoadLibrary the essentials
    hinstOle   = LoadLibrary(szOleDll);
    hinstMapi  = LoadLibrary(szMapiDll);
    #ifdef WIN16
    if (hinstOle   < HINSTANCE_ERROR) hinstOle   = 0;
    if (hinstMapi  < HINSTANCE_ERROR) hinstMapi  = 0;
    #endif
    if (0 == hinstOle || 0 == hinstMapi)
        {
        return ResultFromScode(E_FAIL);
        }
        

    // ----- Setup a few function pointers
    lpfnCoRegisterClassObject = (LPFNCOREGISTERCLASSOBJECT) GetProcAddress(hinstOle,  "CoRegisterClassObject");
    #ifdef WIN32
    lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows@24");
    #else
    lpfnIsEqualGUID           = (LPFNISEQUALGUID          ) GetProcAddress(hinstOle,  "IsEqualGUID");
    lpfnHrQueryAllRows        = (LPFNHRQUERYALLROWS       ) GetProcAddress(hinstMapi,"HrQueryAllRows");
    #endif
    lpfnMAPIFreeBuffer        = (LPFNMAPIFREEBUFFER       ) GetProcAddress(hinstMapi,"MAPIFreeBuffer");
    lpfnMAPIInitialize        = (LPFNMAPIINITIALIZE       ) GetProcAddress(hinstMapi,"MAPIInitialize");
    lpfnMAPIUninitialize      = (LPFNMAPIUNINITIALIZE     ) GetProcAddress(hinstMapi,"MAPIUninitialize");
    AssertSz(lpfnCoRegisterClassObject ,"missing lpfnCoRegisterClassObject");
    AssertSz(lpfnHrQueryAllRows        ,"missing lpfnHrQueryAllRows       ");
    AssertSz(lpfnMAPIFreeBuffer        ,"missing lpfnMAPIFreeBuffer       ");
    AssertSz(lpfnMAPIInitialize        ,"missing lpfnMAPIInitialize       ");
    AssertSz(lpfnMAPIUninitialize      ,"missing lpfnMAPIUninitialize     ");
    if (0 == lpfnCoRegisterClassObject ||
        0 == lpfnHrQueryAllRows        ||
        0 == lpfnMAPIFreeBuffer        ||
        0 == lpfnMAPIInitialize        ||
        0 == lpfnMAPIUninitialize      )
        {
        AssertSz(0,"get procaddress failed");
        return ResultFromScode(E_FAIL);
        }    

    // ----- Initialize MAPI
    hr = (*lpfnMAPIInitialize)(NULL);
    if (S_OK != hr)
        {
        TraceTag(tagForm,"MapiInit failed 0x%08lx",hr);
        return hr;
        }

    // ----- Allocate Memory for our class factory
    pfrmfmr = new FRMFMR;
    if (NULL == pfrmfmr)
        {
        TraceTag(tagForm, "RegisterClassFactory: OOM 0x%08lx",hr);
        hr = ResultFromScode(E_OUTOFMEMORY);
        return hr;
        }

    // ----- Register our class object(s)
    DWORD dwRegMyForm = 0;
    hr = (*lpfnCoRegisterClassObject) (CLSID_MyFormsClsId, (LPUNKNOWN)pfrmfmr,
            CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
            &dwRegMyForm); /* switch singleuse to multipleuse if you are an MDI app */
    if (FAILED(hr))
        {
        TraceTag(tagForm,"CoRegisterClassObject() failed 0x%08lx",hr);
        return hr;
        }

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  HrStopForms
 *
 *  Purpose:
 *      UnInitialize OLE, MAPI, and the Forms Interface
 *
 *  Returns:
 *      HRESULT == 0
 */
HRESULT
HrStopForms(void)
{
    HRESULT hr = ResultFromScode(S_OK);

    TraceTag(tagFormFunc,"HrStopForms");

    (*lpfnMAPIUninitialize)();

    FreeLibrary(hinstOle);
    FreeLibrary(hinstMapi);

    return hr;
}


/*
 *  S a m p l e   F o r m
 */

// Checkers form specific methods follow ///////////////////////////

/*
 *  FRM::ScGetRecipientAdrList
 *
 *  Purpose:
 *      Fill the addrlist with the current recipients in the message
 *
 *  Arguments:
 *      LPMESSAGE - the message (in)
 *      LPADRLIST - the addr list destination (out)
 *
 *  Returns:
 *      SCODE       Error status.
 */
SCODE
FRM::ScGetRecipientAdrlist(LPMESSAGE pmsg, LPADRLIST * ppal)
{
    SCODE           sc      = S_OK;
    HRESULT         hr;
    LPMAPITABLE     pmt     = NULL;
    LPSPropTagArray ptaga   = NULL;

    TraceTag(tagFormFunc,"ScGetRecipientAdrlist");
    AssertSz(!*ppal, "pal should be NULL on entry");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // Get the recipient table from the message
    if (hr = pmsg->GetRecipientTable(MAPI_DEFERRED_ERRORS, &pmt))
    {
        TraceTag(tagForm,"2043 0x%08lx",hr);
        goto Cleanup;
    }

    if (hr = pmt->QueryColumns(TBL_ALL_COLUMNS, &ptaga))
    {
        TraceTag(tagForm,"sdlkfj 0x%08lx",hr);
        goto Cleanup;
    }

#ifdef NEVER
    ConvertToCorrectCharset(ptaga);
#endif

    // Read in the recipients
    hr = (*lpfnHrQueryAllRows)(pmt, ptaga, NULL, NULL, 0, (LPSRowSet *) ppal);
    if (hr)
    {
        TraceTag(tagForm,"sdfhjsadjfhadkflhxxxx 0x%08lx",hr);
        goto Cleanup;
    }

Cleanup:
    TraceTag(tagForm,"ScGetRecipientAdrlist 0x%08lx 0x%08lx", hr, sc);

    (*lpfnMAPIFreeBuffer)(ptaga);
    ReleaseObj(pmt);

    TraceTag(tagForm,"return 0x%08lx",ResultFromScode(sc));
    return sc;
}

/*
 *  FRM::SendForm
 *
 *  Purpose:
 *      Have the message site send us
 *      (also tries to send the message using mapi if message site fails)
 *
 *  Arguments:
 *      None.
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::SendForm(VOID)
{
    HRESULT hr = S_OK;

    TraceTag(tagFormFunc,"FRM::SendForm (this is not a standard form function)");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pMessage);

    // ----- Submit message
    if (hr = pMessageSite->SubmitMessage(0) )
    {
        TraceTag(tagForm,"failure pMessageSite->SubmitMessage 0x%08lx",hr);

        #ifndef GOOD_FORM_BEHAVIOR // the following is not standard behavior

        // ----- No harm in trying to send it myself
        if (IDYES == MessageBox(hMainWnd, "The message site failed to send this form.\nDo you want this form to attempt sending through the message interface?", "Checkers", MB_YESNO | MB_ICONSTOP))
        {
            // this is often times easier to debug than the remoted
            // message site interface (which does the same thing .. almost)

            if (hr = Save(NULL,TRUE))
                {
                TraceTag(tagForm,"NO: ::Save 0x%08lx",hr);
                }

            HandsOffMessage();

            if (hr = pMessage->SaveChanges(KEEP_OPEN_READWRITE) )
                {
                TraceTag(tagForm,"NO: pMessage->SaveChanges 0x%08lx",hr);
                }
            if (hr = pMessage->SubmitMessage(0) )
                {
                TraceTag(tagForm,"NO: pMessage->SubmitMessage 0x%08lx",hr);
                }
        }

        #endif // good_form_behavior
    }

    // ----- advise everyone of what we just did
    ADVISE(OnSubmitted)();

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  FRM::LaunchReplyMessage
 *
 *  Purpose:
 *      Construct a reply to PR_SENDER* (note: ignoring sent representing)
 *      Display any form user interface on the existing form
 *
 *  Arguments:
 *      HWND                Parent window
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::LaunchReplyMessage(ULONG ulhwndParent)
{
    #ifdef ALLOW_SUBCLASS_IPM
    LPMAPIFORM pNewForm;
    LPPERSISTMESSAGE pNewFormIPersist;
    #endif

    ULONG itaga;
    ADRLIST al = {1,0}; /* our adrlist will have exactly one entry */
    HRESULT hr = S_OK;
    LPMAPIMESSAGESITE pNewMessageSite;
    LPMAPIVIEWCONTEXT pNewMapiViewContext;
    LPMESSAGE pNewMessage;

    SizedSPropTagArray(6,tagaSender) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_SENDER_NAME,
            PR_SENDER_ADDRTYPE,
            PR_SENDER_ENTRYID,
            PR_SENDER_EMAIL_ADDRESS,
            PR_SENDER_SEARCH_KEY } };
    SizedSPropTagArray(6,tagaRepliee) =
        { 6,
        { PR_RECIPIENT_TYPE,
            PR_DISPLAY_NAME,
            PR_ADDRTYPE,
            PR_ENTRYID,
            PR_EMAIL_ADDRESS,
            PR_SEARCH_KEY
        } };
    static SizedSPropTagArray(26,tagaRemoveFromNewReply) =
        { 26,
        {   // Stuff you would typically want to remove on reply
            PR_MESSAGE_FLAGS,               // Want unsent compose note
            PR_MESSAGE_RECIPIENTS,          // Will generate new recip list
            PR_SENDER_ENTRYID,              // Clear sender/recipient info
            PR_SENDER_NAME,                 //
            PR_RECEIVED_BY_ENTRYID,         //
            PR_RECEIVED_BY_NAME,            //
            PR_SENT_REPRESENTING_ENTRYID,   // Clear delegate access stuff
            PR_SENT_REPRESENTING_NAME,      //
            PR_SENT_REPRESENTING_ADDRTYPE,  // 10961
            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
            PR_RCVD_REPRESENTING_ENTRYID,   // 
            PR_RCVD_REPRESENTING_NAME,      //
            PR_READ_RECEIPT_ENTRYID,        // Clear destination overrides
            PR_REPORT_ENTRYID,              //
            PR_REPLY_RECIPIENT_ENTRIES,     //
            PR_REPLY_RECIPIENT_NAMES,       //
            PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED, // Clear delivery receipt
            PR_READ_RECEIPT_REQUESTED,      // Clear read receipt
            PR_CLIENT_SUBMIT_TIME,          // Clear submit time
            PR_MESSAGE_ATTACHMENTS,         // Drop attachments on reply
            PR_ORIGINAL_AUTHOR_ENTRYID,     // Keep original author information
            PR_ORIGINAL_AUTHOR_NAME,        //  on forwards
            PR_ORIGINAL_SUBMIT_TIME,        // Keep original time on forwards
            PR_IMPORTANCE,                  // Lose importance on reply
            PR_PRIORITY,                    // Lose priority on reply
            PR_SENSITIVITY                  // Lose sensitivity on reply
        } };

    TraceTag(tagFormFunc,"FRM::LaunchReplyMessage");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    #ifdef ALLOW_SUBCLASS_IPM

    /*
       Since I am a single instance exe, creating a new form results in
       a new process.  For performance reasons, this form does not conform
       to the forms API precisely. This effectively removes the ability to
       subclass the reply note for IPM.Checkers.  This is acceptable.

    */

    // ----- open form manager
    AssertSz(NULL == pFormMgr,"two form managers?");
    hr = pMessageSite->GetFormManager(&pFormMgr);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure MAPIOpenFormMgr 0x%08lx",hr);
        return hr;
        }
    Assert(pFormMgr);

    // ----- Get form info
    hr = pFormMgr->ResolveMessageClass("IPM.Checkers",0,NULL,&pFormInfo);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure to ResolveMessageClass 0x%08lx",hr);
        return hr;
        }

    // ----- Create the new form
    hr = pFormMgr->CreateForm(0,0,pFormInfo,IID_IMAPIForm,(LPVOID FAR*) &pNewForm);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure to CreateForm 0x%08lx",hr);
        return hr;
        }

    hr = pNewForm->QueryInterface(IID_IPersistMessage, (LPVOID FAR*) &pNewFormIPersist);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure asking form for IPersistMessage 0x%08lx",hr);
        return hr;
        }
    AssertSz(pNewFormIPersist,"have it?");

    #endif // ALLOW_SUBCLASS_IPM

    // ----- Create the reply message
    hr = pMessageSite->NewMessage(0,NULL,
        #ifdef ALLOW_SUBCLASS_IPM
        pNewFormIPersist
        #else
        this
        #endif // ALLOW_SUBCLASS_IPM
        ,&pNewMessage,&pNewMessageSite,&pNewMapiViewContext);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure VDOG_*NewMessage* comform...c 0x%08lx",hr);
        return hr;
        }
    AssertSz(pNewMessage,"nothing new with you");
    AssertSz(pNewMessageSite,"no new site?");
    AssertSz(pNewMapiViewContext,"no new view context ... did NewMessage work at all?");

    // ----- Copy current message to new message
    hr = pMessage->CopyTo(0, NULL, (LPSPropTagArray)&tagaRemoveFromNewReply, 0, NULL,
                                    (LPIID) &IID_IMessage, pNewMessage, 0, NULL);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure CopyTo 0x%08lx",hr);
        return hr;
        }

    // ----- who sent this to us?
    hr = pMessage->GetProps((LPSPropTagArray) &tagaSender, 0, &al.aEntries[0].cValues, &al.aEntries[0].rgPropVals);
    AssertSz(ResultFromScode(MAPI_W_ERRORS_RETURNED) == hr,"mapi gave me pr_recipient_type, but should not have");

    // ----- Make the sender the recipient
    if (al.aEntries && al.aEntries[0].rgPropVals)
    {
        al.aEntries[0].rgPropVals[0].ulPropTag = PR_RECIPIENT_TYPE;
        al.aEntries[0].rgPropVals[0].Value.ul = MAPI_TO;
    }
    else
    {
        AssertSz(0,"could not form reply message: al.aEntries && al.aEntries[0].rgPropVals");
        return ResultFromScode(E_FAIL);
    }

    // ----- Set our new recipients properties to their expected property ids
    itaga = 1;

    TraceTag(tagForm,"0x%08lx cEntries",al.cEntries);
    TraceTag(tagForm,"0x%08lx <0x%08lx> %s",al.aEntries[0].rgPropVals[itaga].ulPropTag,al.aEntries[0].rgPropVals[itaga].Value.ul,al.aEntries[0].rgPropVals[itaga].Value.LPSZ);

    for (itaga = 1; itaga < tagaRepliee.cValues; itaga++)
        {
        al.aEntries[0].rgPropVals[itaga].ulPropTag =
            PROP_TAG(PROP_TYPE(al.aEntries[0].rgPropVals[itaga].ulPropTag),
                PROP_ID(tagaRepliee.aulPropTag[itaga]));

        TraceTag(tagForm,"0x%08lx <0x%08lx> %d",al.aEntries[0].rgPropVals[itaga].ulPropTag,al.aEntries[0].rgPropVals[itaga].Value.ul,itaga);

        AssertSz(SUCCEEDED(al.aEntries[0].rgPropVals[itaga].Value.ul),"Failure to get PR_SENDER* 10961 ");
        }

    // ----- Save out addresses
    AssertSz(1 == al.cEntries,"we only reply to one person");
    hr = pNewMessage->ModifyRecipients(0, &al);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"pMessage->ModifyRecipients 0x%08lx",hr);
        return hr;
        }

    // ----- Release everything the read form was remembering (if we reply inplace)
    #ifndef ALLOW_SUBCLASS_IPM
    Forget();
    #endif

    // ----- Call LoadForm (this makes the current form the new form)
    hr =
    #ifdef ALLOW_SUBCLASS_IPM
    pNewFormIPersist->
    #endif
    Load(pNewMessageSite,pNewMessage,0,MSGFLAG_UNSENT);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure LoadForm 0x%08lx",hr);
        return hr;
        }

    // ----- Call SetViewContext
    #ifdef ALLOW_SUBCLASS_IPM
    hr = pNewForm->SetViewContext(pNewMapiViewContext);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure SetViewContext 0x%08lx",hr);
        return hr;
        }
    #endif

    // ----- Call DoVerb So we can see the reply form
    hr = 
    #ifdef ALLOW_SUBCLASS_IPM
    pNewForm->
    #endif
    DoVerb(OLEIVERB_PRIMARY,NULL,ulhwndParent,NULL);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure DoVerb 0x%08lx",hr);
        return hr;
        }

    // ----- release stuff
    pNewMessage->Release();
    pNewMessageSite->Release();
    pNewMapiViewContext->Release();
    (*lpfnMAPIFreeBuffer)(al.aEntries[0].rgPropVals);

    // ----- Close down the read form (that's me)
    #ifdef ALLOW_SUBCLASS_IPM
    hr = ShutdownForm(OLECLOSE_NOSAVE);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failure ShutdownForm'ing read form 0x%08lx",hr);
        return hr;
        }
    #endif // ALLOW_SUBCLASS_IPM

    // ----- return to caller
    return hr;

}

/*
 *  FRM::GetCheckersData
 *
 *  Purpose:
 *      Allows anyone to query the form for it's current data
 *
 *  Arguments:
 *      data members (out)
 *
 *  Returns:
 *      void
 */
VOID
FRM::GetCheckersData(SQUARE* out_b, int* out_turn, long* out_movenum, long* out_score)
{
    Assert(out_b);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (turn) /* set elements only if we have data to give out */
    {
        memcpy(out_b,b,sizeof(b));
        if (out_turn) *out_turn = turn;
        if (out_movenum) *out_movenum = movenum;
        if (out_score) *out_score = score;
    }
}

/*
 *  FRM::SetCheckersData
 *
 *  Purpose:
 *      Allows anyone to set the forms current data members
 *
 *  Arguments:
 *      data members (in)
 *
 *  Returns:
 *      void
 */
VOID
FRM::SetCheckersData(SQUARE* in_b, int in_turn, long in_movenumber, long in_score)
{
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(in_b && in_turn);

    fDirty = TRUE;

    memcpy(b,in_b,sizeof(b));
    turn    = in_turn;
    movenum = in_movenumber;
    score   = in_score;
}


/*
 *  FRM::AddressForm
 *
 *  Purpose:
 *      Look at the current message addresses, and show user
 *      interface to address the message.
 *
 *  Arguments:
 *      HWND - parent
 *      BOOL - true if no user interface should be presented when
 *             recipients are already present
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::AddressForm(HWND hwnd, BOOL fDontShowIfRecipsExist)
{
    LPADRBOOK   pAdrBook;
    ULONG       ulUIParam = (ULONG) (UINT) hwnd;
    SCODE       sc;
    HRESULT     hr = S_OK;
    ADRPARM     ap = { 0 };
    LPADRLIST   pal = NULL;
    BOOL        fCloseForm = FALSE;

    TraceTag(tagFormFunc,"FRM::AddressForm");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    // ----- Read in addresses from message
    sc = ScGetRecipientAdrlist(pMessage, &pal);
    hr = ResultFromScode(sc);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failed to read address into pal 0x%08lx",hr);
        return hr;
        }

    // ----- remember address book from the session
    hr = pSession->OpenAddressBook(ulUIParam, NULL, 0, &pAdrBook);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"failed to get pAdrBook object 0x%08lx",hr);
        goto cleanuppal;
        }

    // ----- Show the address book
    ap.ulFlags = AB_RESOLVE | DIALOG_OPTIONS | DIALOG_MODAL | fMapiUnicode;
    ap.lpszCaption = TEXT("Select Checkers Opponent");
    ap.cDestFields = 1;
    if (0 == fDontShowIfRecipsExist || 0 == pal->cEntries)
        hr = pAdrBook->Address(&ulUIParam, &ap, &pal);
    #ifdef DEBUG
    if (hwnd != hMainWnd)
    {
        TraceTag(tagNull,"ADDRESSFORM: pAdrBook->Address changed it's out parameter even though DIALOG_MODAL");
    }
    #endif
    if (FAILED(hr))
    {
        // cancel is a failed scode
        TraceTag(tagForm,"cant pop up addr book 0x%08lx %d",hr,pal?pal->cEntries:0);
    }

    // ----- Save out addresses
    AssertSz(pMessage,"pMessage said goodbye during the Address function");
    hr = pMessage->ModifyRecipients(0, pal);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"pMessage->ModifyRecipients 0x%08lx",hr);
        goto cleanup;
        }

    // ----- Close the form if there are no recipients for this move
    if (pal->cEntries <= 0) fCloseForm = TRUE;

    // ----- Release the address book, adrlist, and clean up
cleanup:
    Assert(pAdrBook);
    pAdrBook->Release();
cleanuppal:
    Assert(pal);
    (*lpfnMAPIFreeBuffer)( (LPSRowSet) pal);
    if (fCloseForm) ShutdownForm(OLECLOSE_PROMPTSAVE);

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}

/*
 *  FRM::Remember
 *
 *  Purpose:
 *      Store and addref the message site, the message, and the session
 *      for later use
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::Remember(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg)
{
    HRESULT hr;

    TraceTag(tagFormFunc,"FRM::Remember");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pmsite,"what am I going to do without it?");
    AssertSz(pmsg,"what am I going to do without it? pmsg that is");

    // ----- remember our message site object
    AssertSz(!pMessageSite,"who me? a message site?");
    pMessageSite = pmsite;
    pMessageSite->AddRef();

    // ----- remember our message
    AssertSz(!pMessage,"a message in my");
    pMessage = pmsg;
    pMessage->AddRef();

    // ----- remember mapi session
    AssertSz(!pSession,"another session?");
    hr = pMessageSite->GetSession(&pSession);

    #ifdef DEBUG    
    if (FAILED(hr))
    {
        TraceTag(tagForm,"failed to get session object %08lx",hr);
    }    
    #endif

    // ----- return result to caller
    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;

}

/*
 *  FRM::Forget
 *
 *  Purpose:
 *      Release the message site, the message, and the session
 *
 *  Returns:
 *      HRESULT Error Status.
 */
HRESULT
FRM::Forget(VOID)
{
    TraceTag(tagFormFunc,"FRM::Forget");

    if (pMessage) pMessage->Release();
    if (pMessageSite) pMessageSite->Release();
    if (pSession) pSession->Release();

    pMessage = NULL;
    pMessageSite = NULL;
    pSession = NULL;

    return NOERROR;
}

/*
 *  FRM::ShowCurrentMessage
 *
 *  Purpose:
 *      Display any form user interface on a form
 *
 *  Arguments:
 *      HWND                Parent window
 *
 *  Returns:
 *      HRESULT             Error status.
 */
HRESULT
FRM::ShowCurrentMessage(ULONG ulhwndParent)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::ShowCurrentMessage");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pMessageSite);
    Assert(pSession);
    Assert(pMessage);

    // ----- Give our user access to our form interface
    SendMessage(hMainWnd,EM_GIVEFORMTOHWND,0,(LPARAM) this);

    // ----- Display address book modal to form if this message has not yet been addressed
    FORWARD_WM_COMMAND(hMainWnd, IDM_ADDRESS, 0, 1 /* Don't Show Recips */, PostMessage);

    return hr;
}

// IUnknown methods follow ///////////////////////////

/*
 *  FRM::QueryInterface
 *
 *  Purpose:
 *      Returns a pointer to the specified interface.
 *
 *  Arguments:
 *      REFIID              Interface we want.
 *      LPUNKNOWN *         Interface we return.
 *
 *  Returns:
 *      HRESULT             Error status.
 */
STDMETHODIMP
FRM::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFuncTriv,"FRM::QueryInterface %s",DumpCLSID(riid));
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (IsEqualIID(riid, IID_IUnknown))
        {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
        }
    else if (IsEqualIID(riid, IID_IPersistMessage))
        {
        AddRef();
        *ppvObj = (IPersistMessage *)this;
        }
    else if (IsEqualIID(riid, IID_IMAPIForm))
        {
        AddRef();
        *ppvObj = (IMAPIForm *)this;
        }
    else
        {
        hr = ResultFromScode(E_NOINTERFACE);
        *ppvObj = NULL;
        }
    
    #ifdef DEBUG
    if (hr != ResultFromScode(E_NOINTERFACE)) AssertSz(ppvObj,"no object pointer");
    #endif

    TraceTag(tagForm,"return 0x%08lx",hr);
    return hr;
}


/*
 *  FRM::AddRef
 *
 *  Purpose:
 *      Increments reference count on the sample extension.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG               New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRM::AddRef(void)
{
    TraceTag(tagFuncTriv,"FRM::AddRef ret %d",cRef + 1);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    return ++cRef;
}


/*
 *  FRM::Release
 *
 *  Purpose:
 *      Decrements reference count on the sample extension.  If count is
 *      decremented to zero, the object is freed.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG               New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRM::Release(void)
{
    TraceTag(tagFuncTriv,"FRM::Release cRef %d",cRef);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    if (!(--cRef))
        {
        // ----- be sure our ui is gone when we leave
        #ifdef DEBUG
        if (IsWindow(hMainWnd))
            {
            TraceTag(tagForm,"Last Release called, but IsWindow(hMainWnd).  ShutdownForm called?");
            }
        TraceTag(tagForm,"return 0");
        #endif //debug
        delete this;
        return 0;
        }
    return cRef;
}


// IPersistMessage methods follow ///////////////////////////

/*
 *  FRM::GetLastError
 *
 *  Purpose:
 *      Get the last error
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             NOERROR always.
 */
STDMETHODIMP
FRM::GetLastError(HRESULT hResult, ULONG ulFlags, LPMAPIERROR FAR * lppMAPIError)
{
    TraceTag(tagFormFunc,"FRM::GetLastError 0x%08x",hResult);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    if (lppMAPIError) *lppMAPIError = NULL;
    return NOERROR;
}

/*
 *  FRM::GetClassID
 *
 *  Purpose:
 *      Get the class ID associated with this message.
 *
 *  Arguments:
 *      LPCLSID             Where to put the class ID.
 *
 *  Returns:
 *      HRESULT             NOERROR always.
 */
STDMETHODIMP
FRM::GetClassID(LPCLSID pclsid)
{
    
    TraceTag(tagFormFunc,"FRM::GetClassID");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // The form only plays with things of its own class ID, so
    // this is easy; it's more complicated if code supports multiple
    // classes, or can do "treat as" operations
    if (pclsid) *pclsid = clsid;

    return NOERROR;
}


/*
 *  FRM::IsDirty
 *
 *  Purpose:
 *      Returns whether the object has changed since the last save
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             S_OK if dirty, S_FALSE if not dirty.
 */
STDMETHODIMP
FRM::IsDirty(void)
{
    TraceTag(tagFormFunc,"FRM::IsDirty");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    
    return fDirty ? NOERROR : ResultFromScode(S_FALSE);
}


/*
 *  FRM::InitNew
 *
 *  Purpose:
 *      Create a new message of our message class in the provided pmsg.
 *
 *  Arguments:
 *      LPMAPISESSION       Session in which the message belongs.
 *      LPMESSAGE           Message to create the new form in.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::InitNew(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg)
{
    HRESULT             hr;
    SPropValue          prop;

    TraceTag(tagFormFunc,"FRM::InitNew");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
    {
        TraceTag(tagForm,"loss of memory in initnew 0x%08lx",hr);
        return hr;
    }

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT("IPM.Checkers");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in initnew 0x%08lx",hr);
        return hr;
        }

    // ----- remind ourselves that this new message could not have been sent
    fSentMessage = 0;

    // ----- set our special properties
    prop.ulPropTag  = PR_SUBJECT;
    prop.Value.LPSZ = TEXT("--- CHECKERS FORM ---");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_subject here 0x%08lx",hr);
        return hr;
        }

    ADVISE(OnNewMessage)();
    return hr;
}


/*
 *  FRM::Load
 *
 *  Purpose:
 *      Attaches our object to the provided pmsg.
 *
 *  Arguments:
 *      LPMAPISESSION       Our session to remember.
 *      LPMESSAGE           Our message to remember.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::Load(LPMAPIMESSAGESITE pmsite, LPMESSAGE pmsg, ULONG ulMessageStatus, ULONG ulMessageFlags)
{
    ULONG cProps = 0;
    #define ctagMax 10
    char rgchTaga[sizeof(SPropTagArray) + (ctagMax * sizeof(ULONG))];
    LPSPropTagArray ptaga = (LPSPropTagArray) rgchTaga;
    LPSPropValue rgval = NULL;
    LPSPropValue pval = NULL;
    HRESULT         hr=S_OK;

    TraceTag(tagFormFunc,"FRM::Load status=0x%08lx flags=0x%08lx",ulMessageStatus,ulMessageFlags);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- Remember our pointers and such
    hr = Remember(pmsite,pmsg);
    if (FAILED(hr))
        {
        TraceTag(tagForm,"loads call to remember failed 0x%08lx",hr);
        return hr;
        }

    // ----- If this message has been sent we would like to remember that
    fSentMessage = !( ulMessageFlags & MSGFLAG_UNSENT);
    TraceTag(tagForm,"fSentMessage = %d",(int) fSentMessage);

    // ----- Load our data out of the message like a nice form
    ptaga->cValues = 0;
    ptaga->aulPropTag[ptaga->cValues++] = PR_SUBJECT;
    ptaga->aulPropTag[ptaga->cValues++] = PR_BODY;
    ptaga->aulPropTag[ptaga->cValues++] = PR_BOARD;
    ptaga->aulPropTag[ptaga->cValues++] = PR_TURN;
    ptaga->aulPropTag[ptaga->cValues++] = PR_MOVENUMBER;

    hr = pmsg->GetProps(ptaga,0, &cProps, &rgval);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed getprops on pr_board there 0x%08lx",hr);
        return hr;
        }
    AssertSz(ptaga->cValues <= ctagMax, "Too many properties to read!");
    AssertSz(cProps == ptaga->cValues,"to mucho values");
    pval = rgval;

    // ----- set properties to variables
    pval;   // subject
    pval++; // body
    pval++; // board
    if (pval->Value.bin.cb == sizeof(b)) /* if it's a valid board */
        {
        memcpy(b,pval->Value.bin.lpb,(int) pval->Value.bin.cb);
        pval++; // turn
        AssertSz(pval->Value.l == RED || pval->Value.l == BLACK,"cool: neither red or blacks turn according to mapi");
        turn = (int) pval->Value.l;
        pval++; // movenumber
        }
    Assert(rgval);
    (*lpfnMAPIFreeBuffer)(rgval);
    ADVISE(OnNewMessage)();
    return hr;
}


/*
 *  FRM::Save
 *
 *  Purpose:
 *      Writes out our information to the provided pmsg. Does NOT commit
 *      changes; this is the responsibility of the caller. Puts the form
 *      into no-scribble mode until SaveCompleted is called.
 *
 *  Arguments:
 *      LPMESSAGE           Message to write our changes to.
 *      BOOL                TRUE if this is our home message, FALSE if
 *                          this is a different message.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::Save(LPMESSAGE pmsg, ULONG fSameAsLoad)
{
    SPropValue prop;
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::Save");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pMessage,"no pmesssg in ::Save");
    #ifdef DEBUG
    if (!pmsg)
    {
        TraceTag(tagNull,"NULL == pmsg in ::Save fsameasload==0x%08lx",fSameAsLoad);
    }
    #endif

    // ----- If this is the same pmsg as we got back when we loaded ...
    if (fSameAsLoad)
    {
        TraceTag(tagForm,"fSameAsLoad true");
        pmsg = pMessage;
    }

    // ----- Put ourselves in no-scribble mode
    fNoScribble = TRUE;

    // ----- set our message class
    prop.ulPropTag  = PR_MESSAGE_CLASS;
    prop.Value.LPSZ = TEXT("IPM.Checkers");
    hr = pMessage->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in initnew 0x%08lx",hr);
        return hr;
        }

    // ----- Write out our data
    AssertSz(turn,"nobody's turn? this is not a good sign...");
    prop.ulPropTag  = PR_BOARD;
    prop.Value.bin.lpb = (unsigned char *) b;
    prop.Value.bin.cb  = sizeof(b);
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_board here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_TURN;
    prop.Value.l = (long) turn;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_MOVENUMBER;
    prop.Value.l = (long) movenum;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_SCORINGFUNC;
    prop.Value.l = (long) score;
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_turn here 0x%08lx",hr);
        return hr;
        }
    prop.ulPropTag  = PR_BODY;
    prop.Value.lpszA = TextizeBoard(b);
    TraceTag(tagForm,"Here's the board I saved:\n%s",prop.Value.lpszA);
    hr = pmsg->SetProps(1, &prop, NULL);
    if (FAILED(hr) )
        {
        TraceTag(tagForm,"failed setprops in on pr_body here 0x%08lx",hr);
        return hr;
        }


    ADVISE(OnSaved)();
    return hr;
}


/*
 *  FRM::SaveCompleted
 *
 *  Purpose:
 *      Terminates no-scribble and hands-off modes, returning the object
 *      to its normal storage mode.
 *
 *  Arguments:
 *      LPMESSAGE           Our new home message, if we need to change.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::SaveCompleted(LPMESSAGE pmsg)
{
    TraceTag(tagFormFunc,"FRM::SaveCompleted");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // Reset modes
    fDirty = FALSE;
    fNoScribble = FALSE;

    return NOERROR;
}


/*
 *  FRM::HandsOffMessage
 *
 *  Purpose:
 *      Releases our reference on the message so that a Save As operation
 *      can occur.
 *
 *  Arguments:
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::HandsOffMessage(void)
{
    TraceTag(tagFormFunc,"FRM::HandsOffMessage");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    return NOERROR;
}


// IMAPIForm methods follow /////////////////////////////

/*
 *  FRM::DoVerb
 *
 *  Purpose:
 *      Performs the specified verb on the message.
 *
 *  Arguments:
 *      LONG                What to do.
 *      LPMAPIVIEWCONTEXT   Our view context.
 *      HWND                Our parent window.
 *      LPCRECT             Where we should display ourselves given a choice.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::DoVerb(LONG iVerb, LPMAPIVIEWCONTEXT pmvc, ULONG ulhwndParent,
               LPCRECT prcPosRect)
{
    TraceTag(tagFormFunc,"FRM::DoVerb iVerb=%d",iVerb);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    switch (iVerb)
        {
        default:
        case OLEIVERB_HIDE:
        case OLEIVERB_DISCARDUNDOSTATE:
            TraceTag(tagForm,"DoVerb: not implemented iVerb");
            return ResultFromScode(E_NOTIMPL);

        case OLEIVERB_UIACTIVATE:
        case OLEIVERB_INPLACEACTIVATE:
            TraceTag(tagForm,"DoVerb: not implemented iVerb=%d",iVerb);
            if (iVerb < 0)
                return ResultFromScode(E_NOTIMPL);
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);

        case OLEIVERB_SHOW:
            ShowCurrentMessage(ulhwndParent);
            return NOERROR;

        case OLEIVERB_OPEN:
        case OLEIVERB_PRIMARY:
            TraceTag(tagForm,"fSentMessage = %d",(int) fSentMessage);
            if (fSentMessage)
                LaunchReplyMessage(ulhwndParent);
            else
                ShowCurrentMessage(ulhwndParent);

            return NOERROR;
        }
}


/*
 *  FRM::ShutdownForm
 *
 *  Purpose:
 *      Closes down any UI associated with the form.
 *
 *  Arguments:
 *      DWORD               One of OLECLOSE_SAVEIFDIRTY, OLECLOSE_NOSAVE,
 *                          or OLECLOSE_PROMPTSAVE.
 *
 *  Returns:
 *      HRESULT             S_OK, or error value.
 */
STDMETHODIMP
FRM::ShutdownForm(DWORD dwSaveOptions)
{
    HRESULT hr = NOERROR;

    TraceTag(tagFormFunc,"FRM::ShutdownForm dwSaveOptions=%d",dwSaveOptions);
    TraceTag(tagForm,"pMessageSite 0x%08x",(ULONG) pMessageSite);
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");

    // ----- no way I'm closeing if I'm in a modal dialog
    //       especially if the modal dialog occurs in a remoted interface
    if (!IsWindowEnabled(hMainWnd))
        return ResultFromScode(E_ABORT);

    // ----- be kind, and save ourself  
    switch (dwSaveOptions)
    {
        case OLECLOSE_NOSAVE:
            break;

        case OLECLOSE_SAVEIFDIRTY:
            if (fDirty)
                hr = pMessageSite->SaveMessage();
            break;

        default:
        case OLECLOSE_PROMPTSAVE:
            if (fDirty)
                if (IDYES == MessageBox(hMainWnd, "Save changes?", "Checkers", MB_YESNO))
                    hr = pMessageSite->SaveMessage();
            break;

    }

    Assert(hMainWnd && IsWindow(hMainWnd));
    if (NOERROR == hr)
    {
        // ----- let everyone know we're shutting down
        ADVISE(OnShutdown)();

        // ----- Release everything we have remembered thus far
        Forget();

        // ----- make sure everyone has Unadvised
        AssertSz(0==afAdvisee[0],"0 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[1],"1 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[2],"2 didn't Unadvise before ShutdownForm");
        AssertSz(0==afAdvisee[3],"3 didn't Unadvise before ShutdownForm");

        // ----- post a quit message to our UI
        SendMessage(hMainWnd,WM_CLOSE,0,0);
    }

    return hr;
}

STDMETHODIMP
FRM::SetViewContext(LPMAPIVIEWCONTEXT pViewContextNew)
{
    TraceTag(tagFormFunc,"FRM::SetViewContext");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(pViewContextNew,"no view context to set");

    /* View context is used for next and previous behavior
       The checkers form does not do next and previous because
       there is not a standard read note.  It is always in 
       reply mode */

    return NOERROR;
}

STDMETHODIMP
FRM::GetViewContext(LPMAPIVIEWCONTEXT FAR * ppViewContext)
{
    TraceTag(tagFormFunc,"FRM::GetViewContext");
    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(ppViewContext,"get view context to where?");

    if (ppViewContext) *ppViewContext = NULL; /* not supported */

    return NOERROR;
}

STDMETHODIMP
FRM::Advise(LPMAPIVIEWADVISESINK pAdvise, ULONG FAR * pdwStatus)
{
    LONG i;

    TraceTag(tagFormFunc,"FRM::Advise");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    Assert(pdwStatus);
    Assert(pAdvise);

    // ----- remember who to advise    
    for (i=0; i<MAX_ADVISE; i++)
        if (!afAdvisee[i])
        {
            aAdvisePtrs[i] = pAdvise;
            afAdvisee[i] = 1;
            *pdwStatus = i + 1; /* ulConnection of zero is not valid */
            pAdvise->AddRef();
            return NOERROR;
        }

    // ----- bad news
    AssertSz(0,"out of aAdvisPtrs");
    return ResultFromScode(E_FAIL);
    return NOERROR;
}

STDMETHODIMP
FRM::Unadvise(ULONG ulConnection)
{
    TraceTag(tagFormFunc,"FRM::Unadvise");

    AssertSz(cRef > 0,"excuse me.  are you refering to me?");
    AssertSz(ulConnection < MAX_ADVISE && ulConnection >= 0,"testing, 123");
    AssertSz(ulConnection,"a non-zero ulConnection is not valid according to OLE");

    // ----- forget about advising this guy
    --ulConnection; // remember, we added one in advise
    AssertSz(afAdvisee[(int) ulConnection],"never wanted ::Advise in ::Unadvise?");
    afAdvisee[(int) ulConnection] = 0;
    aAdvisePtrs[(int) ulConnection]->Release();
    return NOERROR;
}


/*
 *  FRM::FRM
 *
 *  Purpose:
 *      Initialize or new form object
 *
 */
FRM::FRM(REFCLSID clsid)
{
    LONG i;

    TraceTag(tagFormFunc,"FRM::FRM .................");

    cRef = 1;
    this->clsid = clsid;

    pMessage = NULL;
    pMessageSite = NULL;
    pSession = NULL;

    pFormMgr = NULL;
    pFormInfo = NULL;

    fDirty = FALSE;

    for (i=0; i<MAX_ADVISE; i++)
        {
            aAdvisePtrs[i] = NULL;
            afAdvisee[i] = 0;
        }

    turn = 0;
}

/*
 *  FRM::~FRM
 *
 *  Purpose:
 *      Destroy our form object
 */
FRM::~FRM(void)
{
    TraceTag(tagFormFunc,"FRM::~FRM Bye now ...");
    AssertSz(0==cRef,"quit referring to this form please");
    AssertSz(NULL == pMessage,"still refing the message");
    AssertSz(NULL == pMessageSite,"still refing the messagesite");
    AssertSz(NULL == pSession,"still refing the session");
}

/*
 *  S a m p l e   F o r m   C l a s s   F a c t o r y
 *
 *  Because we are an exe server, we must implement a class factory
 *  so that other viewers (like Exchange) can learn of our clsid
 *
 */

/*
 *  FRMFAC::CreateInstance
 *
 *  Purpose:
 *      Creates a new form object of the IPM.Form class.
 *
 *  Arguments:
 *      LPUNKNOWN       Outer object to aggregate with (not supported).
 *      REFIID          Desired interface on new form object.
 *      LPVOID FAR *    Where to put new form object.
 *
 *  Returns:
 *      HRESULT         S_OK, or one of the following errors:
 *                      CLASS_E_NOAGGREGATION, E_OUTOFMEMORY,
 *                      E_NOINTERFACE, E_INVALIDARG.
 */
STDMETHODIMP
FRMFAC::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, LPVOID FAR * ppvObject)
{
    FRM *   pfrm = NULL;
    HRESULT hr;

    TraceTag(tagFormFunc,"FRMFAC::CreateInstance");

    // ----- Initialize out parameter and check validity of parameters
    if (!ppvObject)
        {
        hr = ResultFromScode(E_INVALIDARG);
        goto Cleanup;
        }
    *ppvObject = NULL;

    if (punkOuter)
        {
        hr = ResultFromScode(CLASS_E_NOAGGREGATION);
        goto Cleanup;
        }

    // ----- Instantiate new form
    if (!(pfrm = new FRM(clsid)))
        {
        TraceTag(tagForm,"E_OUTOFMEMORY 0x%08lx",hr);
        hr = ResultFromScode(E_OUTOFMEMORY);
        goto Cleanup;
        }

    // ----- Get the desired interface
    hr = pfrm->QueryInterface(riid, ppvObject);
    AssertSz(0==hr,"QueryInterface failed");

Cleanup:
    ReleaseObj(pfrm);
    TraceTag(tagForm,"return 0x%08lx initial reference %d",hr,cRef);
    return hr;
}

/*
 *  FRMFAC::QueryInterface
 *
 *  Purpose:
 *      Returns a pointer to the specified interface.
 *
 *  Arguments:
 *      REFIID          Interface we want.
 *      LPUNKNOWN *     Interface we return.
 *
 *  Returns:
 *      HRESULT         Error status.
 */
STDMETHODIMP
FRMFAC::QueryInterface(REFIID riid, LPVOID FAR * ppvObj)
{
    TraceTag(tagFuncTriv,"FRMFAC::QueryInterface %s",DumpCLSID(riid));
    if (IsEqualIID(riid, IID_IUnknown) || 
        IsEqualIID(riid, IID_IClassFactory))
        {
        *ppvObj = this;
        AddRef();
        TraceTag(tagForm,"return ok");
        return NOERROR;
        }

    *ppvObj = NULL;
    TraceTag(tagForm,"return no interface");
    return ResultFromScode(E_NOINTERFACE);
}

/*
 *  FRMFAC::LockServer
 *
 *  Purpose:
 *      
 *
 *  Arguments:
 *      BOOL            Whether to increment or decrement DLL reference count.
 *
 *  Returns:
 *      HRESULT         S_OK always.
 */
STDMETHODIMP
FRMFAC::LockServer(BOOL fLock)
{
    TraceTag(tagFormFunc,"LockServer (not implemented)");
    return NOERROR;
}

/*
 *  FRMFAC::AddRef
 *
 *  Purpose:
 *      Increments reference count on the form class factory.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG           New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRMFAC::AddRef(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::AddRef ret %d",cRef+1);
    return ++cRef;
}


/*
 *  FRMFAC::Release
 *
 *  Purpose:
 *      Decrements reference count on the form class factory.
 *      If count is decremented to zero, the object is freed.
 *
 *  Arguments:
 *
 *  Returns:
 *      ULONG           New value of reference count.
 */
STDMETHODIMP_(ULONG)
FRMFAC::Release(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::Release cRef %d",cRef);
    if (!(--cRef))
        {
        TraceTag(tagForm,"return 0");
        delete this;
        return 0;
        }
    return cRef;
}


FRMFMR::FRMFMR()
{
    TraceTag(tagFuncTriv,"FRMFMR::FRMFMR");
    clsid = CLSID_MyFormsClsId;
}

FRMFAC::FRMFAC()
{
    TraceTag(tagFuncTriv,"FRMFAC::FRMFAC");
    cRef = 1;
}

FRMFAC::~FRMFAC(void)
{
    TraceTag(tagFuncTriv,"FRMFAC::~FRMFAC");
    AssertSz(!cRef,"0817236");
}
