/****************************** Module Header ******************************\
* Module Name: connect.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* DDE Manager conversation connection functions
*
* Created: 11/3/91 Sanford Staab
*
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop
#include "nddeagnt.h"

//#define TESTING
#ifdef TESTING
ULONG
DbgPrint(
    PCH Format,
    ...
    );
VOID
DbgUserBreakPoint(
    VOID
    );

BOOL ValidateConvList(
HCONVLIST hConvList)
{
    PCONVLIST pcl;
    PCL_CONV_INFO pci;
    PXACT_INFO pxi;
    int i;
    BOOL fMatch;

    if (hConvList == 0) {
        return(TRUE);
    }
    pcl = (PCONVLIST)ValidateCHandle((HANDLE)hConvList,
                                     HTYPE_CONVERSATION_LIST,
                                     HINST_ANY);
    for (i = 0; i < pcl->chwnd; i++) {
        /*
         * all windows in the list are valid
         */
        if (!IsWindow(pcl->ahwnd[i])) {
            DebugBreak();
        }
        pci = (PCL_CONV_INFO)GetWindowLongPtr(pcl->ahwnd[i], GWLP_PCI);
       /*
        * All windows have at least one convinfo associated with them.
        */
        if (pci == NULL) {
            DebugBreak();
        }
        fMatch = FALSE;
        while (pci != NULL) {
            /*
             * All non-zombie conversations have hConvList set correctly.
             */
            if (pci->hConvList != hConvList &&
                    TypeFromHandle(pci->ci.hConv) != HTYPE_ZOMBIE_CONVERSATION) {
                DebugBreak();
            }
            /*
             * All conversations have hConvList clear or set correctly.
             */
            if (pci->hConvList != 0 && pci->hConvList != hConvList) {
                DebugBreak();
            }
            /*
             * At least 1 of the conversations references the list
             */
            if (pci->hConvList == hConvList) {
                fMatch = TRUE;
            }
            for (pxi = pci->ci.pxiOut; pxi; pxi = pxi->next) {
                if ((PCL_CONV_INFO)pxi->pcoi != pci) {
                    DebugBreak();
                }
            }
            pci = (PCL_CONV_INFO)pci->ci.next;
        }
        if (!fMatch) {
            /*
             * At least 1 of the conversations references the list
             */
            DebugBreak;
        }
    }
    return(TRUE);
}

VOID ValidateAllConvLists()
{
    ApplyFunctionToObjects(HTYPE_CONVERSATION_LIST, HINST_ANY,
            (PFNHANDLEAPPLY)ValidateConvList);
}

#else // TESTING
#define ValidateConvList(h)
#define ValidateAllConvLists()
#endif // TESTING

CONVCONTEXT TempConvContext;
CONVCONTEXT DefConvContext = {
    sizeof(CONVCONTEXT),
    0,
    0,
    CP_WINANSI,
    0L,
    0L,
    {
        sizeof(SECURITY_QUALITY_OF_SERVICE),
        SecurityImpersonation,
        SECURITY_STATIC_TRACKING,
        TRUE
    }
};

typedef struct tagINIT_ENUM {
    HWND hwndClient;
    HWND hwndSkip;
    LONG lParam;
    LATOM laServiceRequested;
    LATOM laTopic;
    HCONVLIST hConvList;
    DWORD clst;
} INIT_ENUM, *PINIT_ENUM;


BOOL InitiateEnumerationProc(HWND hwndTarget, PINIT_ENUM pie);
VOID DisconnectConv(PCONV_INFO pcoi);


/***************************************************************************\
* DdeConnect (DDEML API)
*
* Description:
* Initiates a DDE conversation.
*
* History:
* 11-1-91 sanfords Created.
\***************************************************************************/

FUNCLOG4(LOG_GENERAL, HCONV, DUMMYCALLINGTYPE, DdeConnect, DWORD, idInst, HSZ, hszService, HSZ, hszTopic, PCONVCONTEXT, pCC)
HCONV DdeConnect(
    DWORD idInst,
    HSZ hszService,
    HSZ hszTopic,
    PCONVCONTEXT pCC)
{
    PCL_INSTANCE_INFO pcii;
    PCL_CONV_INFO pci;
    HCONV hConvRet = 0;
    HWND hwndTarget = 0;
    LATOM aNormalSvcName = 0;

    EnterDDECrit;

    if (!ValidateConnectParameters((HANDLE)LongToHandle( idInst ), &pcii, &hszService, hszTopic,
            &aNormalSvcName, &pCC, &hwndTarget, 0)) {
        goto Exit;
    }
    pci = ConnectConv(pcii, LATOM_FROM_HSZ(hszService), LATOM_FROM_HSZ(hszTopic),
            hwndTarget,
            (pcii->afCmd & CBF_FAIL_SELFCONNECTIONS) ? pcii->hwndMother : 0,
            pCC, 0, CLST_SINGLE_INITIALIZING);
    if (pci == NULL) {
        SetLastDDEMLError(pcii, DMLERR_NO_CONV_ESTABLISHED);
        goto Exit;
    } else {
        hConvRet = pci->ci.hConv;
    }

Exit:
    if (aNormalSvcName) {
        GlobalDeleteAtom(aNormalSvcName);
    }
    LeaveDDECrit;
    return (hConvRet);
}



/***************************************************************************\
* DdeConnectList (DDEML API)
*
* Description:
* Initiates DDE conversations with multiple servers or adds unique servers
* to an existing conversation list.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/

FUNCLOG5(LOG_GENERAL, HCONVLIST, DUMMYCALLINGTYPE, DdeConnectList, DWORD, idInst, HSZ, hszService, HSZ, hszTopic, HCONVLIST, hConvList, PCONVCONTEXT, pCC)
HCONVLIST DdeConnectList(
    DWORD idInst,
    HSZ hszService,
    HSZ hszTopic,
    HCONVLIST hConvList,
    PCONVCONTEXT pCC)
{
    PCL_INSTANCE_INFO pcii;
    PCONV_INFO pcoi, pcoiNew, pcoiExisting, pcoiNext;
    HCONVLIST hConvListRet = 0;
    HWND hwndTarget = 0;
    LATOM aNormalSvcName = 0;
    PCONVLIST pcl = NULL;
    HCONVLIST hConvListOld;
    int i;

    CheckDDECritOut;

    EnterDDECrit;

    if (!ValidateConnectParameters((HANDLE)LongToHandle( idInst ), &pcii, &hszService, hszTopic,
            &aNormalSvcName, &pCC, &hwndTarget, hConvList)) {
        goto Exit;
    }

    ValidateConvList(hConvList);

    hConvListOld = hConvList;
    pcoi = (PCONV_INFO)ConnectConv(pcii,
            LATOM_FROM_HSZ(hszService),
            LATOM_FROM_HSZ(hszTopic),
            hwndTarget,
            (pcii->afCmd & (CBF_FAIL_SELFCONNECTIONS | CBF_FAIL_CONNECTIONS)) ?
                pcii->hwndMother : 0,
            pCC,
            hConvListOld,
            CLST_MULT_INITIALIZING);

    if (pcoi == NULL) {
        /*
         * no new connections made
         */
        SetLastDDEMLError(pcii, DMLERR_NO_CONV_ESTABLISHED);
        hConvListRet = hConvListOld;
        goto Exit;
    }

    /*
     * allocate or reallocate the hConvList hwnd list for later addition
     * If we already have a valid list, reuse the handle so we don't have
     * to alter the preexisting pcoi->hConvList values.
     */
    if (hConvListOld == 0) {
        pcl = (PCONVLIST)DDEMLAlloc(sizeof(CONVLIST));
        if (pcl == NULL) {
            SetLastDDEMLError(pcii, DMLERR_MEMORY_ERROR);
            DisconnectConv(pcoi);
            goto Exit;
        }
        // pcl->chwnd = 0; LPTR zero inits.

        hConvList = (HCONVLIST)CreateHandle((ULONG_PTR)pcl,
                HTYPE_CONVERSATION_LIST, InstFromHandle(pcii->hInstClient));
        if (hConvList == 0) {
            DDEMLFree(pcl);
            SetLastDDEMLError(pcii, DMLERR_MEMORY_ERROR);
            DisconnectConv(pcoi);
            goto Exit;
        }
    } else {
        pcl = (PCONVLIST)GetHandleData((HANDLE)hConvList);
        pcl = DDEMLReAlloc(pcl, sizeof(CONVLIST) + sizeof(HWND) * pcl->chwnd);
        if (pcl == NULL) {
            SetLastDDEMLError(pcii, DMLERR_MEMORY_ERROR);
            hConvListRet = hConvListOld;
            DisconnectConv(pcoi);
            goto Exit;
        }
        SetHandleData((HANDLE)hConvList, (ULONG_PTR)pcl);
    }

    ValidateConvList(hConvListOld);

    if (hConvListOld) {
        /*
         * remove duplicates from new conversations
         *
         * Although we tried to prevent duplicates from happening
         * within the initiate enumeration code, wild initiates or
         * servers responding with different service names than
         * requested could cause duplicates.
         */

        /* For each client window... */

        for (i = 0; i < pcl->chwnd; i++) {

        /* For each existing conversation in that window... */

            for (pcoiExisting = (PCONV_INFO)
                        GetWindowLongPtr(pcl->ahwnd[i], GWLP_PCI);
                    pcoi != NULL && pcoiExisting != NULL;
                    pcoiExisting = pcoiExisting->next) {

                if (!(pcoiExisting->state & ST_CONNECTED))
                    continue;

        /* For each new conversation... */

                for (pcoiNew = pcoi; pcoiNew != NULL; pcoiNew = pcoiNext) {

                    pcoiNext = pcoiNew->next;

        /* see if the new conversation duplicates the existing one */

                    if (!(pcoiNew->state & ST_CONNECTED))
                        continue;

                    UserAssert(((PCL_CONV_INFO)pcoiExisting)->hwndReconnect);
                    UserAssert(((PCL_CONV_INFO)pcoiNew)->hwndReconnect);

                    if (((PCL_CONV_INFO)pcoiExisting)->hwndReconnect ==
                                ((PCL_CONV_INFO)pcoiNew)->hwndReconnect &&
                            pcoiExisting->laTopic == pcoiNew->laTopic &&
                            pcoiExisting->laService == pcoiNew->laService) {
                        /*
                         * duplicate conversation - disconnection causes an unlink
                         */
                        if (pcoiNew == pcoi) {
                            /*
                             * We are freeing up the head of the list,
                             * Reset the head to the next guy.
                             */
                            pcoi = pcoiNext;
                        }
                        ValidateConvList(hConvList);
                        ShutdownConversation(pcoiNew, FALSE);
                        ValidateConvList(hConvList);
                        break;
                    }
                }
            }
        }

        for (pcoiExisting = pcoi; pcoiExisting != NULL; pcoiExisting = pcoiExisting->next) {
            /*
             * if these are all zombies - we DONT want to link it in!
             * This is possible because ShutdownConversation() leaves the critical section
             * and could allow responding terminates to come through.
             */
            if (pcoiExisting->state & ST_CONNECTED) {
                goto FoundOne;
            }
        }
        pcoi = NULL;    // abandon this guy - he will clean up in time.
FoundOne:
        /*
         * add new pcoi (if any are left) hwnd to ConvList hwnd list.
         */
        if (pcoi != NULL) {
            UserAssert(pcoi->hwndConv);
            pcl->ahwnd[pcl->chwnd] = pcoi->hwndConv;
            pcl->chwnd++;
            hConvListRet = hConvList;
        } else {
            hConvListRet = hConvListOld;
            if (!hConvListOld) {
                DestroyHandle((HANDLE)hConvList);
            }
        }


    } else {    // no hConvListOld

        UserAssert(pcoi->hwndConv);
        pcl->ahwnd[0] = pcoi->hwndConv;
        pcl->chwnd = 1;
        hConvListRet = hConvList;
    }

    if (pcoi != NULL) {
        /*
         * set hConvList field for all remaining new conversations.
         */
        UserAssert(hConvListRet);
        for (pcoiNew = pcoi; pcoiNew != NULL; pcoiNew = pcoiNew->next) {
            if (pcoiNew->state & ST_CONNECTED) {
                ((PCL_CONV_INFO)pcoiNew)->hConvList = hConvListRet;
            }
        }
    }

Exit:
    if (aNormalSvcName) {
        DeleteAtom(aNormalSvcName);
    }
    ValidateConvList(hConvListRet);
    LeaveDDECrit;
    return (hConvListRet);
}




/***************************************************************************\
* DdeReconnect (DDEML API)
*
* Description:
* Attempts to reconnect an externally (from the server) terminated
* client side conversation.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/

FUNCLOG1(LOG_GENERAL, HCONV, DUMMYCALLINGTYPE, DdeReconnect, HCONV, hConv)
HCONV DdeReconnect(
    HCONV hConv)
{
    PCL_INSTANCE_INFO pcii;
    PCL_CONV_INFO pci, pciNew;
    HCONV hConvRet = 0;
    CONVCONTEXT cc;

    EnterDDECrit;

    pcii = PciiFromHandle((HANDLE)hConv);
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }
    pci = (PCL_CONV_INFO)ValidateCHandle((HANDLE)hConv,
            HTYPE_CLIENT_CONVERSATION, HINST_ANY);
    if (pci == NULL) {
        SetLastDDEMLError(pcii, DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    if (pci->ci.state & ST_CONNECTED) {
        goto Exit;
    }

    GetConvContext(pci->ci.hwndConv, (LONG *)&cc);
    pciNew = ConnectConv(pcii, pci->ci.laService, pci->ci.laTopic,
            pci->hwndReconnect, 0, &cc, 0, CLST_SINGLE_INITIALIZING);
    if (pciNew == NULL) {
        SetLastDDEMLError(pcii, DMLERR_NO_CONV_ESTABLISHED);
        goto Exit;
    } else {
        hConvRet = pciNew->ci.hConv;
        if (pci->ci.cLinks) {
            PXACT_INFO pxi;
            int iLink;
            PADVISE_LINK paLink;

            /*
             * reestablish advise links
             */

            for (paLink = pci->ci.aLinks, iLink = pci->ci.cLinks;
                    iLink; paLink++, iLink--) {

                pxi = (PXACT_INFO)DDEMLAlloc(sizeof(XACT_INFO));
                if (pxi == NULL) {
                    break;              // abort relinking
                }
                pxi->pcoi = (PCONV_INFO)pciNew;
                pxi->gaItem = LocalToGlobalAtom(paLink->laItem); // pxi copy
                pxi->wFmt = paLink->wFmt;
                pxi->wType = (WORD)((paLink->wType >> 12) | XTYP_ADVSTART);
                if (ClStartAdvise(pxi)) {
                    pxi->flags |= XIF_ABANDONED;
                } else {
                    GlobalDeleteAtom(pxi->gaItem);
                    DDEMLFree(pxi);
                }
            }
        }
    }

Exit:
    LeaveDDECrit;
    return (hConvRet);
}


/***************************************************************************\
* ValidateConnectParameters
*
* Description:
* worker function to handle common validation code.
*
* Note that paNormalSvcName is set to the atom value created upon extracting
* a normal HSZ from an InstanceSpecific HSZ.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/
BOOL ValidateConnectParameters(
    HANDLE hInst,
    PCL_INSTANCE_INFO *ppcii, // set if valid hInst
    HSZ *phszService, // altered if InstSpecific HSZ
    HSZ hszTopic,
    LATOM *plaNormalSvcName, // set to atom that needs freeing when done
    PCONVCONTEXT *ppCC, // set to point to DefConvContext if NULL
    HWND *phwndTarget, // set if hszService is InstSpecific
    HCONVLIST hConvList)
{
    DWORD hszType;
    BOOL fError = FALSE;

    *ppcii = ValidateInstance(hInst);
    if (*ppcii == NULL) {
        return (FALSE);
    }
    hszType = ValidateHSZ(*phszService);
    if (hszType == HSZT_INVALID || ValidateHSZ(hszTopic) == HSZT_INVALID) {
        SetLastDDEMLError(*ppcii, DMLERR_INVALIDPARAMETER);
        return (FALSE);
    }
    if (hszType == HSZT_INST_SPECIFIC) {
        *phwndTarget = ParseInstSpecificAtom(LATOM_FROM_HSZ(*phszService),
            plaNormalSvcName);
        if (*plaNormalSvcName == 0) {
            SetLastDDEMLError(*ppcii, DMLERR_SYS_ERROR);
            return (FALSE);
        }
        *phszService = NORMAL_HSZ_FROM_LATOM(*plaNormalSvcName);
    }
    if (*ppCC == NULL) {
        *ppCC = &DefConvContext;
        if ((*ppcii)->flags & IIF_UNICODE) {
            (*ppCC)->iCodePage = CP_WINUNICODE;
        } else {
            (*ppCC)->iCodePage = CP_WINANSI;
        }
    } else try {
        if ((*ppCC)->cb > sizeof(CONVCONTEXT)) {
            SetLastDDEMLError(*ppcii, DMLERR_INVALIDPARAMETER);
            fError = TRUE;
        } else if ((*ppCC)->cb < sizeof(CONVCONTEXT)) {
            TempConvContext = DefConvContext;
            /*
             * we can use this static temp because we are synchronized.
             */
            RtlCopyMemory(&TempConvContext, *ppCC, (*ppCC)->cb);
            *ppCC = &TempConvContext;
        }
    } except(W32ExceptionHandler(FALSE, RIP_WARNING)) {
        SetLastDDEMLError(*ppcii, DMLERR_INVALIDPARAMETER);
        fError = TRUE;
    }
    if (fError) {
        return(FALSE);
    }
    if (hConvList != 0 &&
            !ValidateCHandle((HANDLE)hConvList, HTYPE_CONVERSATION_LIST,
            (DWORD)InstFromHandle((*ppcii)->hInstClient))) {
        return (FALSE);
    }
    return (TRUE);
}



/***************************************************************************\
* ConnectConv
*
* Description:
* Work function for all Connect cases.
*
* Method:
*
* To reduce the number of windows we use and to simplify how client
* windows handle multiple WM_DDE_ACK messages during initiation, a
* single client window can handle many conversations, each with
* a different server window.
*
* The client window is created and set to a initiation state via the
* GWL_CONVSTATE window word. Initiates are then sent to enumerated server
* window candidates.
* The GWL_CONVSTATE value is used by the DDEML mother windows
* to determine if only one or several ACKs are desired to minimize
* unnessary message traffic.
*
* The client window GWL_CONVCONTEXT? window words are also used by
* Event Windows to pass context information.
*
* Note that all client and server windows are children of the mother
* window. This reduces the number of top level windows that
* WM_DDE_INITIATES need to hit.
*
* Each WM_DDE_ACK that is received by a client window while in the
* initiation state causes it to create a CL_CONV_INFO structure,
* partially initialize it, and link it into its list of CL_CONV_INFO
* structures. The head of the list is pointed to by the GWLP_PCI
* client window word.
*
* After each WM_DDE_INITIALIZE is sent, the GWLP_PCI value is checked
* to see if it exists and needs initialization to be completed. If
* this is the case the init code knows that at least one ACK was
* received in response to the WM_DDE_INITIALIZE send. The
* initialization of each CL_CONV_INFO struct that needs it is then completed.
*
* Once the broadcasting of WM_DDE_INITIALIZE is done, the init code
* then sets the GWL_CONVSTATE value in the client window to indicate that
* initialization is complete.
*
* Returns:
* The head pci to the client window or NULL if no connections made it.
*
* History:
* 11-1-91 sanfords Created.
\***************************************************************************/
PCL_CONV_INFO ConnectConv(
    PCL_INSTANCE_INFO pcii,
    LATOM laService,
    LATOM laTopic,
    HWND hwndTarget, // 0 implies broadcast
    HWND hwndSkip, // 0 implies no skips - avoids self-connections.
    PCONVCONTEXT pCC,
    HCONVLIST hConvList,
    DWORD clst)
{
    INIT_ENUM ie;
    PCL_CONV_INFO pci;
    PCONV_INFO pcoi;
    GATOM gaService, gaTopic;

    CheckDDECritIn;

    if (hwndTarget && hwndTarget == hwndSkip) {
        return(NULL);
    }

    LeaveDDECrit;
    CheckDDECritOut;

    if (pcii->flags & IIF_UNICODE) {
        ie.hwndClient = CreateWindowW((LPWSTR)(gpsi->atomSysClass[ICLS_DDEMLCLIENTW]),
                                     L"",
                                     WS_CHILD,
                                     0, 0, 0, 0,
                                     pcii->hwndMother,
                                     (HMENU)0,
                                     (HANDLE)0,
                                     (LPVOID)NULL);
    } else {
        ie.hwndClient = CreateWindowA((LPSTR)(gpsi->atomSysClass[ICLS_DDEMLCLIENTA]),
                                     "",
                                     WS_CHILD,
                                     0, 0, 0, 0,
                                     pcii->hwndMother,
                                     (HMENU)0,
                                     (HANDLE)0,
                                     (LPVOID)NULL);
    }

    EnterDDECrit;

    if (ie.hwndClient == 0) {
        return (NULL);
    }

    if (pCC != NULL) {
        if (!NtUserDdeSetQualityOfService(ie.hwndClient, &(pCC->qos), NULL)) {
            SetLastDDEMLError(pcii, DMLERR_MEMORY_ERROR);
            goto Error;
        }
    }
    /*
     * Note that a pci will be created and allocated for each ACK recieved.
     */
    SetConvContext(ie.hwndClient, (LONG *)pCC);
    SetWindowLong(ie.hwndClient, GWL_CONVSTATE, clst);
    SetWindowLongPtr(ie.hwndClient, GWLP_SHINST, (LONG_PTR)pcii->hInstServer);
    SetWindowLongPtr(ie.hwndClient, GWLP_CHINST, (LONG_PTR)pcii->hInstClient);

    gaService = LocalToGlobalAtom(laService);
    gaTopic = LocalToGlobalAtom(laTopic);
    ie.lParam = MAKELONG(gaService, gaTopic);
    if (!hwndTarget) {
        ie.hwndSkip = hwndSkip;
        ie.laServiceRequested = laService;
        ie.laTopic = laTopic;
        ie.hConvList = hConvList;
        ie.clst = clst;
    }

    LeaveDDECrit;

    if (hwndTarget) {
        SendMessage(hwndTarget, WM_DDE_INITIATE, (WPARAM)ie.hwndClient,
                ie.lParam);
    } else {
        /*
         * Send this message to the nddeagnt app first so it can start
         * the netdde services BEFORE we do an enumeration of windows.
         * This lets things work the first time.  NetDDEAgent caches
         * service status so this is the fastest way to do this.
         */
        HWND hwndAgent = FindWindowW(SZ_NDDEAGNT_CLASS, SZ_NDDEAGNT_TITLE);
        if (hwndAgent) {
            SendMessage(hwndAgent,
                WM_DDE_INITIATE, (WPARAM)ie.hwndClient, ie.lParam);
        }
        EnumWindows((WNDENUMPROC)InitiateEnumerationProc, (LPARAM)&ie);
    }

    EnterDDECrit;
    /*
     * hConvList may have been destroyed during the enumeration but we are
     * done with it now so no need to revalidate.
     */

#if DBG
    {
        WCHAR sz[10];

        if (gaService && GlobalGetAtomName(gaService, sz, 10) == 0) {
            RIPMSG1(RIP_ERROR, "Bad Service Atom after Initiate phase: %lX", (DWORD)gaService);
        }
        if (gaTopic && GlobalGetAtomName(gaTopic, sz, 10) == 0) {
            RIPMSG1(RIP_ERROR, "Bad Topic Atom after Initiate phase: %lX", (DWORD)gaTopic);
        }
    }
#endif // DBG

    GlobalDeleteAtom(gaService);
    GlobalDeleteAtom(gaTopic);

    //
    // Get the first pci allocated when a WM_DDE_ACK was recieved.
    //
    pci = (PCL_CONV_INFO)GetWindowLongPtr(ie.hwndClient, GWLP_PCI);
    if (pci == NULL) {
Error:
        LeaveDDECrit;
        NtUserDestroyWindow(ie.hwndClient);
        EnterDDECrit;
        return (NULL);
    }

    SetWindowLong(ie.hwndClient, GWL_CONVSTATE, CLST_CONNECTED);
    if (hwndTarget) {
        /*
         * If hwndTarget was NULL, the enumeration proc took care of this.
         */
        pci->hwndReconnect = hwndTarget;
        UserAssert(pci->ci.next == NULL);
        pci->ci.laServiceRequested = laService;
        IncLocalAtomCount(laService); // pci copy
    }

    if (pcii->MonitorFlags & MF_CONV) {
        for (pcoi = (PCONV_INFO)pci; pcoi; pcoi = pcoi->next) {
            MONCONV(pcoi, TRUE);
        }
    }
    return (pci);
}


/*
 * Undoes the work of ConnectConv()
 */
VOID DisconnectConv(
PCONV_INFO pcoi)
{
    PCONV_INFO pcoiNext;

    for (; pcoi; pcoi = pcoiNext) {
        pcoiNext = pcoi->next;
        ShutdownConversation(pcoi, FALSE);
    }
}


/***************************************************************************\
* InitiateEnumerationProc (FILE LOCAL)
*
* Description:
* Function used via EnumWindows to enumerate all server window candidates
* during DDE initiation. The enumeration allows DDEML to know what
* window WM_DDE_INITIATE was sent to so that it can be remembered for
* possible reconnection later. (The window that receives the WM_DDE_INITIATE
* message is not necessarily going to be the server window.)
*
* History:
* 11-1-91 sanfords Created.
\***************************************************************************/
BOOL InitiateEnumerationProc(
    HWND hwndTarget,
    PINIT_ENUM pie)
{
    PCL_CONV_INFO pci;

    CheckDDECritOut;

    if (hwndTarget == pie->hwndSkip) {
        return (TRUE);
    }

    if (pie->hConvList && pie->laTopic && pie->laServiceRequested) {
        /*
         * Head off duplicates BEFORE we send the WM_DDE_INITIATE messages!
         */
        PCONVLIST pcl;
        PCONV_INFO pcoiExisting;
        int i;

        EnterDDECrit;
        /*
         * We revalidate hConvList here because we left the critical section.
         */
        pcl = (PCONVLIST)ValidateCHandle((HANDLE)pie->hConvList,
                HTYPE_CONVERSATION_LIST, HINST_ANY);
        if (pcl != NULL) {
            for (i = 0; i < pcl->chwnd; i++) {
                for (pcoiExisting = (PCONV_INFO)GetWindowLongPtr(pcl->ahwnd[i], GWLP_PCI);
                        pcoiExisting != NULL;
                        pcoiExisting = pcoiExisting->next) {
                    if (pcoiExisting->state & ST_CONNECTED &&
                            ((PCL_CONV_INFO)pcoiExisting)->hwndReconnect == hwndTarget &&
                            pcoiExisting->laTopic == pie->laTopic &&
                            pcoiExisting->laService == pie->laServiceRequested) {
                        LeaveDDECrit;
                        return(TRUE);
                    }
                }
            }
        }
        LeaveDDECrit;
    }

    CheckDDECritOut;

    SendMessage(hwndTarget, WM_DDE_INITIATE, (WPARAM)pie->hwndClient,
            pie->lParam);

    EnterDDECrit;

    //
    // During the initiate process, any acks received cause more pci's
    // to become linked together under the same hwndClient. Once
    // the SendMessage() returns, we set the parts of the new pci's
    // that hold initiate context information.
    //
    pci = (PCL_CONV_INFO)GetWindowLongPtr(pie->hwndClient, GWLP_PCI);
    if (pci == NULL) {
        LeaveDDECrit;
        return (TRUE);
    }

    while (pci != NULL) {
        if (pci->hwndReconnect == 0) {  // this one needs updating
            pci->hwndReconnect = hwndTarget;
            if (pie->laServiceRequested) {
                pci->ci.laServiceRequested = pie->laServiceRequested;
                IncLocalAtomCount(pie->laServiceRequested); // pci copy
            }
        }
        if (pie->clst == CLST_SINGLE_INITIALIZING) {
            break;
        }
        pci = (PCL_CONV_INFO)pci->ci.next;
    }
    LeaveDDECrit;
    return (pie->clst == CLST_MULT_INITIALIZING);
}




/***************************************************************************\
* SetCommonStateFlags()
*
* Description:
*   Common client/server worker function
*
* History:
* 05-12-91 sanfords Created.
\***************************************************************************/
VOID SetCommonStateFlags(
HWND hwndUs,
HWND hwndThem,
PWORD pwFlags)
{
    DWORD pidUs, pidThem;

    GetWindowThreadProcessId(hwndUs, &pidUs);
    GetWindowThreadProcessId(hwndThem, &pidThem);
    if (pidUs == pidThem) {
        *pwFlags |= ST_INTRA_PROCESS;
    }

    if (IsWindowUnicode(hwndUs) && IsWindowUnicode(hwndThem)) {
        *pwFlags |= ST_UNICODE_EXECUTE;
    }
}




/***************************************************************************\
* DdeQueryNextServer (DDEML API)
*
* Description:
* Enumerates conversations within a list.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/

FUNCLOG2(LOG_GENERAL, HCONV, DUMMYCALLINGTYPE, DdeQueryNextServer, HCONVLIST, hConvList, HCONV, hConvPrev)
HCONV DdeQueryNextServer(
    HCONVLIST hConvList,
    HCONV hConvPrev)
{
    HCONV hConvRet = 0;
    PCONVLIST pcl;
    HWND *phwnd;
    int i;
    PCL_CONV_INFO pci;
    PCL_INSTANCE_INFO pcii;

    EnterDDECrit;

    pcl = (PCONVLIST)ValidateCHandle((HANDLE)hConvList,
            HTYPE_CONVERSATION_LIST, HINST_ANY);
    if (pcl == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }
    if (!pcl->chwnd) {      // empty list
        goto Exit;
    }
    pcii = PciiFromHandle((HANDLE)hConvList);
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    pcii->LastError = DMLERR_NO_ERROR;

    do {

        hConvRet = 0;

        if (hConvPrev == 0) {
            pci = (PCL_CONV_INFO)GetWindowLongPtr(pcl->ahwnd[0], GWLP_PCI);
            if (pci == NULL) {
                goto Exit;  // Must have all conversations zombied.
            }
            hConvPrev = hConvRet = pci->ci.hConv;
            continue;
        }

        pci = (PCL_CONV_INFO)ValidateCHandle((HANDLE)hConvPrev,
                HTYPE_CLIENT_CONVERSATION, InstFromHandle(hConvList));
        if (pci == NULL) {
            pci = (PCL_CONV_INFO)ValidateCHandle((HANDLE)hConvPrev,
                    HTYPE_ZOMBIE_CONVERSATION, InstFromHandle(hConvList));
            if (pci == NULL) {
                SetLastDDEMLError(pcii, DMLERR_INVALIDPARAMETER);
                break;
            } else {
                goto ZombieSkip;
            }
        }

        if (pci->hConvList != hConvList) {
            SetLastDDEMLError(pcii, DMLERR_INVALIDPARAMETER);
            break;
        }

ZombieSkip:

        if (pci->ci.next == NULL) {

            /*
             * end of list for this window, go to next window
             */
            for (phwnd = pcl->ahwnd, i = 0; (i + 1) < pcl->chwnd; i++) {
                if (phwnd[i] == pci->ci.hwndConv) {
                    pci = (PCL_CONV_INFO)GetWindowLongPtr(phwnd[i + 1], GWLP_PCI);
                    if (pci == NULL) {
                        break;
                    }
                    hConvPrev = hConvRet = pci->ci.hConv;
                    break;
                }
            }
        } else {

            hConvPrev = hConvRet = pci->ci.next->hConv; // next conv for this window.
        }

    } while (hConvRet && TypeFromHandle(hConvRet) == HTYPE_ZOMBIE_CONVERSATION);
Exit:
    LeaveDDECrit;
    return (hConvRet);
}





/***************************************************************************\
* DdeDisconnect (DDEML API)
*
* Description:
* Terminates a conversation.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/

FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, DdeDisconnect, HCONV, hConv)
BOOL DdeDisconnect(
    HCONV hConv)
{
    BOOL fRet = FALSE;
    PCONV_INFO pcoi;
    PCL_INSTANCE_INFO pcii;

    CheckDDECritOut;
    EnterDDECrit;

    pcoi = (PCONV_INFO)ValidateCHandle((HANDLE)hConv,
            HTYPE_CLIENT_CONVERSATION, HINST_ANY);
    if (pcoi == NULL) {
        pcoi = (PCONV_INFO)ValidateCHandle((HANDLE)hConv,
                HTYPE_SERVER_CONVERSATION, HINST_ANY);
    }
    if (pcoi == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }
    pcii = PciiFromHandle((HANDLE)hConv);
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }
    if (pcoi->state & ST_CONNECTED) {
        ShutdownConversation(pcoi, FALSE);
    }
    fRet = TRUE;

Exit:
    LeaveDDECrit;
    return (fRet);
}


/***************************************************************************\
* DdeDisconnectList (DDEML API)
*
* Description:
* Terminates all conversations in a conversation list and frees the list.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/

FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, DdeDisconnectList, HCONVLIST, hConvList)
BOOL DdeDisconnectList(
    HCONVLIST hConvList)
{
    BOOL fRet = FALSE;
    PCL_INSTANCE_INFO pcii;
    PCONVLIST pcl;
    PCONV_INFO pcoi, pcoiNext;
    int i;

    CheckDDECritOut;
    EnterDDECrit;

    pcl = (PCONVLIST)ValidateCHandle((HANDLE)hConvList,
            HTYPE_CONVERSATION_LIST, HINST_ANY);
    if (pcl == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }
    ValidateConvList(hConvList);
    pcii = PciiFromHandle((HANDLE)hConvList);
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    for(i = pcl->chwnd - 1; i >= 0; i--) {
        pcoi = (PCONV_INFO)GetWindowLongPtr(pcl->ahwnd[i], GWLP_PCI);
        while (pcoi != NULL && pcoi->state & ST_CONNECTED) {
            pcoiNext = pcoi->next;
            ShutdownConversation(pcoi, FALSE);  // may unlink pcoi!
            pcoi = pcoiNext;
        }
    }

    DestroyHandle((HANDLE)hConvList);
    DDEMLFree(pcl);
    fRet = TRUE;

Exit:
    LeaveDDECrit;
    return (fRet);
}




/***************************************************************************\
* ShutdownConversation
*
* Description:
* This function causes an imediate termination of the given conversation
* and generates apropriate callbacks to notify the application.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/
VOID ShutdownConversation(
    PCONV_INFO pcoi,
    BOOL fMakeCallback)
{
    CheckDDECritIn;

    if (pcoi->state & ST_CONNECTED) {
        pcoi->state &= ~ST_CONNECTED;

        if (IsWindow(pcoi->hwndPartner)) {
            PostMessage(pcoi->hwndPartner, WM_DDE_TERMINATE,
                    (WPARAM)pcoi->hwndConv, 0);
        }
        if (fMakeCallback && !(pcoi->pcii->afCmd & CBF_SKIP_DISCONNECTS)) {
            DoCallback(pcoi->pcii, (WORD)XTYP_DISCONNECT, 0, pcoi->hConv,
                    0, 0, 0, 0, (pcoi->state & ST_ISSELF) ? 1L : 0L);
        }
        MONCONV(pcoi, FALSE);
    }

    FreeConversationResources(pcoi);
}



/***************************************************************************\
* UnlinkConvFromOthers
*
* Description:
*
* Helper function to handle ugly cross dependency removal.  If we are
* unlinking a conversation that is going zombie, fGoingZombie is TRUE;
*
* Conversations that are going zombie are in phase 1 of a 2 phase unlink.
* Phase 1 unlinks do not remove the pcoi from its hwnd's list.
* All unlinks should result in:
*   pcoi->hConvList = 0;
*   hConvList/aServerLookup no longer refrences pcoi->hwndConv unless
*       one of the pcoi's related to hwndConv is still active.
*
*
* History:
*  3-2-92 sanfords Created.
\***************************************************************************/
VOID UnlinkConvFromOthers(
PCONV_INFO pcoi,
BOOL gGoingZombie)
{
    PCONV_INFO pcoiPrev, pcoiFirst, pcoiNow;
    PCONVLIST pcl;
    int i, cActiveInList = 0;
#ifdef TESTING
    DWORD path = 0;
#define ORPATH(x) path |= x;
#else
#define ORPATH(x)
#endif // TESTING

    CheckDDECritIn;

    /*
     * Scan pcoi linked list to get key pointers.
     */
    pcoiPrev = NULL;
    pcoiFirst = pcoiNow = (PCONV_INFO)GetWindowLongPtr(pcoi->hwndConv, GWLP_PCI);

#ifdef TESTING
    /*
     * verify that pcoi is in the conv list for this window.
     */
    while (pcoiNow != NULL) {
        if (pcoiNow == pcoi) {
            goto FoundIt;
        }
        pcoiNow = pcoiNow->next;
    }
    DebugBreak();
FoundIt:
    pcoiNow = pcoiFirst;
#endif // TESTING

    UserAssert(pcoiFirst);
    while (pcoiNow != NULL) {
        if (TypeFromHandle(pcoiNow->hConv) != HTYPE_ZOMBIE_CONVERSATION) {
            ORPATH(1);
            cActiveInList++;
        }
        if (pcoiNow->next == pcoi) {
            pcoiPrev = pcoiNow;
        }
        pcoiNow = pcoiNow->next;
    }

    ValidateAllConvLists();

    /*
     * Unlink conversation unless its going Zombie.
     */
    if (!gGoingZombie) {
        ORPATH(2);
        if (TypeFromHandle(pcoi->hConv) != HTYPE_ZOMBIE_CONVERSATION) {
            ORPATH(4);
            cActiveInList--;
        }

        if (pcoiPrev == NULL) {
            ORPATH(8);
            pcoiFirst = pcoi->next;
            SetWindowLongPtr(pcoi->hwndConv, GWLP_PCI, (LONG_PTR)pcoiFirst);
        } else {
            pcoiPrev->next = pcoi->next;
        }
    }

    UserAssert(pcoiFirst != NULL || !cActiveInList);

    if (cActiveInList == 0) {
        ORPATH(0x10);
        if (pcoi->state & ST_CLIENT) {
            ORPATH(0x20);
            if (((PCL_CONV_INFO)pcoi)->hConvList) {
                /*
                 * Remove pcoi's hwnd from its hConvList.
                 */
                pcl = (PCONVLIST)GetHandleData((HANDLE)((PCL_CONV_INFO)pcoi)->hConvList);
                for (i = 0; i < pcl->chwnd; i++) {
                    if (pcl->ahwnd[i] == pcoi->hwndConv) {
                        ORPATH(0x40);
                        pcl->chwnd--;
                        UserAssert(pcl->ahwnd[pcl->chwnd]);
                        pcl->ahwnd[i] = pcl->ahwnd[pcl->chwnd];
                        ValidateConvList(((PCL_CONV_INFO)pcoi)->hConvList);
                        break;
                    }
                }
                ORPATH(0x80);
            }
        } else {  // SERVER
            /*
             * remove server window from the service/topic lookup table.
             */
            ORPATH(0x100);
            for (i = 0; i < pcoi->pcii->cServerLookupAlloc; i++) {
                if (pcoi->pcii->aServerLookup[i].hwndServer == pcoi->hwndConv) {
                    ORPATH(0x200);
                    
                    /*
                     * Check for appcompat hack
                     */
                    if (GetAppCompatFlags2(VERMAX) & GACF2_DDE) {
                        DeleteAtom(pcoi->pcii->aServerLookup[i].laService); // delete laService
                        DeleteAtom(pcoi->pcii->aServerLookup[i].laTopic);   // delete laTopic
                    }
                    
                    if (--(pcoi->pcii->cServerLookupAlloc)) {
                        ORPATH(0x400);
                        pcoi->pcii->aServerLookup[i] =
                                pcoi->pcii->aServerLookup[pcoi->pcii->cServerLookupAlloc];
                    } else {
                        DDEMLFree(pcoi->pcii->aServerLookup);
                        pcoi->pcii->aServerLookup = NULL;
                    }
                    break;
                }
            }
        }
    }
#ifdef TESTING
      else {
        /*
         * make sure at this point we have at least one non-zombie
         */
        pcoiNow = pcoiFirst;
        while (pcoiNow != NULL) {
            if (TypeFromHandle(pcoiNow->hConv) != HTYPE_ZOMBIE_CONVERSATION) {
                goto Out;
            }
            pcoiNow = pcoiNow->next;
        }
        DebugBreak();
Out:
        ;
    }
#endif // TESTING

    ValidateAllConvLists();
    ORPATH(0x800);

    /*
     * In any case remove hConvList references from client conversation.
     */
    if (pcoi->state & ST_CLIENT) {
#ifdef TESTING
        /*
         * Verify that the hConvList that is being removed, doesn't reference
         * this window.
         */
        if (((PCL_CONV_INFO)pcoi)->hConvList && !cActiveInList) {
            BOOL fFound = FALSE;

            pcl = (PCONVLIST)GetHandleData((HANDLE)((PCL_CONV_INFO)pcoi)->hConvList);
            for (i = 0; i < pcl->chwnd; i++) {
                if (pcl->ahwnd[i] == pcoi->hwndConv) {
                    fFound = TRUE;
                    break;
                }
            }
            UserAssert(!fFound);
        }
#endif // TESTING
        ((PCL_CONV_INFO)pcoi)->hConvList = 0;
        pcoi->state &= ~ST_INLIST;
    }

    /*
     * last one out turns out the lights.
     */
    if (pcoiFirst == NULL) {
        /*
         * If the pcoi list is empty, this window can go away.
         */
        LeaveDDECrit;
        NtUserDestroyWindow(pcoi->hwndConv);
        EnterDDECrit;
    }
}





/***************************************************************************\
* FreeConversationResources
*
* Description:
* Used when: Client window is disconnected by app, Server window is
* disconnected by either side, or when a conversation is disconnected
* at Uninitialize time.
*
* This function releases all resources held by the pcoi and unlinks it
* from its host window pcoi chian. pcoi is freed once this return s.
*
* History:
* 12-21-91 sanfords Created.
\***************************************************************************/
VOID FreeConversationResources(
    PCONV_INFO pcoi)
{
    PADVISE_LINK paLink;
    PDDE_MESSAGE_QUEUE pdmq;
    PXACT_INFO pxi;

    CheckDDECritIn;

    /*
     * Don't free resources on locked conversations.
     */
    if (pcoi->cLocks > 0) {
        pcoi->state |= ST_FREE_CONV_RES_NOW;
        return;
    }

    /*
     * Don't free resources if a synchronous transaction is in effect!
     */
    pxi = pcoi->pxiOut;
    while (pxi != NULL) {
        if (pxi->flags & XIF_SYNCHRONOUS) {
            /*
             * This conversation is in a synchronous transaction.
             * Shutdown the modal loop FIRST, then call this when
             * the loop exits.
             */
            PostMessage(pcoi->hwndConv, WM_TIMER, TID_TIMEOUT, 0);
            pcoi->state |= ST_FREE_CONV_RES_NOW;
            return;
        }
        pxi = pxi->next;
    }

    /*
     * If this is an Intra-Process conversation that hasn't yet received
     * a terminate message, make it a zombie.  We will call this routine
     * again once the terminate arrives or when WaitForZombieTerminate() has
     * timed out waiting.
     */
    if (pcoi->state & ST_INTRA_PROCESS && !(pcoi->state & ST_TERMINATE_RECEIVED)) {
        DestroyHandle((HANDLE)pcoi->hConv);
        pcoi->hConv = (HCONV)CreateHandle((ULONG_PTR)pcoi, HTYPE_ZOMBIE_CONVERSATION,
                InstFromHandle(pcoi->hConv));
        UnlinkConvFromOthers(pcoi, TRUE);
        return;
    }

    /*
     * remove any transactions left in progress
     */
    while (pcoi->pxiOut != NULL) {
        (pcoi->pxiOut->pfnResponse)(pcoi->pxiOut, 0, 0);
    }

    /*
     * Throw away any incoming queued DDE messages.
     */
    while (pcoi->dmqOut != NULL) {

        pdmq = pcoi->dmqOut;
        DumpDDEMessage(!(pcoi->state & ST_INTRA_PROCESS), pdmq->msg, pdmq->lParam);
        pcoi->dmqOut = pcoi->dmqOut->next;
        if (pcoi->dmqOut == NULL) {
            pcoi->dmqIn = NULL;
        }
        DDEMLFree(pdmq);
    }

    //
    // Remove all link info
    //
    paLink = pcoi->aLinks;
    while (pcoi->cLinks) {
        if (pcoi->state & ST_CLIENT) {
            MONLINK(pcoi->pcii, FALSE, paLink->wType & XTYPF_NODATA,
                    pcoi->laService, pcoi->laTopic,
                    LocalToGlobalAtom(paLink->laItem),
                    paLink->wFmt, FALSE,
                    (HCONV)pcoi->hwndPartner, (HCONV)pcoi->hwndConv);
        } else {
            MONLINK(pcoi->pcii, FALSE, paLink->wType & XTYPF_NODATA,
                    pcoi->laService, pcoi->laTopic,
                    LocalToGlobalAtom(paLink->laItem),
                    paLink->wFmt, TRUE,
                    (HCONV)pcoi->hwndConv, (HCONV)pcoi->hwndPartner);
        }
        if (!(pcoi->state & ST_CLIENT)) {
            DeleteLinkCount(pcoi->pcii, paLink->pLinkCount);
        }
        DeleteAtom(paLink->laItem); // link structure copy
        paLink++;
        pcoi->cLinks--;
    }
    if (pcoi->aLinks) {
        DDEMLFree(pcoi->aLinks);
    }

    //
    // free atoms associated with this conv
    //
    DeleteAtom(pcoi->laService);
    DeleteAtom(pcoi->laTopic);
    if (pcoi->laServiceRequested) {
        DeleteAtom(pcoi->laServiceRequested);
    }

    UnlinkConvFromOthers(pcoi, FALSE);

    /*
     * invalidate app's conversation handle
     */
    DestroyHandle((HANDLE)pcoi->hConv);

    DDEMLFree(pcoi);
}



BOOL WaitForZombieTerminate(
HANDLE hData)
{
    PCONV_INFO pcoi;
    MSG msg;
    HWND hwnd;
    BOOL fTerminated;
    DWORD fRet = 0;

    CheckDDECritOut;
    EnterDDECrit;

    fTerminated = FALSE;
    while ((pcoi = (PCONV_INFO)ValidateCHandle(hData,
            HTYPE_ZOMBIE_CONVERSATION, InstFromHandle(hData))) != NULL &&
            !(pcoi->state & ST_TERMINATE_RECEIVED)) {
        hwnd = pcoi->hwndConv;
        LeaveDDECrit;
        while (PeekMessage(&msg, hwnd, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)) {
            DispatchMessage(&msg);
            if (msg.message == WM_DDE_TERMINATE) {
                fTerminated = TRUE;
            }
        }
        if (!fTerminated) {
            fRet = MsgWaitForMultipleObjectsEx(0, NULL, 100, QS_POSTMESSAGE, 0);
            if (fRet == 0xFFFFFFFF) {
                RIPMSG0(RIP_WARNING, "WaitForZombieTerminate: I give up - faking terminate.");
                ProcessTerminateMsg(pcoi, pcoi->hwndPartner);
                EnterDDECrit;
                return(FALSE);
            }
        }
        EnterDDECrit;
    }
    LeaveDDECrit;
    return(TRUE);
}