/*
copyright (c) 1992  Microsoft Corporation

Module Name:

    ddeproxy.cpp

Abstract:

    This module contains the code for the dde proxy (wrapper)

Author:

    Srini  Koppolu   (srinik)    22-June-1992
    Jason  Fuller   (jasonful)  24-July-1992
*/
#include "ddeproxy.h"
#include <tls.h>

DebugOnly (static UINT v_cDdeObjects=0;)
/*
 *  IMPLEMENTATION of CDdeObject
 *
 */

#ifdef OLD
#define UpdateExtent(old,new) do { if ((long)new!=old) {old=(long)new; } } while (0)
#endif

//+---------------------------------------------------------------------------
//
//  Function:   CreateDdeClientHwnd
//
//  Synopsis:   Creates a per thread ClientDde window.
//
//  Effects:    This window is created so we can keep a list of windows that
//		need to be cleaned up in the event the thread dies or OLE32 is
//		unloaded. In the case of DLL unload, we will fault if we don't
//		cleanup this window, since user will dispatch messages to
//		non-existant code. The easy way to track these windows is to
//		make them children of a common per thread window.
//
//		This routine is called by the TLSGetDdeClient() routine to
//		create a window per thread. This window doesn't need to respond
//		to DDE Initiates.
//
//  Arguments:  [void] --
//
//  Returns:    HWND to DdeClientWindow.
//
//  History:    12-10-94   kevinro   Created
//
//----------------------------------------------------------------------------

HWND CreateDdeClientHwnd(void)
{
    return SSCreateWindowExA(0,"STATIC","DdeClientHwnd",WS_DISABLED,
                         0,0,0,0,NULL,NULL,hinstSO,NULL);
}

//+---------------------------------------------------------------------------
//
//  Function:   GetDdeCallControlInterface
//
//  Synopsis:   Gets the thread specific instance of the DDE CallControl
//
//  Effects:    If there is already a per thread CallControl interface,
//		AddRef() it and return the value. Otherwise, get one and
//		set it in the TLS data structure. The TLS structure is
//		a global per thread structure that contains various values.
//
//		Each threads per instance version of the CallControl will
//		be stored in a global place. ReleaseDdeCallControlInterface
//		is used to free up instances of the interface.
//
//  Arguments:  (none)
//
//  Requires:
//
//  Returns:	Per thread instance of ICallControl. NULL if error
//
//  Signals:
//
//  Modifies:
//
//  Algorithm:
//
//  History:    5-13-94   kevinro   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
PCALLCONTROL GetDdeCallControlInterface()
{
    PCALLCONTROL pDdeControl = (PCALLCONTROL)TLSGetDdeCallControl();

    if (pDdeControl != NULL)
    {
	pDdeControl->AddRef();
	return(pDdeControl);
    }
    //
    // There wasn't one in the TLS data. Create one, and return it.
    //
    ORIGINDATA origindata;

    //
    // There is a single channel control (static implementation)
    // for all DDE windows in the process. The hwndCli is set
    // to NULL, since DDE messages are handled specially by
    // the MessageFilter code.
    //
    origindata.pChCont = (PCHANNELCONTROL)&g_CDdeChannelControl;
    origindata.CallOrigin = CALLORIGIN_DDE;
    origindata.hwnd = (HWND)0;
    origindata.wFirstMsg = WM_DDE_FIRST;
    origindata.wLastMsg = WM_DDE_LAST;

    if(CoGetCallControl(&origindata, &pDdeControl) == NOERROR)
    {
	TLSSetDdeCallControl(pDdeControl);
    }
    else
    {
	intrAssert(!"Could not CoGetCallControl");
	pDdeControl = NULL;
    }
    return(pDdeControl);
}

//+---------------------------------------------------------------------------
//
//  Function:   ReleaseDdeCallControlInterface
//
//  Synopsis:   This function will release the per thread CallControl
//		interface. If the Release() returns zero, then the
//		function will set the per thread instance to NULL.
//
//  Effects:
//
//  Arguments:  (none)
//
//  Requires:
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Algorithm:
//
//  History:    5-13-94   kevinro   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
void ReleaseDdeCallControlInterface()
{
    PCALLCONTROL pDdeControl = (PCALLCONTROL)TLSGetDdeCallControl();

    intrAssert(pDdeControl != NULL);
#if DBG == 1
    if (pDdeControl == NULL)
    {
	DebugBreak();
    }
#endif
    if ((pDdeControl != NULL) && (pDdeControl->Release() == 0))
    {
	TLSSetDdeCallControl(NULL);
    }
}


// CreateDdeProxy
//
// This corresponds to ProxyManager::Create in 2.0
//


INTERNAL_ (LPUNKNOWN) CreateDdeProxy
    (IUnknown * pUnkOuter,
    REFCLSID clsid)
{
    LPUNKNOWN punk;
    intrDebugOut((DEB_ITRACE,"CreateDdeProxy(pUnkOuter=%x)\n",pUnkOuter));
    punk = CDdeObject::Create (pUnkOuter, clsid);
    intrDebugOut((DEB_ITRACE,
		  "CreateDdeProxy(pUnkOuter=%x) returns %x\n",
		  pUnkOuter,
		  punk));
    return punk;
}

//+---------------------------------------------------------------------------
//
//  Method:     CDdeObject::Create
//
//  Synopsis:   Creates a CDdeObject
//
//  Effects:
//
//  Arguments:  [pUnkOuter] --  Controlling IUnknown
//      [clsid] --      OLE1 ClassID
//      [ulObjType] --  Object type. Optional: def to OT_EMBEDDED
//      [aTopic] --     Atom of link. Optional: def to NULL
//      [szItem] --     String for link object (def to NULL)
//      [ppdde] --      Output pointer to CDdeObject (def to NULL)
//      [fAllowNullClsid] -- Is NULL clsid OK? Default: false
//
//  Requires:
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Derivation:
//
//  Algorithm:
//
//  History:
//
//  Notes:
//
//----------------------------------------------------------------------------
INTERNAL_(LPUNKNOWN) CDdeObject::Create
    (IUnknown *     pUnkOuter,
    REFCLSID        clsid,
    ULONG           ulObjType,// optional, default OT_EMBEDDED
    ATOM            aTopic,   // optional, only relevant if ulObjType==OT_LINK
    LPOLESTR            szItem,   // optional, only relevant if ulObjType==OT_LINK
    CDdeObject * * ppdde,       // optional, thing created
    BOOL            fAllowNullClsid) // default FALSE
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::Create(%x,ulObjType=%x)\n",
          pUnkOuter,
          ulObjType));

    CDdeObject * pDdeObject;
    static int iTopic=1;  // used to make topic names unique
    WCHAR szTopic[30];
    Assert (ulObjType==OT_LINK || ulObjType==OT_EMBEDDED);

    Assert (ulObjType != OT_LINK || wIsValidAtom(aTopic));
    if (ppdde)
        *ppdde = NULL;

    if (NULL==(pDdeObject = new CDdeObject (pUnkOuter))
        || NULL == pDdeObject->m_pDataAdvHolder
        || NULL == pDdeObject->m_pOleAdvHolder)
    {
        Assert (!"new CDdeObject failed");
        return NULL;
    }

    pDdeObject->m_refs      = 1;
    pDdeObject->m_clsid     = clsid;
    pDdeObject->m_aClass    = wAtomFromCLSID(clsid);

#ifdef OLE1INTEROP

    pDdeObject->m_fOle1interop = TRUE;

#endif

    if (ulObjType==OT_LINK)
    {

        pDdeObject->m_aTopic = wDupAtom (aTopic);
        pDdeObject->m_aItem  = wGlobalAddAtom (szItem);
        // Never close a linked document
        pDdeObject->m_fNoStdCloseDoc = TRUE;
    }
    else
    {
        // This string may actually be visible in the Window Title Bar for a sec.
	InterlockedIncrement((long *)&iTopic);
        wsprintf (szTopic,OLESTR("Embedded Object #%u"), iTopic);
        Assert (lstrlenW(szTopic) < 30);
        pDdeObject->m_aItem = NULL;
        pDdeObject->m_aTopic = wGlobalAddAtom (szTopic);
    }
    pDdeObject->m_bOldSvr   = wIsOldServer (pDdeObject->m_aClass);
    pDdeObject->m_ulObjType = ulObjType;

    // we can only run if we have a MFI
    pDdeObject->m_aExeName = wGetExeNameAtom(clsid);

    intrDebugOut((DEB_ITRACE,
          "::Create(%x,aTopic=%x,aItem=%x,szItem=%ws,aExeName=%x)\n",
          pDdeObject,
          pDdeObject->m_aTopic,
          pDdeObject->m_aItem,
          szItem?szItem:L"<NULL>",
          pDdeObject->m_aExeName));

    if (ppdde)
        *ppdde = pDdeObject;
    return &pDdeObject->m_Unknown;
}



// Constructor
CDdeObject::CDdeObject (IUnknown * pUnkOuter) :
    m_Unknown(this),
    CONSTRUCT_DEBUG
    m_Data(this),
    m_Ole(this),
    m_PersistStg(this),
    m_ProxyMgr(this),
    m_OleItemContainer(this)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::CDdeObject(%x)\n",this));
    if (!pUnkOuter)
        pUnkOuter = &m_Unknown;

    m_pUnkOuter = pUnkOuter;
    m_bRunning  = FALSE;
    m_pOleClientSite = NULL;
    m_pstg = NULL;
    m_pSysChannel = NULL;
    m_pDocChannel = NULL;
    m_bInitNew = NULL;
    m_hNative = NULL;
    m_hPict   = NULL;
    m_hExtra  = NULL;
    m_cfExtra = NULL;
    m_cfPict = 0;
    m_aItem = NULL;
    m_iAdvSave = 0;
    m_iAdvClose = 0;
    m_iAdvChange = 0;
    m_fDidAdvNative = FALSE;
    m_pOleAdvHolder = NULL;
    m_pDataAdvHolder = NULL;
    m_fDidSendOnClose = FALSE;
    m_fNoStdCloseDoc = FALSE;
    m_fDidStdCloseDoc = FALSE;
    m_fDidStdOpenDoc = FALSE;
    m_fDidGetObject = FALSE;
    m_fDidLaunchApp = FALSE;
    m_fUpdateOnSave = TRUE;
    m_fVisible = FALSE;
    m_fWasEverVisible = FALSE;
    m_fCalledOnShow = FALSE;
    m_fGotCloseData = FALSE;
    m_cLocks = 1;               // connections are initially locked
    m_chk = chkDdeObj;
    m_ptd = NULL;
    m_fDoingSendOnDataChange = FALSE;
#ifdef _CHICAGO_
    //Note:POSTPPC
    _DelayDelete = NoDelay;
#endif // _CHICAGO_

    CreateOleAdviseHolder (&m_pOleAdvHolder);
    Assert (m_pOleAdvHolder);
    CreateDataAdviseHolder (&m_pDataAdvHolder);
    Assert (m_pDataAdvHolder);

#ifdef OLD
    m_cxContentExtent = 2000;  // 2 centimeters , totally random default
    m_cyContentExtent = 2000;
#endif

    m_wTerminate = Terminate_None;

    DebugOnly (v_cDdeObjects++;)
    Putsi (v_cDdeObjects);
}



CDdeObject::~CDdeObject
    (void)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::~CDdeObject(%x)\n",this));

    if (m_pDocChannel)
    {
        intrDebugOut((DEB_IWARN , "Abnormal situation: Doc Channel not deleted. Server died?"));
        delete m_pDocChannel;
    }
    if (m_pSysChannel)
    {
        Warn ("Abnormal situation: Sys Channel not deleted. Server died?");
        delete m_pSysChannel;
    }
    if (m_hNative)
    {
        GlobalFree(m_hNative);
    }

    if (m_hPict)
    {
        wFreeData (m_hPict, m_cfPict, TRUE);
    }

    if (m_hExtra)
    {
        wFreeData (m_hExtra, m_cfExtra, TRUE);
    }

    // release all the pointers that we remember

    if (m_pOleClientSite)
    {
        DeclareVisibility (FALSE);
        m_pOleClientSite->Release();
    }

    if (m_pDataAdvHolder)
        m_pDataAdvHolder->Release();

    if (m_pOleAdvHolder)
        m_pOleAdvHolder->Release();

    if (m_pstg)
        m_pstg->Release();

    if (m_aExeName)
        GlobalDeleteAtom (m_aExeName);

    if (m_aClass)
        GlobalDeleteAtom (m_aClass);

    if (m_aTopic)
        GlobalDeleteAtom (m_aTopic);

    if (m_aItem)
        GlobalDeleteAtom (m_aItem);

    if (m_ptd)
        delete m_ptd;

    m_chk = 0;
    DebugOnly (v_cDdeObjects--;)
    Putsi (v_cDdeObjects);
}




//  Handles WM_DDE_ACKs received while in initiate state. If this is the first
//  reply, save its window handle. If multiple replies are received, take the
//  one with the prefered instance, if there is one. Keep a count of
//  WM_DDE_TERMINATEs we send so that we don't shut the window until we get
//  all of the responses for  WM_DDE_TERMINATEs.


INTERNAL_(void) CDdeObject::OnInitAck (LPDDE_CHANNEL pChannel, HWND hwndSvr)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::OnInitAck(%x,hwndSvr=%x)\n",this,hwndSvr));
#ifdef _MAC
#else
    if (!IsWindow (hwndSvr))
    {
        Assert (0);
        return;
    }
    if (pChannel->hwndSvr) { // if we already have a handle
	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::OnInitAck(%x,hwndSvr=%x) Already have hwndSvr=%x\n",
		      this,
		      hwndSvr,
		      pChannel->hwndSvr));
        // just take the very first one. Direct post is OK
	MPostWM_DDE_TERMINATE(hwndSvr,pChannel->hwndCli);
        // Expect an extra WM_DDE_TERMINATE
        ++pChannel->iExtraTerms;
    } else {
        // this is the server we want
        pChannel->hwndSvr = hwndSvr;
        pChannel->iExtraTerms = NULL;

	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::OnInitAck(%x,hwndSvr=%x) Established Connection\n",
		      this,
		      hwndSvr,
		      pChannel->hwndSvr));
    }
#endif _MAC
}

INTERNAL_(BOOL) CDdeObject::OnAck (LPDDE_CHANNEL pChannel, LONG lParam)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::OnAck(%x,lParam=%x)\n",this,lParam));

    BOOL    retval = TRUE;
    ATOM    aItem;
    WORD    wStatus;
    HANDLE  hData;

    if( pChannel->iAwaitAck == AA_EXECUTE)
    {
       wStatus = GET_WM_DDE_EXECACK_STATUS( NULL, lParam );
       hData   = GET_WM_DDE_EXECACK_HDATA( NULL, lParam );
    }
    else
    {
       wStatus = GET_WM_DDE_ACK_STATUS( NULL, lParam );
       aItem = GET_WM_DDE_ACK_ITEM( NULL, lParam );
    }


    // check for busy bit
    if (wStatus & 0x4000)
    {
        // we got busy from the server.
        pChannel->fRejected = TRUE;
        // tell the wait loop that we got a busy ack
        //CoSetAckState(pChannel->pCI , FALSE,TRUE, SERVERCALLEX_RETRYLATER);
	intrDebugOut((DEB_ITRACE,"::OnAck(%x) Busy SetCallState(SERVERCALLEX_RETRYLATER)\n",this));
        pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_RETRYLATER, NOERROR);
        return TRUE;
    }

    // just reset the flag always
    m_wTerminate = Terminate_None;

    intrDebugOut((DEB_ITRACE,
		  "::OnAck(%x)aItem=%x(%ws) wStatus=\n",
		  this,
		  aItem,
		  wAtomName(aItem),
		  wStatus));

    if (pChannel->iAwaitAck == AA_EXECUTE)
    {
        GlobalFree (hData);
        pChannel->hCommands = NULL;
    }
    else
    {
        if (hData)
            GlobalDeleteAtom ((ATOM)hData);
    }


    // even if the client got terminate we have to go thru this path.

    if (pChannel->wTimer) {
        KillTimer (pChannel->hwndCli, 1);
        pChannel->wTimer = 0;
    }


    if (pChannel->iAwaitAck == AA_POKE)
        // We have to free the data first. OnAck can trigger
        // another Poke (like pokehostnames)
        wFreePokeData (pChannel, (m_bOldSvr && m_aClass==aMSDraw));


    if (!(wStatus & POSITIVE_ACK))
    {
	intrDebugOut((DEB_ITRACE,"::OnAck(%x) OnAck got an ack with fAck==FALSE.\n",this));

        // A negative ack is OK when doing a temporary advise from
        // IsFormatAvailable().   Also, apps don't seem to positively
        // ack all unadvises.

        // review: johannp : this is the case were have to inform the reply rejected call

        retval = FALSE;
        // we got the ack and can leave the wait loop
        //CoSetAckState(pChannel->pCI, FALSE);

	intrDebugOut((DEB_ITRACE,
		      "::OnAck(%x) ***_ NACK _*** SetCallState(ISHANDLED,RPC_E_DDE_NACK)\n",
		      this));

        pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_ISHANDLED, RPC_E_DDE_NACK);

        // MSDraw frees hOptions even on a NACK, despite official DDE rules.
        if (pChannel->iAwaitAck == AA_ADVISE && m_clsid != CLSID_MSDraw)
           GlobalFree (pChannel->hopt);
    }
    else
    {
        // we got the ack and can leave the wait loop
        // CoSetAckState(pChannel->pCI, FALSE);
	intrDebugOut((DEB_ITRACE,
		      "::OnAck(%x) POSITIVE_ACK SetCallState(SERVERCALLEX_ISHANDLED)\n",
		      this));
        pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_ISHANDLED, NOERROR);
    }

    pChannel->hopt = NULL;
    pChannel->iAwaitAck = NULL;
    return retval;

}





INTERNAL_(void) CDdeObject::OnTimer (LPDDE_CHANNEL pChannel)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::OnTimer(%x)\n",this));
    // Since there is only one timer for each client, just
    // repost the message and delete the timer.
#ifdef _MAC
#else
    KillTimer (pChannel->hwndCli, 1);
    pChannel->wTimer = 0;

    if (wPostMessageToServer(pChannel, pChannel->wMsg, pChannel->lParam,FALSE))
        return ;

    // Postmessage failed. We need to getback to the main stream of
    // commands for the object.
    OnAck (pChannel, pChannel->lParam);
#endif _MAC
}



// Called when we get a WM_DDE_DATA message in reponse to
// a DDE_REQUEST we sent to check if a format is available.
//
INTERNAL CDdeObject::OnDataAvailable
    (LPDDE_CHANNEL  pChannel,
    HANDLE          hDdeData,
    ATOM            aItem)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::OnDataAvailable(%x)\n",this));
    CLIPFORMAT cf;
    Assert (AA_REQUESTAVAILABLE == pChannel->iAwaitAck);
    intrAssert( wIsValidAtom(aItem));

    DDEDATA * pDdeData = (DDEDATA *) GlobalLock (hDdeData);
    RetZS (pDdeData, E_OUTOFMEMORY);
    if (!pDdeData->fAckReq && aItem)
    {
            GlobalDeleteAtom (aItem);
    }
    cf = pDdeData->cfFormat;
    GlobalUnlock (hDdeData);
    wFreeData (wHandleFromDdeData (hDdeData), cf);
    return NOERROR;
}



// Called for WM_DDE_DATA message. If data is from an ADVISE-ON-CLOSE and this
// is there are no more outstanding ADVISE-ON-CLOSE requests, close the
// document and end the conversation.


//+---------------------------------------------------------------------------
//
//  Method:     CDdeObject::OnData
//
//  Synopsis:   Called when a WM_DDE_DATA message is recieved. If the data
//		is from an ADVISE_ON_CLOSE, and there are no more
//		outstanding ADVISE_ON_CLOSE request, close the document
//		and end the conversation.
//
//  Effects:    The effects of this routine are complex.
//
//  Wow! What else can be said. This routine does alot of stuff in response
//  to an incoming WM_DDE_DATA message. There are basically two flavors of
//  response here. First is when we were expecting to get this result,
//  in which case we know what we wanted to do with the data. Second is
//  when the data just arrives, but we didn't expect it. These cases could
//  indicate that the server is shutting down.
//
//
//  Arguments:  [pChannel] -- The DDE channel recieving the message
//		[hDdeData] -- Handle to the data
//		[aItem] -- Atom to the item
//
//  Requires:
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Derivation:
//
//  Algorithm:
//
//  History:    5-16-94   kevinro   Restructured and commented
//
//  Notes:
//
//
//	Be extra careful you change this routine.
//	This is especially neccesary if you are	going to exit early. The
//	way that hDdeData is free'd or kept should be understood before
//	changing.
//
//
//----------------------------------------------------------------------------
INTERNAL CDdeObject::OnData
    (LPDDE_CHANNEL  pChannel,
    HANDLE          hDdeData,
    ATOM            aItem)
{
    intrDebugOut((DEB_ITRACE,
          "CDdeObject::OnData(%x,pChannel=%x,hDdeData=%x,aItem=%x(%s)iAwaitAck=%x\n",
          this,
	  pChannel,
          hDdeData,
          aItem,
          wAtomNameA(aItem),
	  pChannel->iAwaitAck));
#ifdef _MAC
#else
    DDEDATA * lpDdeData = NULL;
    BOOL         fAck = TRUE;	
    int          iAdvOpt;
    BOOL         fCallBack;
    HRESULT      hresult = NOERROR;

    ICallControl *lpCallCont = pChannel->pCallCont;
    intrAssert(lpCallCont != NULL);
    BOOL fRequested = FALSE;

    intrAssert(wIsValidAtom(aItem));

    int iAwaitAck = pChannel->iAwaitAck;

    //
    // If we were waiting for this data, then we are sitting in the
    // modal loop. Set the call state on the call control interface
    // to indicate that a response was recieved. Pass NOERROR to indicate
    // that there was success. If an error is determined later, then
    // the state will be set a second time.
    //

    if ((AA_REQUEST == iAwaitAck) || (AA_REQUESTAVAILABLE == iAwaitAck))
    {
	intrDebugOut((DEB_ITRACE,
		      "::OnData(%x) AA_REQUEST/AVAILABLE pCD->id=%x\n",
		      this,
		      pChannel->pCD?pChannel->pCD->id:0xBAD));

	//
	// Regardless of the outcome of this call, we have recieved a
	// response. Set the Awaiting Ack state to nothing.
	//
        pChannel->iAwaitAck = AA_NONE;

	//
	// Determine if this channels call data is valid, and is in use.
	// If the channel is in use, then then its id will not be
	// CALLDATAID_UNUSED. If the call data isn't valid, then something
	// is wrong, but there isn't a good way to recover.
	//

        if (pChannel->pCD  && (pChannel->pCD->id != CALLDATAID_UNUSED))
	{
	    // call only if there is a call id
            //CoSetAckState(pChannel->pCI, FALSE); // clear waiting flag
	    intrDebugOut((DEB_ITRACE,"::OnData(%x) SetCallState(SERVERCALLEX_ISHANDLED)\n",this));
            pChannel->pCallCont->SetCallState(pChannel->pCD,
					      SERVERCALLEX_ISHANDLED,
					      NOERROR);
	}
    }


    //
    // Check the string for aItem, looking for advise options. The variable
    // iAdvOpt will be set to indicate what type of data we just got, such
    // as ON_CHANGE, etc. If the option is invalid, then we won't know
    // what to do with it.
    //

    if ((hresult=wScanItemOptions (aItem, (int *) &iAdvOpt)) != NOERROR)
    {
        intrAssert(!"Item found with unknown advise option\n");
	LPARAM lp;
        if(!wPostMessageToServer (pChannel,
                          WM_DDE_ACK,
                          lp = MAKE_DDE_LPARAM (WM_DDE_ACK,NEGATIVE_ACK, aItem),TRUE))
	{
	    hresult = RPC_E_SERVER_DIED;

	}

	//
	// Did we need to free hDdeData here? No, according to the DDE spec, if the
	// receiever responds with a NACK, then the sender is responsible for
	// freeing the data.
	//
	return hresult;
    }

    //
    // If the server sent no data, there ain't much we can do about it.
    //

    if (hDdeData == NULL)
    {
	intrDebugOut((DEB_IERROR,
		      "::OnData(%x)hDdeData is NULL!\n",
		      this));

	return(RPC_E_INVALID_PARAMETER);
    }

    //
    // Lock the data into memory so we can use it. Be careful, the way
    // this routine was written originally, there are places that free
    // and realloc hDdeData. Specifically, the call to KeepData. Carefully
    // evaluate each place where you are returning, to insure the memory
    // isn't leaked. (if you have time, please restructure this routine
    // so it is easier to understand.
    //
    if (!(lpDdeData = (DDEDATA FAR *) GlobalLock(hDdeData)))
    {
	intrDebugOut((DEB_IERROR,
		      "::OnData(%x)GlobalLock on lpDdeData failed\n",
		      this));
	//
	// BUGBUG: (KevinRo)Did we need to free hDdeData here? I think
	// we should have if the fRelease flag was set. The old code
	// didn't. Need to research this further (ie you figure it out!)
	//
        return ResultFromScode (E_OUTOFMEMORY);
    }

    intrDebugOut((INTR_DDE,
    	      "::OnData(%x) lpDdeData->cfFormat=%x\n",
    	      this,
    	      (UINT)lpDdeData->cfFormat));

    //
    // The server will set fAckReq if it wants a response.
    // don't call HIC for call where not acknoewledge is requested
    //
    fAck = lpDdeData->fAckReq;

    if (pChannel->bTerminating) {
        intrDebugOut((INTR_DDE,"::OnData(%x) Got DDE_DATA in terminate sequence\n",this));
	//
	// BUGBUG: this is very dangerous since the pointer on the
	//	hDocWnd does not get deleted and a further will
	//	DDE message will GPF - we need to fix this!!!
	//
        GlobalUnlock (hDdeData);
        GlobalFree (hDdeData);
	goto exitRtn;
    }

    //
    // (KevinRo) Found this comment:
    //
    //	important that we post the acknowledge first. Otherwise the
    // 	messages are not in sync.
    //
    // The above comment might be intended to mean that the acknowledge needs to be
    // send now, because we may call one of the advise functions below, which in
    // turn may send another message to the OLE 1.0 server. Therefore, we ACK now,
    // so the messages to the OLE 1.0 server are in the correct order.
    //
    if (fAck)
    {
        LPARAM lp;
        if(!wPostMessageToServer (pChannel,
                          WM_DDE_ACK,
                          lp=MAKE_DDE_LPARAM(WM_DDE_ACK,POSITIVE_ACK, aItem),TRUE))
        {
	    return(RPC_E_SERVER_DIED);
        }
    }

    //
    // this call is now an async call and can not be rejected be HandleIncomingMessage
    //

    if ((AA_REQUESTAVAILABLE == pChannel->iAwaitAck) && (lpDdeData->fResponse))
    {
	//
	// For some reasons, OnDataAvailable will be the one to delete this data.
	// I don't understand it, but lets roll with it. (KevinRo)
	//
        GlobalUnlock (hDdeData);
        return OnDataAvailable (pChannel, hDdeData, aItem);
    }

    //
    // If the clipboard format is binary, and the topic is aStdDocName, then this
    // OnData is a RENAME
    //
    if (lpDdeData->cfFormat == (short)g_cfBinary && aItem== aStdDocName)
    {
	// ON_RENAME
	//
	// The data should be the new name, in ANSI.
	//
	ChangeTopic ((LPSTR)lpDdeData->Value);
	GlobalUnlock (hDdeData);
	GlobalFree (hDdeData);
	return(NOERROR);
    }

    //
    // Based on iAdvOpt, determine if we can callback. This one is a little
    // hard to understand. I don't either. CanCallBack appears to return
    // true if the count is 0,1, or 3, but returns FALSE if its 2 or
    // greater than 3. There are no comments in the old code as to why
    // this is. I am leaving it, since it must have been put there for
    // a reason. See CanCallBack in ddeworker.cxx for futher (ie no) details
    //
    switch (iAdvOpt)
    {
        case ON_SAVE:
            fCallBack = CanCallBack(&m_iAdvSave);
		intrDebugOut((INTR_DDE,
			      "::OnData(%x)ON_SAVE m_iAdvSave=%x\n",
			      this,
			      m_iAdvSave));

            break;
        case ON_CLOSE:
            fCallBack = CanCallBack(&m_iAdvClose);
		intrDebugOut((INTR_DDE,
			      "::OnData(%x)ON_CLOSE m_iAdvClose=%x\n",
			      this,
			      m_iAdvClose));
            break;
        case ON_CHANGE:
            fCallBack = TRUE;
		intrDebugOut((INTR_DDE,
			      "::OnData(%x)ON_CHANGE m_iAdvClose=%x\n",
			      this,
			      m_iAdvClose));
            break;
	default:
	    intrAssert( !"Unknown iAdvOpt: Somethings really broke");
    }

    // Keep the data in a cache for a future GetData call
    // which may be triggered a few lines later by the
    // SendOnDataChange().

    fRequested = lpDdeData->fResponse;


    // The call  to KeepData will change hDdeData and
    // invalidate lpDdeData. Check out KeepData for details. The net
    // result is that hDdeData is no longer valid

    GlobalUnlock (hDdeData);
    lpDdeData=NULL;

    hresult = KeepData (pChannel, hDdeData);

    //
    // This is unpleasant, but if KeepData fails, we need to
    // call SetCallState again, resetting the error code. This
    // code is such a mess that rearranging it to do
    // it in a rational way is going to be too much work given
    // the amount of time I have until shipping.
    //
    // If you have time, please simplify this code. Thanks
    //
    if (hresult != NOERROR)
    {
	//
	// At this point, hDdeData has been unlocked, and deleted by
	// the KeepData routine. Therefore, the return here doesn't
	// need to be concerned with cleaning up after hDdeData
	//
	intrDebugOut((DEB_ITRACE,
		      "::OnData(%x) KeepData failed %x\n",
		      this,
		      hresult));
        //
	// Reset the error code on the call control
	//
	if ((AA_REQUEST == iAwaitAck) || (AA_REQUESTAVAILABLE == iAwaitAck))
	{
	    if (pChannel->pCD && (pChannel->pCD->id != CALLDATAID_UNUSED))
	    {
		pChannel->pCallCont->SetCallState(pChannel->pCD,
						  SERVERCALLEX_ISHANDLED,
						  hresult);
	    }
	}
	goto exitRtn;
    }

    if (fRequested)
    {
        // We REQUESTed the data. So, we are no longer waiting.
        // Do NOT call SendOnDataChange because the data hasn't
        // really changed again, we just requested it to satisfy
        // a call to GetData, which was probably called by the
        // real SendOnDataChange.
	intrDebugOut((INTR_DDE,
		      "::OnData(%x) fRequested DATA\n",
		      this));

        iAwaitAck = NULL;
	hresult = NOERROR;
	goto exitRtn;

    }

    //
    // Now we have decided this is data we had not asked for. This makes
    // it a change/close/saved notificiation.
    //
    intrDebugOut((INTR_DDE,"::OnData(%x) Non requested DATA\n",this));
    pChannel->AddReference();
    if (fCallBack && iAdvOpt != ON_CHANGE)
    {
	// ON_CHANGE will be handled by OleCallback, below

	intrDebugOut((INTR_DDE,
		      "::OnData(%x)Dispatching SendOnDataChange\n",
		      this));


	//
	// There are a couple of things to note about the following. First,
	// the iid of the call doesn't matter. Since OLE 1.0 servers don't
	// do nested calls, the original LID (Logical ID) can be any random
	// value. Therefore, we don't initalize it.
	//
	// According to JohannP, the calltype of these calls is supposed
	// to be CALLTYPE_SYNC. I don't fully understand why they are.
	// I am taking is decision on faith.
	//
	// Using the new call control interfaces, we do the following.
	//
	IID iid;

	DDEDISPATCHDATA ddedispdata;
	DISPATCHDATA dispatchdata;
	INTERFACEINFO32 ifInfo;

	ifInfo.pUnk = m_pDataAdvHolder;
	ifInfo.iid = IID_IDataAdviseHolder;
	//
	// We are about to call method #6 in the interface,
	// which is SendOnDataChange
	//
	ifInfo.wMethod = 6;
	ifInfo.callcat = CALLCAT_SYNCHRONOUS;

	//dispatchdata.scode = S_OK;
	dispatchdata.pData = (LPVOID) &ddedispdata;

	ddedispdata.pCDdeObject = this;
	ddedispdata.wDispFunc = DDE_DISP_SENDONDATACHANGE;
	ddedispdata.iArg = iAdvOpt;

	lpCallCont->HandleDispatchCall((DWORD)GetWindowTask(pChannel->hwndSvr),
				       iid,
				       &ifInfo,
				       &dispatchdata);

    }
    if (fCallBack && (pChannel->pCallCont != NULL))
    {
	// in 1.0 ON_CLOSE comes with data
	if (iAdvOpt==ON_CLOSE)
	{

	    intrDebugOut((INTR_DDE,
			  "::OnData(%x) iAdvOpt == ON_CLOSE, send ON_SAVE\n",
			  this));

	    m_fGotCloseData = TRUE;

	    hresult = OleCallBack(ON_SAVE,pChannel);
            if (hresult != NOERROR)
	    {
		goto errRel;
	    }

	    //ErrRtnH (DdeHandleIncomingCall(pChannel->hwndSvr, CALLTYPE_TOPLEVEL) );
	    //ErrRtnH (OleCallBack (ON_SAVE));
        }

	// check if app can handle this call
	// we do not need to call HIC for SendOnClose

	hresult = OleCallBack (iAdvOpt,pChannel);
    }

errRel:
    // Don't use pChannel after this. It can get deleted. (srinik)
    if (pChannel->ReleaseReference() == 0)
    {
	m_pDocChannel = NULL;
    }

exitRtn:
    if (!fAck && aItem)
    {
	GlobalDeleteAtom (aItem);
    }
  return hresult;
#endif _MAC
}

//+---------------------------------------------------------------------------
//
//  Method:     CDdeObject::OleCallBack
//
//  Synopsis:   Send all the right notifications whan a Save or Close happens.
//
//  Effects:    OleCallBack is a double duty function. It is called in two
//		different cases.
//
//		First, is to setup the callback, and call HandleIncomingCall.
//		Second is from DispatchCall() in the CDdeChannelControl.
//
//		The reason for doing it this way is we localize the setup
//		and processing of these calls to one routine. Therefore,
//		we can go to one spot in the code to find all of the call
//		back information.
//
//  Arguments:  [iAdvOpt] -- Which Advise operation to perform
//		[pChannel] -- Which channel is being called back
//
//  Requires:   pChannel == NULL, and the AdviseHolders are called.
//		pChannel != NULL, and the call is setup, and HandleIncomingCall
//			    is setup.
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Derivation:
//
//  Algorithm:
//
//  History:    5-23-94   kevinro   Created
//
//  Notes:
//
//  WARNING: This code sucks completely. One of the major problems you need
//  to know about is that the CDdeObject may go away as part of the normal
//  processing of some of the below. Be very careful about any processing
//  that might occur after an ON_CLOSE
//
//----------------------------------------------------------------------------
INTERNAL CDdeObject::OleCallBack (int iAdvOpt, LPDDE_CHANNEL pChannel)
{
    HRESULT hresult = NOERROR;
    IID iid;
    DDEDISPATCHDATA ddedispdata;
    DISPATCHDATA dispatchdata;
    INTERFACEINFO32 ifInfo;
    ICallControl *lpCallCont = NULL;

    //
    // If the channel isn't NULL, then setup the data structures for calling
    // off to the call control.
    //
    if (pChannel != NULL)
    {
	lpCallCont = pChannel->pCallCont;

	if (lpCallCont == NULL)
	{
	    return(E_UNEXPECTED);
	}
	//
	// Only do this work if we really have to
	//

	dispatchdata.scode = S_OK;
	dispatchdata.pData = (LPVOID) &ddedispdata;
	ddedispdata.pCDdeObject = this;
	ddedispdata.wDispFunc = DDE_DISP_OLECALLBACK;
	ddedispdata.iArg = iAdvOpt;

    }

    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::OleCallBack(%x,iAdvOpt=%x,pChannel=%x,lpCallCont=%x)\n",
		  this,
		  iAdvOpt,
		  pChannel,
		  lpCallCont));

    //
    // Determine what needs to be done, based on the iAdvOpt. This should be
    // one of the handled cases below, otherwise its an error.
    //
    switch (iAdvOpt)
    {
    case ON_CLOSE:
	if (pChannel != NULL)
	{
	    intrDebugOut((DEB_ITRACE,
			  "::OleCallBack(%x) setup for ON_CLOSE\n",
			  this));

	    ifInfo.pUnk = m_pOleAdvHolder;
	    ifInfo.iid = IID_IOleAdviseHolder;
	    // IOleAdviseHolder::SendOnClose is method 8
	    ifInfo.wMethod = 8;
	    ifInfo.callcat = CALLCAT_SYNCHRONOUS;
	}
	else
	{
	    intrDebugOut((DEB_ITRACE,"::OleCallBack(%x) ON_CLOSE\n",this));
            DeclareVisibility (FALSE);
            RetZ (!m_fDidSendOnClose); // This SendOnClose should happen 1st
                                      // Don't let OnTerminate() do it too
            hresult = SendOnClose();

	    //
	    // WARNING WARNING WARNING: SendOnClose() may have caused the
	    // destruction of this CDdeObject. Touch nothing on the way
	    // out. Actually, if you have time, which I currently don't,
	    // see what you can do with reference counting tricks to
	    // insure this object doesn't die during this callback.
	    // Its a tricky problem, and we are shipping in 2 weeks.
	    // (KevinRo 8/6/94)
	    //
	}
        break;

    case ON_SAVE:
	if (pChannel != NULL)
	{
	    intrDebugOut((DEB_ITRACE,
			  "::OleCallBack(%x) setup for ON_SAVE\n",
			  this));

	    if (m_pOleClientSite == NULL)
	    {
		ifInfo.pUnk = m_pOleClientSite;
		ifInfo.iid = IID_IOleClientSite;
		// IOleClientSite::SaveObject method 7
		ifInfo.wMethod = 7;
		ifInfo.callcat = CALLCAT_SYNCHRONOUS;
	    }
	    else
	    {
		// Going to call the IOleAdviseHolder

		ifInfo.pUnk = m_pOleAdvHolder;
		ifInfo.iid = IID_IOleAdviseHolder;
		// IOleAdviseHolder::SendOnSave method 7
		// (Yes, same ordinal as above, I double checked)
		ifInfo.wMethod = 7;
		ifInfo.callcat = CALLCAT_SYNCHRONOUS;
	    }
	}
	else
	{

	    intrDebugOut((DEB_ITRACE,"::OleCallBack(%x) ON_SAVE\n",this));
            if (m_pOleClientSite)
            {
                // We just got data from the server, so we don't want to
                // ask him for it again when the container does a save.
                m_fUpdateOnSave = FALSE;
                m_pOleClientSite->SaveObject();
                // SendOnSave is called in PS::SaveCompleted
                m_fUpdateOnSave = TRUE;
            }
            else
            {
                // Link case
                RetZS (m_pOleAdvHolder, E_OUTOFMEMORY);
                m_pOleAdvHolder->SendOnSave();
            }
	}
        break;

    case ON_CHANGE:
	if (pChannel != NULL)
	{
		// Going to call the IDataAdviseHolder

		ifInfo.pUnk = m_pDataAdvHolder;
		ifInfo.iid = IID_IDataAdviseHolder;
		// IDataAdviseHolder::SendOnDataChange method 6
		ifInfo.wMethod = 6;
		ifInfo.callcat = CALLCAT_SYNCHRONOUS;

	}
	else
	{
            RetZS (m_pDataAdvHolder, E_OUTOFMEMORY);
	    intrDebugOut((DEB_ITRACE,"::OleCallBack(%x) ON_CHANGE\n",this));
            hresult = SendOnDataChange (ON_CHANGE);
	}
	break;

    default:


	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::OleCallBack(%x,iAdvOpt=%x) UNKNOWN iAdvOpt\n",
		      this,
		      iAdvOpt));
	intrAssert(!"Unexpected iAdvOpt");
	return(E_UNEXPECTED);
        break;
    }

    //
    // There are a couple of things to note about the following. First,
    // the iid of the call doesn't matter. Since OLE 1.0 servers don't
    // do nested calls, the original LID (Logical ID) can be any random
    // value. Therefore, we don't initalize it.
    //
    // According to JohannP, the calltype of these calls is supposed
    // to be CALLTYPE_SYNCHRONOUS. I don't fully understand why they are.
    // I am taking is decision on faith.
    //
    //
    // Its possible that during the handling of this call that this object
    // will get deleted. This is a pain in the butt. This means that anything
    // used after this call MUST be protected. lpCallCont happens to be one of
    // these. We have been having problems with lpCallCont being released as
    // part of the object cleanup. The call control code will access member
    // variables on its way out of the HandleDispatch. We need to bracket
    // the call below so this doesn't happen.
    //
    if (pChannel != NULL)
    {
        //
	// We normally keep a per thread copy of the call control interface.
	// When the reference count for that copy goes to zero, we need to
	// flush that copy. We can do the addref here, because the current
	// reference is the same value that would be returned by
	// GetDdeCallControlInterface. The balancing call, however, needs
	// to be ReleaseDdeCallControlInterface, since it may need to flush
	// the TLS value.
	//

	lpCallCont->AddRef();

	hresult = lpCallCont->HandleDispatchCall((DWORD)GetWindowTask(pChannel->hwndSvr),
				       iid,
				       &ifInfo,
				       &dispatchdata);

	ReleaseDdeCallControlInterface();
    }

    return hresult;
}


INTERNAL CDdeObject::SendOnDataChange
    (int iAdvOpt)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::SendOnDataChange(%x)\n",this));
    HRESULT hresult;
    RetZS (m_pDataAdvHolder, E_OUTOFMEMORY);
    m_fDoingSendOnDataChange = TRUE;
    hresult = m_pDataAdvHolder->SendOnDataChange (&m_Data,
						  DVASPECT_CONTENT,
						  0);
    if (ON_CLOSE==iAdvOpt)
    {
        hresult = m_pDataAdvHolder->SendOnDataChange (&m_Data,
						      DVASPECT_CONTENT,
						      ADVF_DATAONSTOP);
    }
    m_fDoingSendOnDataChange = FALSE;
    return hresult;
}




INTERNAL CDdeObject::SendOnClose
    (void)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::SendOnClose(%x)\n",this));
    RetZS (m_pOleAdvHolder, E_OUTOFMEMORY);
    m_fDidSendOnClose = TRUE;
    RetErr (m_pOleAdvHolder->SendOnClose() );
    return NOERROR;
}




INTERNAL CDdeObject::OnTerminate
    (LPDDE_CHANNEL pChannel,
    HWND hwndPost)
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::OnTerminate(%x,pChannel=%x,hwndPost=%x)\n",
		  this,
		  pChannel,
		  hwndPost));

    //
    // If the hwndPost and hwndSvr are different, then it is one of two
    // cases. We could have recieved more than one Acknowlege during our
    // initiate, in which case the count iExtraTerms would have been
    // incremented, and this terminate is accounted for iExtraTerms.
    //
    // The other case is that we were terminated by a window that was
    // NOT the window we were conversing with.
    //
    if (pChannel->hwndSvr != hwndPost)
    {
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) Extra terms is 0x%x \n",
		      this,
		      pChannel->iExtraTerms));

	//
	// iExtraTerms shouldn't go below zero. If it does, something
	// has gone wrong. We have been seeing some problems with the
	// HWND mapping layer in the past. If the following condition
	// ever trips, then it is possible that another mapping
	// problem has been seen.
	//
#if DBG == 1
	if((pChannel->iExtraTerms == 0 ) &&
           (((DWORD)pChannel->hwndSvr) & (0xffff)) == ((DWORD)hwndPost & (0xffff)))
	{
	    intrDebugOut((DEB_ERROR,
			  "*** OnTerminate expected hwnd=%x got hwnd=%x ***\n",
			  pChannel->hwndSvr,hwndPost));

	    intrDebugOut((DEB_ERROR,
			  "\n*** Call KevinRo or SanfordS ***\n\n",
			  pChannel->hwndSvr,hwndPost));

	}
#endif
        --pChannel->iExtraTerms;

        intrAssert((pChannel->iExtraTerms >= 0) && "Call KevinRo or SanfordS");
        return NOERROR;
    }
    if (m_wTerminate == Terminate_Detect) {
        // we should only detect the call but not execute the code
        // set the state to Received
        m_wTerminate = Terminate_Received;
        pChannel->iAwaitAck = NULL;
        // Since Excel incorrectly did not send an ACK, we need to
        // delete the handle in the DDE message ourselves.
        if (pChannel->hCommands)
        {
            GlobalFree (pChannel->hCommands);
            pChannel->hCommands = NULL;
        }
        //CoSetAckState(pChannel->pCI, FALSE);
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) Terminate_Detect SERVERCALLEX_ISHANDLED\n",
		      this));
        pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_ISHANDLED, RPC_E_SERVER_DIED);
        return NOERROR;
    }

    RetZ (pChannel);
    ChkDR (this);

    if (!pChannel->bTerminating)
    {
        // Got unprompted terminate
        BOOL    bBusy;

        // Necessary safety bracket
        m_pUnkOuter->AddRef();

        bBusy = wClearWaitState (pChannel);

        if (pChannel->iAwaitAck || bBusy)
        {
            pChannel->iAwaitAck = NULL;
            //CoSetAckState(pChannel->pCI, FALSE);
	    intrDebugOut((DEB_ITRACE,"::OnTerminate(%x) !bTerminating SERVERCALLEX_ISHANDLED,RPC_E_DDE_UNEXP_MSG\n",this));
            pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_ISHANDLED, RPC_E_DDE_UNEXP_MSG);
        }

        if (!m_fDidSendOnClose)
        {
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) SendOnClose from terminate\n",
		      this));

            BOOL f= m_fNoStdCloseDoc;
            m_fNoStdCloseDoc = TRUE;

            DeclareVisibility (FALSE);
            SendOnClose();

            m_fNoStdCloseDoc = f;
        }
        else
        {
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) Already did SendOnClose\n",
		      this));
            Puts ("Already did SendOnClose\n");
        }
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) Posting DDE_TERMINATE as reply\n",
		      this));

        wPostMessageToServer (pChannel, WM_DDE_TERMINATE, NULL,FALSE);

        // The terminate that we are sending itself is a reply, so we don't
        // need to do WaitForReply.
        DeleteChannel (pChannel);

        // Necessary safety bracket
        m_pUnkOuter->Release();
    }
    else
    {
	intrDebugOut((DEB_ITRACE,
		      "::OnTerminate(%x) Received DDE_TERMINATE in reply\n",
		      this));

        // We sent the WM_DDE_TERMINATE and we got the acknowledge for it
        pChannel->hwndSvr = NULL;
        pChannel->iExtraTerms == NULL;
        pChannel->iAwaitAck = NULL;
        //CoSetAckState(pChannel->pCI, FALSE);
	intrDebugOut((DEB_ITRACE,"::OnTerminate(%x) bTerminating SERVERCALLEX_ISHANDLED\n",this));
        pChannel->pCallCont->SetCallState(pChannel->pCD, SERVERCALLEX_ISHANDLED, NOERROR);
    }
    Puts ("OnTerminate() done.\n");
    return NOERROR;
}




INTERNAL_(BOOL) CDdeObject::AllocDdeChannel
    (LPDDE_CHANNEL * lplpChannel, LPSTR lpszWndClass)
{
    intrDebugOut((DEB_ITRACE,
          "CDdeObject::AllocDdeChannel(%x,WndClass=%s)\n",
          this,
          lpszWndClass));

    //
    // Try to get the global call control interface
    //

    PCALLCONTROL pCallCont = GetDdeCallControlInterface();

    if (pCallCont == NULL)
    {
	//
	// Couldn't allocate a CallControl. Fail.
	//

	intrAssert(pCallCont != NULL);
	return(FALSE);
    }

    //
    // Now try to allocate a channel
    //

    if (!(*lplpChannel =  (LPDDE_CHANNEL) new DDE_CHANNEL ))
    {
	//
	// This failed, so give back the CallControl
	//
	intrAssert(*lplpChannel != NULL);
	ReleaseDdeCallControlInterface();
        return FALSE;
    }

    (*lplpChannel)->m_cRefs = 1;
    (*lplpChannel)->hwndSvr         = NULL;
    (*lplpChannel)->bTerminating    = FALSE;
    (*lplpChannel)->wTimer          = NULL;
    (*lplpChannel)->hDdePoke        = NULL;
    (*lplpChannel)->hCommands       = NULL;
    (*lplpChannel)->hopt            = NULL;
    (*lplpChannel)->dwStartTickCount= 0;
    (*lplpChannel)->msgFirst        = 0;
    (*lplpChannel)->msgLast         = 0;
    (*lplpChannel)->fRejected       = FALSE;
    (*lplpChannel)->wChannelDeleted = 0;
    //(*lplpChannel)->pCI             = NULL;
    (*lplpChannel)->pCD             = NULL;
    (*lplpChannel)->pCallCont = pCallCont;
    (*lplpChannel)->pChanCont = &g_CDdeChannelControl;

    if (!((*lplpChannel)->hwndCli = SSCreateWindowExA(0,
						       lpszWndClass,
						       "DDE Channel",
						       WS_CHILD,
						       0,0,0,0,
						       (HWND)TLSGetDdeClientWindow(),
						       NULL,
						       hinstSO,
						       NULL)))
    {
        intrAssert (!"Could not create AllocDdeChannel window");

	//
	// DeleteChannel will give back the CallControl
	//

        DeleteChannel(*lplpChannel);
        *lplpChannel = NULL;
        return FALSE;
    }

    SetWindowLong ((*lplpChannel)->hwndCli, 0, (LONG) this);
    return TRUE;
}



INTERNAL_(BOOL) CDdeObject::InitSysConv()
{
    DWORD dwResult;
    intrDebugOut((DEB_ITRACE,"CDdeObject::InitSysConv(%x)\n",this));

    dwResult = wInitiate (m_pSysChannel, m_aClass, aOLE);
    if (!dwResult)
    {
       intrDebugOut((DEB_ITRACE,"\t::InitSysConv(%x) Try aSysTopic\n",this));
       dwResult = wInitiate (m_pSysChannel, m_aClass, aSysTopic);
    }

    if (!dwResult)
    {
       intrDebugOut((DEB_ITRACE,"\t::InitSysConv(%x) is failing\n",this));
    }
    return(dwResult);
}



INTERNAL_(void) CDdeObject::SetTopic(ATOM aTopic)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::SetTopic(%x)\n",this));
    intrAssert(wIsValidAtom(aTopic));
    if (m_aTopic)
        GlobalDeleteAtom (m_aTopic);

    m_aTopic = aTopic;
}



INTERNAL CDdeObject::TermConv
    (LPDDE_CHANNEL pChannel,
    BOOL fWait)     // Default==TRUE.  FALSE only in ProxyManager::Disconnect
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::TermConv(%x,pChannel=%x)\n",
		  this,
		  pChannel));

    HRESULT hres;
    if (!pChannel)
    {
        return NOERROR;
    }

    if (!wPostMessageToServer (pChannel, WM_DDE_TERMINATE, 0,FALSE))
    {
	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::TermConv(%x) Server Probably Died\n",
		      this));
        hres = RPC_E_SERVER_DIED;
    }
    else
    {
	pChannel->bTerminating = TRUE;
	Putsi (fWait);
	hres = fWait ? WaitForReply (pChannel,
				     AA_TERMINATE,
				     /*fStdCloseDoc*/FALSE,
				    /*fDetectTerminate*/ FALSE) : NOERROR;
	if (pChannel==m_pDocChannel)
	{
	    DeclareVisibility (FALSE);
	    if (!m_fDidSendOnClose)
	    {
		SendOnClose();
	    }
	}
    }

    DeleteChannel (pChannel);
    intrDebugOut((DEB_ITRACE,"::TermConv(%x) returns %x\n",this,hres));
    return hres;
}




INTERNAL_(void) CDdeObject::DeleteChannel (LPDDE_CHANNEL pChannel)
{
    BOOL fDocChannel = FALSE;
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::DeleteChannel(%x,pChannel=%x)\n",
		  this,
		  pChannel));

    if (pChannel == NULL)
    {
        return;
    }

    if (pChannel == m_pDocChannel)
	fDocChannel = TRUE;



    // delete any data if we were in busy mode.
    wClearWaitState (pChannel);

    if (pChannel == m_pDocChannel)
    {
	intrDebugOut((DEB_ITRACE,
		      "::DeleteChannel(%x)Clean up pDocChannel\n",
		      this));

        // Cleanup per-conversation information
        m_fDidSendOnClose = FALSE;
        m_fDidStdCloseDoc = FALSE;
        m_ConnectionTable.Erase();
        m_iAdvSave = 0;
        m_iAdvClose= 0;
        m_fWasEverVisible = FALSE;
        m_fGotCloseData = FALSE;
        if (m_ptd)
        {
            delete m_ptd;
            m_ptd = NULL;
        }
        if (m_pstg)
        {
            m_pstg->Release();
            m_pstg = NULL;
        }
        if (m_pDataAdvHolder)
        {
            Verify (0==m_pDataAdvHolder->Release());
        }
        CreateDataAdviseHolder (&m_pDataAdvHolder);
        if (m_pOleAdvHolder)
        {
            m_pOleAdvHolder->Release(); // may not return 0 if we are
                                        // in a SendOnClose
        }
        CreateOleAdviseHolder (&m_pOleAdvHolder);
    }

    if (pChannel->hwndCli)
    {
	intrDebugOut((DEB_ITRACE,
		      "::DeleteChannel(%x)Destroy hwndCli(%x)\n",
		      this,
		      pChannel->hwndCli));

        Assert (IsWindow (pChannel->hwndCli));
        Assert (this==(CDdeObject *)GetWindowLong (pChannel->hwndCli, 0));
        Verify (SSDestroyWindow (pChannel->hwndCli));
    }

    if (pChannel == m_pDocChannel)
    {
        m_pDocChannel = NULL;
    }
    else
    {
	intrAssert(pChannel == m_pSysChannel);
        m_pSysChannel = NULL;
    }


    if (pChannel->pCallCont != NULL)
    {
	ReleaseDdeCallControlInterface();
	pChannel->pCallCont = NULL;
    }

    // Channel will be deleted in the modallp.cpp
    // if flag is on.

    if (pChannel->wChannelDeleted == Channel_InModalloop)
    {
	intrDebugOut((DEB_ITRACE,
		      "::DeleteChannel(%x) Channel(%x) in Modal Loop\n",
		      this,pChannel));

        pChannel->wChannelDeleted = Channel_DeleteNow;
    }
    else
    {
	if (pChannel->ReleaseReference() == 0)
	    pChannel = NULL;
    }

    if (fDocChannel)
	m_pDocChannel = pChannel;
}

const WCHAR  EMB_STR[]= OLESTR(" -Embedding ") ;

INTERNAL_(BOOL) CDdeObject::LaunchApp (void)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::LaunchApp(%x)\n",this));

    STARTUPINFO startInfo;
    PROCESS_INFORMATION procInfo;
    BOOL        fProcStarted;
    WCHAR       cmdline[MAX_PATH + sizeof(EMB_STR)];
    WCHAR       exeName[MAX_PATH + sizeof(cmdline)];
    //
    // Init all fields of startInfo to zero
    //
    memset((void *)&startInfo,0,sizeof(startInfo));

    //
    // The normal startup is set here.
    //
    startInfo.wShowWindow = SW_NORMAL;
    startInfo.dwFlags = STARTF_USESHOWWINDOW;

    m_fDidLaunchApp = FALSE;


    DWORD dw;

    //
    // Do our best to find the path
    //
    intrAssert(wIsValidAtom(m_aExeName));

    if (m_aExeName == 0)
    {
	//
	// There is no exe name to execute. Can't start it.
	//
	return(FALSE);
    }

    dw = SearchPath(NULL,wAtomName(m_aExeName),NULL,MAX_PATH,exeName, NULL);

    if ((dw == 0) || (dw > MAX_PATH))
    {
	intrDebugOut((DEB_ITRACE,
		      "::LaunchApp(%x) SearchPath failed. Do Default",this));
	//
	// SearchPath failed. Use the default
	//
	GlobalGetAtomName (m_aExeName, exeName, MAX_PATH);
    }

    memcpy(cmdline, EMB_STR,sizeof(EMB_STR));

    if (m_ulObjType == OT_LINK)
    {
	intrAssert(wIsValidAtom(m_aTopic));
       // File name
       Assert (wAtomName (m_aTopic));

       lstrcatW (cmdline, wAtomName (m_aTopic));
    }

    if (m_clsid == CLSID_ExcelWorksheet  // Stupid apps that show themselves
	|| m_clsid == CLSID_ExcelMacrosheet // when they're not supposed to
	|| m_clsid == CLSID_ExcelChart
	|| m_clsid == CLSID_PBrush)
    {
       startInfo.wShowWindow = SW_SHOWMINNOACTIVE;
    }

    //
    // According to the spec, the most robust way to start the app is to
    // only use a cmdline that consists of the exe name, followed by the
    // command line arguments.
    //

    lstrcatW(exeName,cmdline);

    Assert((lstrlenW(exeName)+1) < sizeof(exeName));

    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::LaunchApp(%x) Starting '%ws' \n",
		  this,
		  exeName));

    if (IsWOWThreadCallable())
    {
        HRESULT hr;

	hr = g_pOleThunkWOW->WinExec16(exeName, startInfo.wShowWindow);

        fProcStarted = SUCCEEDED(hr);

#if DBG==1
        if (!fProcStarted)
        {
            intrDebugOut((DEB_ITRACE,
                        "::LaunchApp(%x) in Wow FAILED(%x) TO START %ws \n",
                        this,
    		        hr,
    		        exeName));
        }
#endif
    }
    else
    {
        fProcStarted = CreateProcess(NULL,
    				     exeName,
    				     NULL,
    				     NULL,
    				     FALSE,
    				     0,
    				     NULL,
    				     NULL,
    				     &startInfo,
    				     &procInfo);
        if (fProcStarted)
        {
           //
           // Let's give the server a chance to register itself. On NT,
           // CreateProcess gets the other process going, but returns
           // to let it run asynchronously. This isn't good, since we
           // need some way of knowing when it has started, so we can
           // send the DDE_INITIATES 'after' they create their DDE
           // window.
           //
           // Maximum timeout we want here shall be set at 30 seconds.
           // This should give enough time for even a 16bit WOW app to
           // start. This number was picked by trial and error. Normal
           // apps that go into an InputIdle state will return as soon
           // as they are ready. Therefore, we normally won't wait
           // the full duration.
           //

           ULONG ulTimeoutDuration = 30000L;

           //
           // Now modify this start time to handle classes
           // that have known problems. This list includes:
           //

           switch(WaitForInputIdle(procInfo.hProcess, ulTimeoutDuration))
           {
           case 0:
               intrDebugOut((DEB_ITRACE,
                      "::LaunchApp, %ws started\n",
                      exeName));
                break;
           case WAIT_TIMEOUT:
               intrDebugOut((DEB_ITRACE,
                      "::LaunchApp, %ws wait timeout at %u (dec) ms. Go Anyway\n",
                      exeName,
    		  ulTimeoutDuration));
               break;
           default:
               intrDebugOut((DEB_ITRACE,
                      "::LaunchApp, %ws unknown condition (%x)\n",
                      exeName,
                      GetLastError()));
           }
           //
           // We are already done with the Process and Thread handles
           //
           CloseHandle(procInfo.hProcess);
           CloseHandle(procInfo.hThread);
        }
        else
        {
            intrDebugOut((DEB_ITRACE,
                        "::LaunchApp(%x) FAILED(%x) TO START %ws \n",
                        this,
    		        GetLastError(),
    		        exeName));
        }
    }

    if (fProcStarted)
    {
        // If we ran the server, it should not be visible yet.
        DeclareVisibility (FALSE);
        m_fDidLaunchApp = TRUE;
    }

    return fProcStarted;
}


INTERNAL CDdeObject::MaybeUnlaunchApp (void)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::MaybeUnlaunchApp(%x)\n",this));
    if (m_fDidLaunchApp
        && !m_fDidGetObject
        && (m_clsid == CLSID_ExcelWorksheet
            || m_clsid == CLSID_ExcelMacrosheet
            || m_clsid == CLSID_ExcelChart))
    {
        return UnlaunchApp();
    }
    return NOERROR;
}




INTERNAL CDdeObject::UnlaunchApp (void)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::UnlaunchApp(%x)\n",this));
    HANDLE hCommands;
    HRESULT hresult = NOERROR;
    RetZS (AllocDdeChannel (&m_pSysChannel, SYS_CLASSA), E_OUTOFMEMORY);
    ErrZS (InitSysConv(), E_UNEXPECTED);
    ErrRtnH (PostSysCommand (m_pSysChannel,(LPSTR) &achStdExit, /*bStdNew*/FALSE,
                             /*fWait*/FALSE));
    hCommands = m_pSysChannel->hCommands;
    hresult = TermConv (m_pSysChannel);

    // Since Excel incorrectly did not send an ACK, we need to
    // delete the handle ("[StdExit]") in the DDE message ourselves.
    if (hCommands)
        GlobalFree (hCommands);

    return hresult;

  errRtn:
    DeleteChannel (m_pSysChannel);
    return hresult;
}




INTERNAL CDdeObject::Execute
    (LPDDE_CHANNEL pChannel,
    HANDLE hdata,
    BOOL fStdCloseDoc,
    BOOL fWait,
    BOOL fDetectTerminate)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::Execute(%x,hdata=%x)\n",this,hdata));

    LPARAM lp;

#ifdef _CHICAGO_
	//POSTPPC
    if (fWait && (CanMakeOutCall(pChannel) == FALSE))
    {
	intrDebugOut((DEB_ERROR, "::Execute(%x) can not call out\n", this));
	GlobalFree (hdata);
        return ResultFromScode (E_UNEXPECTED);
    }
#endif

    if (wPostMessageToServer (pChannel,
                      WM_DDE_EXECUTE,
		      lp=MAKE_DDE_LPARAM(WM_DDE_EXECUTE,0, hdata),TRUE))
    {
        if (fStdCloseDoc)
        {
            // Prepare to free the handle if Excel does not send an Ack
            pChannel->hCommands = hdata;
        }
        return fWait ? WaitForReply (pChannel, AA_EXECUTE, fStdCloseDoc,
                                     fDetectTerminate)
                     : NOERROR ;
    }
    GlobalFree (hdata);
    return ReportResult(0, RPC_E_DDE_POST, 0, 0);
}




INTERNAL_(HRESULT) CDdeObject::AdviseOn (CLIPFORMAT cfFormat, int iAdvOn)
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::AdviseOn(%x,cfFormat=%x,iAdvOn=%x)\n",
		  this,
		  cfFormat,
		  iAdvOn));

    HANDLE          hopt=NULL;
    DDEADVISE *  lpopt=NULL;
    ATOM            aItem=(ATOM)0;
    HRESULT         hresult = ReportResult(0, E_UNEXPECTED, 0, 0);

    RetZ (m_pDocChannel);

    if (NOERROR == m_ConnectionTable.Lookup (cfFormat, NULL))
    {
        // We already got a call to DataObject::Advise on this format.
	intrDebugOut((DEB_ITRACE,
		      "::AdviseOn(%x) Advise had been done on cfFormat=%x\n",
		      this,
		      cfFormat));
        return NOERROR;
    }

    UpdateAdviseCounts (cfFormat, iAdvOn, +1);

    if (m_fDidSendOnClose)
    {
	intrDebugOut((DEB_ITRACE,"::AdviseOn(%x)Ignoring Advise because we are closing\n",this));
        return NOERROR;
    }

    if (!(hopt = GlobalAlloc (GMEM_DDESHARE | GMEM_ZEROINIT, sizeof(DDEADVISE))))
    {
	intrDebugOut((DEB_ITRACE,"::AdviseOn(%x)GlobalAlloc returned NULL\n",this));
        return ReportResult(0, E_OUTOFMEMORY, 0, 0);
    }


    if (!(lpopt = (DDEADVISE FAR *) GlobalLock (hopt)))
    {
	intrDebugOut((DEB_ITRACE,"::AdviseOn(%x)GlobalLock returned NULL\n",this));
        GlobalFree (hopt);
        return ReportResult(0, E_OUTOFMEMORY, 0, 0);
    }

    lpopt->fAckReq = TRUE;
    lpopt->fDeferUpd = FALSE;
    lpopt->cfFormat = cfFormat;
    m_pDocChannel->hopt = hopt;

    if (iAdvOn == ON_RENAME)
    {
        aItem = wDupAtom (aStdDocName);
	intrAssert(wIsValidAtom(aItem));
    }
    else
    {
	intrAssert(wIsValidAtom(m_aItem));
        aItem = wExtendAtom (m_aItem, iAdvOn);
	intrAssert(wIsValidAtom(aItem));
    }

    intrDebugOut((DEB_ITRACE,
		  "::AdviseOn(%x) lpopt->cfFormat = %x, aItem=%x (%ws)\n",
		  this,
		  lpopt->cfFormat,
		  aItem,
		  wAtomName(aItem)));

    GlobalUnlock (hopt);

    LPARAM lp;
    if (FALSE==wPostMessageToServer (m_pDocChannel, WM_DDE_ADVISE,
                                lp=MAKE_DDE_LPARAM(WM_DDE_ADVISE,hopt,aItem),TRUE))
    {
	intrDebugOut((DEB_ITRACE,"::AdviseOn(%x)wPostMessageToServer failed\n",this));
        if (aItem)
            GlobalDeleteAtom (aItem);
        if (hopt)
            GlobalFree (hopt);
        return ResultFromScode (RPC_E_DDE_POST);
    }
    ErrRtnH (WaitForReply (m_pDocChannel, AA_ADVISE));

    return NOERROR;

errRtn:
    hresult = (RPC_E_DDE_NACK == hresult) ? DV_E_CLIPFORMAT : hresult;
    intrDebugOut((DEB_ITRACE,
		  "::AdviseOn(%x) errRet, AdviseRejected, returning %x\n",
		  this,hresult));
    return hresult;
}

INTERNAL CDdeObject::UpdateAdviseCounts
    (CLIPFORMAT cf,
    int         iAdvOn,
    signed int  cDelta)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::UpdateAdviseCounts(%x)\n",this));
    if (cf==g_cfBinary)
        return NOERROR;

    // Update m_iAdv* flags
    #define macro(Notif, NOTIF) \
    if (iAdvOn == ON_##NOTIF)   \
       m_iAdv##Notif += cDelta; \
    if (m_iAdv##Notif < 0)      \
        m_iAdv##Notif = 0;      \
    else if (m_iAdv##Notif > 2) \
        m_iAdv##Notif = 2;

    macro (Close, CLOSE)
    macro (Save,  SAVE)
    macro (Change,CHANGE)
    #undef macro

    Assert (m_iAdvClose < 3 && m_iAdvSave < 3 && m_iAdvChange < 3);
    Assert (m_iAdvClose >= 0 && m_iAdvSave >= 0 && m_iAdvChange >= 0);

    if (cf == g_cfNative)
    {
        if (iAdvOn != ON_CHANGE)
            m_fDidAdvNative = (cDelta > 0);
        else
	    intrDebugOut((DEB_ITRACE,
			  "::UpdateAdviseCounts(%x)Asked advise on cfNative\n",
			  this));
    }

    return NOERROR;
}




INTERNAL CDdeObject::UnAdviseOn (CLIPFORMAT cfFormat, int iAdvOn)
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::UnAdviseOn(%x,cfFormat=%x,iAdvOn=%x)\n",
		  this,cfFormat,iAdvOn));
    HRESULT hr;
    ATOM aItem= (ATOM)0;

    RetZ (m_pDocChannel);
    UpdateAdviseCounts (cfFormat, iAdvOn, -1);
    if (m_fDidSendOnClose)
    {
	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::UnAdviseOn(%x) Ignored because closing\n",
		      this));
        return NOERROR;
    }
    if (wTerminateIsComing (m_pDocChannel))
    {
        // We already did a StdCloseDocument, so the server is not willing
        // to do an unadvise even though the default hanlder asked us to.
	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::UnAdviseOn(%x) Terminate coming\n",
		      this));
        return NOERROR;
    }

    if (iAdvOn == ON_RENAME)
    {
        aItem = wDupAtom (aStdDocName);
	intrAssert(wIsValidAtom(aItem));
    }
    else
    {
	intrAssert(wIsValidAtom(m_aItem));
        aItem = wExtendAtom (m_aItem, iAdvOn);
	intrAssert(wIsValidAtom(aItem));
    }


    if (!wPostMessageToServer(m_pDocChannel, WM_DDE_UNADVISE,
                                MAKE_DDE_LPARAM (WM_DDE_UNADVISE,cfFormat,aItem),FALSE))
    {
        if (aItem)
            GlobalDeleteAtom (aItem);
	intrDebugOut((DEB_ITRACE,
		      "CDdeObject::UnAdviseOn(%x) wPostMessage failed\n",
		      this));
        return RPC_E_DDE_POST;
    }

    // Wait For Reply
    hr = WaitForReply (m_pDocChannel, AA_UNADVISE, FALSE);
    if (hr != NOERROR && hr != RPC_E_DDE_NACK)
    {
	intrDebugOut((DEB_ITRACE,
		      "::UnAdviseOn(%x)WaitForReply returns %x\n",
		      this));
        return hr;
    }


    if (cfFormat==m_cfPict)
    {
        if (m_hPict)
        {
            // Invalidate the cache so when someone explicitly asks for
            // the data, they will get fresh data.
            wFreeData (m_hPict, m_cfPict, TRUE);
            m_hPict = (HANDLE)0;
            m_cfPict = 0;
        }
    }

    // Due to a bug in the OLE1 libraries, unadvising on a presentation
    // format effectively unadvises on native.
    if (cfFormat != g_cfNative && m_fDidAdvNative)
    {
        if (iAdvOn == ON_SAVE)
        {
            // to reflect the fact that the native advise connection was lost
            m_iAdvSave--;
            m_fDidAdvNative = FALSE;
            RetErr (AdviseOn (g_cfNative, ON_SAVE));  // re-establish
        }
        else if (iAdvOn == ON_CLOSE)
        {
            // to reflect the fact that the native advise connection was lost
            m_iAdvClose--;
            m_fDidAdvNative = FALSE;
            RetErr (AdviseOn (g_cfNative, ON_CLOSE));
        }
    }

    return NOERROR;
}


//
// Post a message to a 1.0 server (callee) and wait for the acknowledge
//


INTERNAL CDdeObject::Poke
    (ATOM aItem, HANDLE hDdePoke)
{
    HRESULT hr;

    intrDebugOut((DEB_ITRACE,"CDdeObject::Poke(%x)\n",this));

    ATOM aTmpItem;

    intrAssert(wIsValidAtom(aItem));

    aTmpItem = wDupAtom (aItem);

    intrAssert(wIsValidAtom(aTmpItem));

    m_pDocChannel->hDdePoke = hDdePoke;

    LPARAM lp;
    if (hr = wPostMessageToServer (m_pDocChannel,
				   WM_DDE_POKE,
                lp=MAKE_DDE_LPARAM(WM_DDE_POKE,hDdePoke,aTmpItem),TRUE))
    {
	hr = WaitForReply (m_pDocChannel, AA_POKE);
	intrDebugOut((DEB_ITRACE,"::Poke(%x) returning %x\n",this,hr));
        return hr;
    }

    intrDebugOut((DEB_ITRACE,"::Poke(%x)wPostMessage failed %x\n",this,hr));
    // Error case
    if (aTmpItem)
        GlobalDeleteAtom (aTmpItem);
    wFreePokeData (m_pDocChannel, m_bOldSvr && m_aClass==aMSDraw);
    hr = RPC_E_DDE_POST;
    intrDebugOut((DEB_ITRACE,"::Poke(%x)wPostMessage returns %x\n",this,hr));
    return hr;

}

INTERNAL CDdeObject::PostSysCommand
    (LPDDE_CHANNEL pChannel,
    LPCSTR szCmd,
    BOOL fStdNew,
    BOOL fWait)
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::PostSysCommand(%x,szCmd=%s,fStdNew=%x,fWait=%x)\n",
		  this,
		  szCmd,
		  fStdNew,
		  fWait));

    ULONG    size;
    WORD   len;
    LPSTR  lpdata= NULL;
    HANDLE hdata = NULL;
    HRESULT hresult;


    #define LN_FUDGE        16     // [],(), 3 * 3 (2 double quotes and comma)

    len =  strlen (szCmd);

    // for StdNewDocument command add class name
    if (fStdNew)
        len += wAtomLenA (m_aClass);

    // Now add the document length.
    len += wAtomLenA (m_aTopic);

    // now add the fudge factor for the Quotes etc.
    len += LN_FUDGE;

    // allocate the buffer and set the command.
    if (!(hdata = GlobalAlloc (GMEM_DDESHARE, size = len)))
        return ReportResult(0, E_OUTOFMEMORY, 0, 0);

    if (!(lpdata = (LPSTR)GlobalLock (hdata))) {
        Assert (0);
        GlobalFree (hdata);
        return ReportResult(0, E_OUTOFMEMORY, 0, 0);
    }

    strcpy (lpdata, (LPSTR)"["); // [
    strcat (lpdata, szCmd);      // [StdCmd
    if (strcmp (szCmd, "StdExit"))
    {
        strcat (lpdata, "(\"");      // [StdCmd("

        if (fStdNew)
        {
            len = strlen (lpdata);
            GlobalGetAtomNameA (m_aClass, (LPSTR)lpdata + len, size-len);
                                                // [StdCmd("class
            strcat (lpdata, "\",\"");          // [StdCmd("class","
        }

        len = strlen (lpdata);
        // now get the topic name.
        GlobalGetAtomNameA (m_aTopic, lpdata + len, (WORD)size - len);
                                                // [StdCmd("class","topic
        strcat (lpdata, "\")");                // [StdCmd("class","topic")
    }
    strcat (lpdata, "]");
    Assert (strlen(lpdata) < size);
    intrDebugOut((DEB_ITRACE,"::PostSysCommand(%x) hData(%s)\n",this,lpdata));
    GlobalUnlock (hdata);

    // return Execute (m_pSysChannel, hdata, /*fStdClose*/FALSE, fWait);
    // REVIEW: this fixed bug 1856 (johannp)
    // JasonFul - does it break something else?

    hresult = Execute (m_pSysChannel,
		       hdata,
		       /*fStdClose*/FALSE,
		       fWait,
		       /*fDetectTerminate*/ TRUE);

    intrDebugOut((DEB_ITRACE,"::PostSysCommand(%x) returns \n",this,hresult));
    return hresult;

}

//+---------------------------------------------------------------------------
//
//  Method:     CDdeObject::KeepData
//
//  Synopsis: Given the DDEDATA structure from a WM_DDE_DATA message, extract
// 		the real data and keep it till GetData or Save is done.
//
//
//  Effects:
//
//  Arguments:  [pChannel] --
//		[hDdeData] --
//
//  Requires:
//
//  Returns:	E_OUTOFMEMORY or E_HANDLE if failure, NOERROR if success
//		
//
//  Signals:
//
//  Modifies:
//
//  Derivation:
//
//  Algorithm:
//
//  History:    5-14-94   kevinro   Commented
//
//  Notes:
//
//----------------------------------------------------------------------------
INTERNAL CDdeObject::KeepData
    (LPDDE_CHANNEL pChannel, HANDLE hDdeData)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::KeepData(%x)\n",this));

    DDEDATA *   lpDdeData = NULL;
    HANDLE          hData;
    CLIPFORMAT      cfFormat;



    if (!(lpDdeData = (DDEDATA *) (GlobalLock (hDdeData))))
    {
        return E_OUTOFMEMORY;;
    }


    cfFormat = lpDdeData->cfFormat;
    intrDebugOut((DEB_ITRACE,
		  "::KeepData(%x) Keeping cfFormat=%x\n",
		  this,
		  cfFormat));

    GlobalUnlock (hDdeData);

    // Possible Side effect of wHandleFromDdeData() is the freeing of hDdeData
    if (!(hData = wHandleFromDdeData (hDdeData))
        || !wIsValidHandle (hData, cfFormat) )
    {
        Assert(0);
        return ReportResult(0, E_OUTOFMEMORY, 0, 0);
    }

    if (cfFormat == g_cfNative) {
        if (m_hNative)
            GlobalFree (m_hNative);
        // Keep the native data
        RetErr (wTransferHandle (&m_hNative, &hData, cfFormat));
    }
    else if (cfFormat == CF_METAFILEPICT ||
             cfFormat == CF_BITMAP       ||
             cfFormat == CF_DIB)
    {
        if (m_hPict)
            wFreeData (m_hPict, m_cfPict, TRUE);
        m_cfPict = cfFormat;
        // Keep the presentation data
        RetErr (wTransferHandle (&m_hPict, &hData, cfFormat));

#ifdef OLD
        // Remember size of picture so we can return
        // a reasonable answer for GetExtent
        if (cfFormat == CF_METAFILEPICT)
        {
            LPMETAFILEPICT  lpMfp = (LPMETAFILEPICT) GlobalLock (m_hPict);
            if (NULL==lpMfp)
                return E_HANDLE;
            UpdateExtent (m_cxContentExtent, lpMfp->xExt);
            UpdateExtent (m_cyContentExtent, lpMfp->yExt);
            GlobalUnlock (m_hPict);
        }
        else if (cfFormat==CF_BITMAP)
        {
            BITMAP bm;
            if (0==GetObject (m_hPict, sizeof(BITMAP), (LPVOID) &bm))
                return E_HANDLE;
            UpdateExtent (m_cxContentExtent,
                            wPixelsToHiMetric (bm.bmWidth, giPpliX));
            UpdateExtent (m_cyContentExtent,
                            wPixelsToHiMetric (bm.bmHeight,giPpliY));
        }
        else if (cfFormat==CF_DIB)
        {
            BITMAPINFOHEADER * pbminfohdr;
            pbminfohdr = (BITMAPINFOHEADER *) GlobalLock (m_hPict);
            if (NULL==pbminfohdr)
                return E_HANDLE;
            UpdateExtent (m_cxContentExtent,
                            wPixelsToHiMetric (pbminfohdr->biWidth, giPpliX));
            UpdateExtent (m_cyContentExtent,
                             wPixelsToHiMetric (pbminfohdr->biHeight,giPpliY));
            GlobalUnlock (m_hPict);
        }
#endif

    }
    else
    {
        if (m_hExtra)
            wFreeData (m_hExtra, m_cfExtra, TRUE);
        m_cfExtra = cfFormat;
        wTransferHandle (&m_hExtra, &hData, cfFormat);
    }

    return NOERROR;
}


// IsFormatAvailable
//
// Does a temporary DDE_REQUEST to see if server supports a format
// Returns NOERROR if format is available.
//


INTERNAL CDdeObject::IsFormatAvailable
    (LPFORMATETC pformatetc)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::IsFormatAvailable(%x)\n",this));
    ATOM    aItem=(ATOM)0;
    HRESULT hresult;

    Puts ("DdeObject::IsFormatAvailable\n");

    if (!HasValidLINDEX(pformatetc))
    {
        intrDebugOut((DEB_IERROR, "\t!HasValidLINDEX(pformatetc)\n"));
        return(DV_E_LINDEX);
    }

    if (0==pformatetc->cfFormat)
        return ResultFromScode (E_INVALIDARG);

    if (pformatetc->dwAspect & DVASPECT_ICON)
    {
        if (pformatetc->cfFormat==CF_METAFILEPICT)
        {
            // This is always available. we get it from the exe.
            return NOERROR;
        }
        // an icon must be a metafile
        return ResultFromScode (S_FALSE);
    }
    if (!(pformatetc->dwAspect & (DVASPECT_CONTENT | DVASPECT_DOCPRINT)))
    {
        // 1.0 does not support Thumb.
        return ReportResult(0, S_FALSE, 0, 0);
    }

    if (NOERROR == (hresult=m_ConnectionTable.Lookup (pformatetc->cfFormat, NULL)))
    {
        // We already got a call to DataObject::Advise on this format,
        // so it must be available.
        Puts ("DataObject::Advise had been done on this format.\n");
        return NOERROR;
    }
    else
    {
        // Lookup () didn't find this format.
        ErrZ (GetScode(hresult)==S_FALSE);
    }

    intrAssert(wIsValidAtom(m_aItem));
    aItem = wDupAtom (m_aItem);
    intrAssert(wIsValidAtom(aItem));

    LPARAM lp;
    if(!wPostMessageToServer (m_pDocChannel, WM_DDE_REQUEST,
                              lp = MAKE_DDE_LPARAM (WM_DDE_REQUEST,pformatetc->cfFormat,aItem),TRUE))
    {

	return(RPC_E_DDE_POST);
    }

    if (NOERROR==WaitForReply (m_pDocChannel, AA_REQUESTAVAILABLE))
        return NOERROR;

    // Last ditch effort: Advise
    if (NOERROR== AdviseOn (pformatetc->cfFormat, ON_SAVE))
    {
        // We cannot Unadvise because an OLE 1.0 bug
        // terminates DDE advise connections for ALL formats.
        //// UnAdviseOn (pformatetc->cfFormat, ON_SAVE);
        // Instead, just remember we did this advise.
        m_ConnectionTable.Add (0, pformatetc->cfFormat, ADVFDDE_ONSAVE);
        return NOERROR;
    }
    return ResultFromScode (S_FALSE);

errRtn:
    AssertSz (0, "Error in CDdeObject::IsFormatAvailable");
    Puth (hresult); Putn();
    if (aItem)
        GlobalDeleteAtom (aItem);

    return hresult;
}




INTERNAL CDdeObject::ChangeTopic
    (LPSTR lpszTopic)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::ChangeTopic(%x,lpszTopic=%s)\n",this,lpszTopic));
    HRESULT hresult;
    LPMONIKER pmkFile=NULL;
    LPMONIKER pmkItem=NULL;
    LPMONIKER pmkComp=NULL;
    LPMONIKER pmkNewName=NULL;
    ATOM aTopic = wGlobalAddAtomA (lpszTopic);
    intrAssert(wIsValidAtom(aTopic));

    // Yet-Another-Excel-Hack
    // Excel 4.0 sends StdDocumentName every time it saves,
    // whether or not the file name has actually changed. Bug 2957
    if (aTopic != m_aTopic)
    {
        ErrRtnH (CreateOle1FileMoniker (wAtomName(aTopic), m_clsid, &pmkFile));
        if (m_aItem)
        {
            intrAssert (wIsValidAtom (m_aItem));
            ErrRtnH (CreateItemMoniker (OLESTR("!"), wAtomName (m_aItem), &pmkItem));
            ErrRtnH (CreateGenericComposite (pmkFile, pmkItem, &pmkComp));
            (pmkNewName = pmkComp)->AddRef();
        }
        else
        {
            (pmkNewName = pmkFile)->AddRef();
        }
        RetZS (m_pOleAdvHolder, E_OUTOFMEMORY);
        RetZ (pmkNewName);
        ErrRtnH (m_pOleAdvHolder->SendOnRename (pmkNewName));
    }
    SetTopic (aTopic);
    hresult = NOERROR;

  errRtn:
    if (pmkFile)
        pmkFile->Release();
    if (pmkItem)
        pmkItem->Release();
    if (pmkComp)
        pmkComp->Release();
    if (pmkNewName)
        pmkNewName->Release();
    return hresult;
}


//+---------------------------------------------------------------------------
//
//  Method:     CDdeObject::ChangeItem
//
//  Synopsis:   Changes the m_aItem atom, using an Ansi string
//
//  Effects:
//
//  Arguments:  [szItem] -- Ansi string for the new item
//
//  Requires:
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Derivation:
//
//  Algorithm:
//
//  History:    5-12-94   kevinro   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
INTERNAL_(void) CDdeObject::ChangeItem
    (LPSTR szItem)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::ChangeItem(%x,szItem=%s)\n",this,szItem));
    intrAssert(wIsValidAtom(m_aItem));
    if (m_aItem)
        GlobalDeleteAtom (m_aItem);
    m_aItem = wGlobalAddAtomA (szItem);
    intrAssert(wIsValidAtom(m_aItem));
}




INTERNAL CDdeObject::DeclareVisibility
    (BOOL f,
    BOOL fCallOnShowIfNec)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::DelcareVisibility(%x)\n",this));
    if (f)
        m_fWasEverVisible = TRUE;
    if ((f && (!m_fVisible || !m_fCalledOnShow)) ||
        (!f && m_fVisible))
    {
        if (m_pOleClientSite && fCallOnShowIfNec && m_clsid != CLSID_Package)
        {
            m_pOleClientSite->OnShowWindow (f);
            m_fCalledOnShow = f;
        }
        m_fVisible = f;
    }
    return NOERROR;
}



INTERNAL CDdeObject::Update
    (BOOL fRequirePresentation)
{
    intrDebugOut((DEB_ITRACE,
		  "CDdeObject::Update(%x,fRequiredPresentation=%x)\n",
		  this,
		  fRequirePresentation));
    // Get latest data
    // OLE 1.0 spec says servers must supply metafile format.
    HRESULT hresult = RequestData (m_cfPict ? m_cfPict : CF_METAFILEPICT);
    if (fRequirePresentation && hresult!=NOERROR)
        return hresult;
    RetErr (RequestData (g_cfNative));
    SendOnDataChange (ON_CHANGE);
    return NOERROR;
}



INTERNAL CDdeObject::Save
    (LPSTORAGE pstg)
{
    intrDebugOut((DEB_ITRACE,"CDdeObject::Save(%x)\n",this));
    VDATEIFACE (pstg);
#ifdef OLE1INTEROP
    RetErr (StSave10NativeData (pstg, m_hNative, m_fOle1interop));
#else
    RetErr (StSave10NativeData (pstg, m_hNative, FALSE));
#endif
    if (m_aItem)
    {
	intrAssert(wIsValidAtom(m_aItem));
        RetErr (StSave10ItemName (pstg, wAtomNameA (m_aItem)));
    }
    RetErr (wWriteFmtUserType (pstg, m_clsid));
    return NOERROR;
}

/*
 *  IMPLEMENTATION of CUnknownImpl
 *
 */



STDMETHODIMP_(ULONG) NC(CDdeObject,CUnknownImpl)::AddRef()
{
    ChkD(m_pDdeObject);

    return InterlockedAddRef(&(m_pDdeObject->m_refs));
}


STDMETHODIMP_(ULONG) NC(CDdeObject,CUnknownImpl)::Release()
{
    ChkD(m_pDdeObject);
    Assert (m_pDdeObject->m_refs != 0);
    ULONG ul;

    if ((ul=InterlockedRelease(&(m_pDdeObject->m_refs))) == 0) {
        m_pDdeObject->m_ProxyMgr.Disconnect();

#ifdef _CHICAGO_
	//Note:POSTPPC
	// the object can not be delete if guarded
	// which is the case if DelayDelete state is 'DelayIt'
	//
	if (m_pDdeObject->_DelayDelete == DelayIt)
	{
	    // set the state to ReadyToDelete and
	    // the object will be deleted at the
	    // UnGuard call
	    m_pDdeObject->_DelayDelete = ReadyToDelete;
	    intrDebugOut((DEB_IWARN ,"Can not release CDdeObject\n"));
	    return 1;
	}
#endif //_CHICAGO_
	delete m_pDdeObject;
        return 0;
    }
    return ul;
}




STDMETHODIMP NC(CDdeObject,CUnknownImpl)::QueryInterface(REFIID iid, LPLPVOID ppv)
{
    ChkD(m_pDdeObject);
    if (iid == IID_IUnknown) {
        *ppv = (void FAR *)&m_pDdeObject->m_Unknown;
        AddRef();
        return NOERROR;
    }
    else if (iid ==  IID_IOleObject)
        *ppv = (void FAR *) &(m_pDdeObject->m_Ole);
    else if (iid ==  IID_IDataObject)
        *ppv = (void FAR *) &(m_pDdeObject->m_Data);
    else if (iid ==  IID_IPersist || iid == IID_IPersistStorage)
        *ppv = (void FAR *) &(m_pDdeObject->m_PersistStg);
    else if (iid == IID_IProxyManager)
        *ppv = (void FAR *) &(m_pDdeObject->m_ProxyMgr);
    else if (iid == IID_IOleItemContainer
             || iid == IID_IOleContainer
             || iid == IID_IParseDisplayName)
        *ppv = (void FAR *) &(m_pDdeObject->m_OleItemContainer);
    else {
        Puts ("INTERFACE NOT FOUND \r\n");
        *ppv = NULL;
        return ReportResult(0, E_NOINTERFACE, 0, 0);
    }

    m_pDdeObject->m_pUnkOuter->AddRef();
    return NOERROR;
}