Пример #1
0
/***************************************************************************\
* DestroyInstance
*
* Description:
* Removes an instance from the aInstance table. This does nothing for
* the server side instance info.
*
* History:
* 11-19-91 sanfords Created.
\***************************************************************************/
HANDLE DestroyInstance(
int hInstClient)
{
    register HANDLE hInstServerRet = 0;

    DestroyHandle((HANDLE)hInstClient);
    hInstServerRet = aInstance[InstFromHandle(hInstClient)];
    aInstance[InstFromHandle(hInstClient)] = (HANDLE)iFirstFreeInst;
    iFirstFreeInst = InstFromHandle(hInstClient);

    return (hInstServerRet);
}
Пример #2
0
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);
}
Пример #3
0
/***************************************************************************\
* DdeUninitialize (DDEML API)
*
* Description:
* Shuts down a DDEML instance and frees all associated resources.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/
BOOL DdeUninitialize(
DWORD idInst)
{
    PCL_INSTANCE_INFO pcii, pciiPrev;
    BOOL fRet = FALSE;

    CheckDDECritOut;
    EnterDDECrit;

    pcii = ValidateInstance((HANDLE)LongToHandle( idInst ));
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    /*
     * If this thread is in the middle of a synchronous transaction or
     * a callback, we need to back out of those first.
     */
    if ((pcii->flags & IIF_IN_SYNC_XACT) || pcii->cInDDEMLCallback) {
        pcii->afCmd |= APPCMD_UNINIT_ASAP;
        fRet = TRUE;
        goto Exit;
    }

    ApplyFunctionToObjects(HTYPE_CONVERSATION_LIST, InstFromHandle(pcii->hInstClient),
            (PFNHANDLEAPPLY)DdeDisconnectList);
    ApplyFunctionToObjects(HTYPE_CLIENT_CONVERSATION, InstFromHandle(pcii->hInstClient),
            (PFNHANDLEAPPLY)DdeDisconnect);
    ApplyFunctionToObjects(HTYPE_SERVER_CONVERSATION, InstFromHandle(pcii->hInstClient),
            (PFNHANDLEAPPLY)DdeDisconnect);
    ApplyFunctionToObjects(HTYPE_ZOMBIE_CONVERSATION, InstFromHandle(pcii->hInstClient),
            (PFNHANDLEAPPLY)WaitForZombieTerminate);
    ApplyFunctionToObjects(HTYPE_DATA_HANDLE, InstFromHandle(pcii->hInstClient),
            (PFNHANDLEAPPLY)ApplyFreeDataHandle);

    LeaveDDECrit;
    NtUserCallOneParam((ULONG_PTR)pcii->hInstServer, SFI__CSDDEUNINITIALIZE);
    NtUserDestroyWindow(pcii->hwndMother);
    EnterDDECrit;

    DDEMLFree(pcii->plaNameService);
    DestroyInstance(pcii->hInstClient);

    // unlink pcii from pciiList

    if (pciiList == pcii) {
        pciiList = pciiList->next;
    } else {
        for (pciiPrev = pciiList; pciiPrev != NULL && pciiPrev->next != pcii;
                pciiPrev = pciiPrev->next) {
            ;
        }
        if (pciiPrev != NULL) {
            pciiPrev->next = pcii->next;
        }
    }
    DDEMLFree(pcii);
    fRet = TRUE;

Exit:
    LeaveDDECrit;
    return (fRet);
}
Пример #4
0
/***************************************************************************\
* DdeEnableCallback (DDEML API)
*
* Description:
* Turns on and off asynchronous callbacks (BLOCKABLE).
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/
BOOL DdeEnableCallback(
DWORD idInst,
HCONV hConv,
UINT wCmd)
{
    BOOL fRet = FALSE;
    PCL_INSTANCE_INFO pcii;
    PCONV_INFO pcoi;
    ENABLE_ENUM_STRUCT ees;

    EnterDDECrit;

    pcii = (PCL_INSTANCE_INFO)ValidateInstance((HANDLE)idInst);
    if (pcii == NULL) {
        BestSetLastDDEMLError(DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    switch (wCmd) {
    case EC_QUERYWAITING:
    case EC_DISABLE:
    case EC_ENABLEONE:
    case EC_ENABLEALL:
        break;

    default:
        SetLastDDEMLError(pcii, DMLERR_INVALIDPARAMETER);
        goto Exit;
    }

    if (hConv) {
        pcoi = (PCONV_INFO)ValidateCHandle((HANDLE)hConv,
                HTYPE_CLIENT_CONVERSATION, InstFromHandle(idInst));
        if (pcoi == NULL) {
            pcoi = (PCONV_INFO)ValidateCHandle((HANDLE)hConv,
                    HTYPE_SERVER_CONVERSATION, InstFromHandle(idInst));
        }
        if (pcoi == NULL) {
            SetLastDDEMLError(pcii, DMLERR_INVALIDPARAMETER);
            goto Exit;
        }
        pcoi->cLocks++;
        fRet = SetEnableState(pcoi, wCmd);
        switch (wCmd) {
        case EC_ENABLEALL:
        case EC_ENABLEONE:
            CheckForQueuedMessages(pcoi);
        }
        pcoi->cLocks--;
        if (pcoi->cLocks == 0 && pcoi->state & ST_FREE_CONV_RES_NOW) {
            FreeConversationResources(pcoi);
        }
    } else {
        if (wCmd == EC_ENABLEONE) {
            wCmd = EC_ENABLEONEOFALL;
        }
        switch (wCmd) {
        case EC_ENABLEONEOFALL:
            pcii->ConvStartupState = ST_BLOCKNEXT | ST_BLOCKALLNEXT;
            break;

        case EC_DISABLE:
            pcii->ConvStartupState = ST_BLOCKED;
            break;

        case EC_ENABLEALL:
            pcii->ConvStartupState = 0;
            break;
        }
        ees.pfRet = &fRet;
        ees.wCmd = wCmd;
        switch (wCmd) {
        case EC_ENABLEALL:
            ees.wCmd2 = EC_CHECKQUEUE;
            break;

        case EC_ENABLEONEOFALL:
            ees.wCmd2 = EC_CHECKQUEUEONCE;
            break;

        default:
            ees.wCmd2 = 0;
        }
        EnumChildWindows(pcii->hwndMother, (WNDENUMPROC)EnableEnumProc,
                (LONG)&ees);
    }

Exit:
    LeaveDDECrit;
    return (fRet);
}
Пример #5
0
/***************************************************************************\
* DdeQueryNextServer (DDEML API)
*
* Description:
* Enumerates conversations within a list.
*
* History:
* 11-12-91 sanfords Created.
\***************************************************************************/
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)GetWindowLong(pcl->ahwnd[0], GWL_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)GetWindowLong(phwnd[i + 1], GWL_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);
}
Пример #6
0
/***************************************************************************\
* 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(EXCEPTION_EXECUTE_HANDLER) {
        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);
}
Пример #7
0
/***************************************************************************\
* 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.
\***************************************************************************/
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)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((DWORD)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, (DWORD)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)
                        GetWindowLong(pcl->ahwnd[i], GWL_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);
}
Пример #8
0
/***************************************************************************\
* 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((DWORD)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 incomming 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);
}