/****************************** Module Header ******************************\ * Module Name: DMGDDE.C * * This module contains functions used for interfacing with DDE structures * and such. * * Created: 12/23/88 sanfords * * Copyright (c) 1988, 1989 Microsoft Corporation \***************************************************************************/ #include "ddemlp.h" VOID FreeDdeMsgData(WORD msg, LPARAM lParam); UINT EmptyQueueTimerId = 0; /***************************** Private Function ****************************\ * timeout() * * This routine creates a timer for hwndTimeout. It then runs a modal loop * which will exit once the pai->wTimeoutStatus word indicates things are * either done (TOS_DONE), aborted (TOS_ABORT), or the system is shutting * down (TOS_SHUTDOWN). A values of TOS_TICK is used to support timouts * >64K in length. * * Returns fSuccess, ie TRUE if TOS_DONE was received. before TOS_ABORT. * * PUBDOC START * Synchronous client transaction modal loops: * * During Synchronous transactions, a client application will enter a modal * loop while waiting for the server to respond to the request. If an * application wishes to filter messages to the modal loop, it may do so * by setting a message filter tied to MSGF_DDEMGR. Applications should * be aware however that the DDEMGR modal loop processes private messages * in the WM_USER range, WM_DDE messages, and WM_TIMER messages with timer IDs * using the TID_ constants defined in ddeml.h. * These messages must not be filtered by an application!!! * * PUBDOC END * * History: * Created sanfords 12/19/88 \***************************************************************************/ BOOL timeout( PAPPINFO pai, DWORD ulTimeout, HWND hwndTimeout) { MSG msg; PAPPINFO paiT; SEMENTER(); /* * We check all instances in this task (thread) since we cannot let * one thread enter a modal loop two levels deep. */ paiT = NULL; while (paiT = GetCurrentAppInfo(paiT)) { if (paiT->hwndTimer) { SETLASTERROR(pai, DMLERR_REENTRANCY); AssertF(FALSE, "Recursive timeout call"); SEMLEAVE(); return(FALSE); } } pai->hwndTimer = hwndTimeout; SEMLEAVE(); if (!SetTimer(hwndTimeout, TID_TIMEOUT, ulTimeout > 0xffffL ? 0xffff : (WORD)ulTimeout, NULL)) { SETLASTERROR(pai, DMLERR_SYS_ERROR); return(FALSE); } if (ulTimeout < 0xffff0000) { ulTimeout += 0x00010000; } // // We use this instance-wide global variable to note timeouts so that // we don't need to rely on PostMessage() to work when faking timeouts. // do { ulTimeout -= 0x00010000; if (ulTimeout <= 0xffffL) { // the last timeout should be shorter than 0xffff SetTimer(hwndTimeout, TID_TIMEOUT, (WORD)ulTimeout, NULL); } pai->wTimeoutStatus = TOS_CLEAR; /* * stay in modal loop until a timeout happens. */ while (pai->wTimeoutStatus == TOS_CLEAR) { if (!GetMessage(&msg, (HWND)NULL, 0, 0)) { /* * Somebody posted a WM_QUIT message - get out of this * timer loop and repost so main loop gets it. This * fixes a bug where some apps (petzolds ShowPop) use * rapid synchronous transactions which interfere with * their proper closing. */ pai->wTimeoutStatus = TOS_ABORT; PostMessage(msg.hwnd, WM_QUIT, 0, 0); } else { if (!CallMsgFilter(&msg, MSGF_DDEMGR)) DispatchMessage(&msg); } } } while (pai->wTimeoutStatus == TOS_TICK && HIWORD(ulTimeout)); KillTimer(hwndTimeout, TID_TIMEOUT); // // remove any remaining timeout message in the queue. // while (PeekMessage(&msg, hwndTimeout, WM_TIMER, WM_TIMER, PM_NOYIELD | PM_REMOVE)) { if (msg.message == WM_QUIT) { /* * Windows BUG: This call will succeed on WM_QUIT messages! */ PostQuitMessage(0); break; } } SEMENTER(); pai->hwndTimer = 0; SEMLEAVE(); /* * post a callback check incase we blocked callbacks due to being * in a timeout. */ if (!PostMessage(pai->hwndDmg, UM_CHECKCBQ, 0, (DWORD)(LPSTR)pai)) { SETLASTERROR(pai, DMLERR_SYS_ERROR); } return(TRUE); } /***************************** Private Function ****************************\ * Allocates global DDE memory and fills in first two words with fsStatus * and wFmt. * * History: created 6/15/90 rich gartland \***************************************************************************/ HANDLE AllocDDESel(fsStatus, wFmt, cbData) WORD fsStatus; WORD wFmt; DWORD cbData; { HANDLE hMem = NULL; DDEDATA FAR * pMem; SEMENTER(); if (!cbData) cbData++; // fixes GLOBALALLOC bug where 0 size object allocation fails if ((hMem = GLOBALALLOC(GMEM_DDESHARE, cbData))) { pMem = (DDEDATA FAR * )GLOBALPTR(hMem); *(WORD FAR * )pMem = fsStatus; pMem->cfFormat = wFmt; } SEMLEAVE(); return(hMem); } /***************************** Private Function ****************************\ * This routine institutes a callback directly if psi->fEnableCB is set * and calls QReply to complete the transaction, * otherwise it places the data into the queue for processing. * * Since hData may be freed by the app at any time once the callback is * issued, we cannot depend on it being there for QReply. Therefore we * save all the pertinant data in the queue along with it. * * Returns fSuccess. * * History: * Created 9/12/89 Sanfords \***************************************************************************/ BOOL MakeCallback( PCOMMONINFO pcoi, HCONV hConv, HSZ hszTopic, HSZ hszItem, WORD wFmt, WORD wType, HDDEDATA hData, DWORD dwData1, DWORD dwData2, WORD msg, WORD fsStatus, HWND hwndPartner, HANDLE hMemFree, BOOL fQueueOnly) { PCBLI pcbli; SEMENTER(); pcbli = (PCBLI)NewLstItem(pcoi->pai->plstCB, ILST_LAST); if (pcbli == NULL) { SETLASTERROR(pcoi->pai, DMLERR_MEMORY_ERROR); SEMLEAVE(); return(FALSE); } pcbli->hConv = hConv; pcbli->hszTopic = hszTopic; pcbli->hszItem = hszItem; pcbli->wFmt = wFmt; pcbli->wType = wType; pcbli->hData = hData; pcbli->dwData1 = dwData1; pcbli->dwData2 = dwData2; pcbli->msg = msg; pcbli->fsStatus = fsStatus; pcbli->hwndPartner = hwndPartner; pcbli->hMemFree = hMemFree; pcbli->pai = pcoi->pai; pcbli->fQueueOnly = fQueueOnly; SEMLEAVE(); if (!(pcoi->fs & ST_BLOCKED)) if (!PostMessage(pcoi->pai->hwndDmg, UM_CHECKCBQ, 0, (DWORD)(LPSTR)pcoi->pai)) { SETLASTERROR(pcoi->pai, DMLERR_SYS_ERROR); } #ifdef DEBUG if (hMemFree) { LogDdeObject(0xB000, hMemFree); } #endif return(TRUE); } #define MAX_PMRETRIES 3 // // This routine extends the size of the windows message queue by queueing // up failed posts on the sender side. This avoids the problems of full // client queues and of windows behavior of giving DDE messages priority. // BOOL PostDdeMessage( PCOMMONINFO pcoi, // senders COMMONINFO WORD msg, HWND hwndFrom, // == wParam LONG lParam, WORD msgAssoc, HGLOBAL hAssoc) { LPMQL pmql; PPMQI ppmqi; int cTries; HANDLE hTaskFrom, hTaskTo; HWND hwndTo; PQST pMQ; hwndTo = (HWND)pcoi->hConvPartner; if (!IsWindow(hwndTo)) { return(FALSE); } hTaskTo = GetWindowTask(hwndTo); /* * locate message overflow queue for our target task (pMQ) */ for (pmql = gMessageQueueList; pmql; pmql = pmql->next) { if (pmql->hTaskTo == hTaskTo) { break; } } if (pmql != NULL) { pMQ = pmql->pMQ; } else { pMQ = NULL; } /* * See if any messages are already queued up */ if (pMQ && pMQ->cItems) { if (msg == WM_DDE_TERMINATE) { /* * remove any non-terminate queued messages from us to them. */ ppmqi = (PPMQI)FindNextQi(pMQ, NULL, FALSE); while (ppmqi) { FreeDdeMsgData(ppmqi->msg, ppmqi->lParam); FreeDdeMsgData(ppmqi->msgAssoc, MAKELPARAM(ppmqi->hAssoc, ppmqi->hAssoc)); ppmqi = (PPMQI)FindNextQi(pMQ, (PQUEUEITEM)ppmqi, ppmqi->hwndTo == hwndTo && ppmqi->wParam == hwndFrom); } pMQ = NULL; // so we just post it } else { // add the latest post attempt ppmqi = (PPMQI)Addqi(pMQ); if (ppmqi == NULL) { SETLASTERROR(pcoi->pai, DMLERR_MEMORY_ERROR); return(FALSE); // out of memory } ppmqi->hwndTo = hwndTo; ppmqi->msg = msg; ppmqi->wParam = hwndFrom; ppmqi->lParam = lParam; ppmqi->hAssoc = hAssoc; ppmqi->msgAssoc = msgAssoc; } } if (pMQ == NULL || pMQ->cItems == 0) { // just post the given message - no queue involved. cTries = 0; hTaskFrom = GetWindowTask(hwndFrom); while (!PostMessage(hwndTo, msg, hwndFrom, lParam)) { /* * we yielded so recheck target window */ if (!IsWindow(hwndTo)) { return(FALSE); } /* * Give reciever a chance to clean out his queue */ if (hTaskTo != hTaskFrom) { Yield(); } else if (!(pcoi->pai->wFlags & AWF_INPOSTDDEMSG)) { MSG msgs; PAPPINFO pai; pcoi->pai->wFlags |= AWF_INPOSTDDEMSG; /* * Reciever is US! * * We need to empty our queue of stuff so we can post more * to ourselves. */ while (PeekMessage((MSG FAR *)&msgs, NULL, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)) { DispatchMessage((MSG FAR *)&msgs); } /* * tell all instances in this task to process their * callbacks so we can clear our queue. */ for (pai = pAppInfoList; pai != NULL; pai = pai->next) { if (pai->hTask == hTaskFrom) { CheckCBQ(pai); } } pcoi->pai->wFlags &= ~AWF_INPOSTDDEMSG; } if (cTries++ > MAX_PMRETRIES) { /* * relocate message overflow queue for our target task (pMQ) * We need to do this again because we gave up control * with the dispatch message and CheckCBQ calls. */ for (pmql = gMessageQueueList; pmql; pmql = pmql->next) { if (pmql->hTaskTo == hTaskTo) { break; } } if (pmql == NULL) { /* * create and link in a new queue for the target task */ pmql = (LPMQL)FarAllocMem(hheapDmg, sizeof(MQL)); if (pmql == NULL) { SETLASTERROR(pcoi->pai, DMLERR_MEMORY_ERROR); return(FALSE); } pmql->pMQ = CreateQ(sizeof(PMQI)); if (pmql->pMQ == NULL) { FarFreeMem(pmql); SETLASTERROR(pcoi->pai, DMLERR_MEMORY_ERROR); return(FALSE); } pmql->hTaskTo = hTaskTo; pmql->next = gMessageQueueList; gMessageQueueList = pmql; } pMQ = pmql->pMQ; ppmqi = (PPMQI)Addqi(pMQ); if (ppmqi == NULL) { SETLASTERROR(pcoi->pai, DMLERR_MEMORY_ERROR); return(FALSE); // out of memory } ppmqi->hwndTo = hwndTo; ppmqi->msg = msg; ppmqi->wParam = hwndFrom; ppmqi->lParam = lParam; ppmqi->hAssoc = hAssoc; ppmqi->msgAssoc = msgAssoc; return(TRUE); } } #ifdef DEBUG LogDdeObject(msg | 0x1000, lParam); if (msgAssoc) { LogDdeObject(msgAssoc | 0x9000, MAKELPARAM(hAssoc, hAssoc)); } #endif return(TRUE); } // come here if the queue exists - empty it as far as we can. EmptyDDEPostQ(); return(TRUE); } // // EmptyDDEPost // // This function checks the DDE post queue list and emptys it as far as // possible. // BOOL EmptyDDEPostQ() { PPMQI ppmqi; LPMQL pPMQL, pPMQLPrev; PQST pMQ; BOOL fMoreToDo = FALSE; pPMQLPrev = NULL; pPMQL = gMessageQueueList; while (pPMQL) { pMQ = pPMQL->pMQ; while (pMQ->cItems) { ppmqi = (PPMQI)Findqi(pMQ, QID_OLDEST); if (!PostMessage(ppmqi->hwndTo, ppmqi->msg, ppmqi->wParam, ppmqi->lParam)) { if (IsWindow(ppmqi->hwndTo)) { fMoreToDo = TRUE; break; // skip to next target queue } else { FreeDdeMsgData(ppmqi->msg, ppmqi->lParam); FreeDdeMsgData(ppmqi->msgAssoc, MAKELPARAM(ppmqi->hAssoc, ppmqi->hAssoc)); } } else { #ifdef DEBUG LogDdeObject(ppmqi->msg | 0x2000, ppmqi->lParam); if (ppmqi->msgAssoc) { LogDdeObject(ppmqi->msgAssoc | 0xA000, MAKELPARAM(ppmqi->hAssoc, ppmqi->hAssoc)); } #endif } Deleteqi(pMQ, QID_OLDEST); } if (pMQ->cItems == 0) { /* * Delete needless queue (selector) */ DestroyQ(pMQ); if (pPMQLPrev) { pPMQLPrev->next = pPMQL->next; FarFreeMem(pPMQL); pPMQL = pPMQLPrev; } else { gMessageQueueList = gMessageQueueList->next; FarFreeMem(pPMQL); pPMQL = gMessageQueueList; continue; } } pPMQLPrev = pPMQL; pPMQL = pPMQL->next; } if (fMoreToDo & !EmptyQueueTimerId) { EmptyQueueTimerId = SetTimer(NULL, TID_EMPTYPOSTQ, TIMEOUT_QUEUECHECK, (TIMERPROC)EmptyQTimerProc); } return(fMoreToDo); } /* * Used to asynchronously check overflow message queues w/o using PostMessage() */ void CALLBACK EmptyQTimerProc( HWND hwnd, UINT msg, UINT tid, DWORD dwTime) { KillTimer(NULL, EmptyQueueTimerId); EmptyQueueTimerId = 0; EmptyDDEPostQ(); }