527 lines
15 KiB
C
527 lines
15 KiB
C
/****************************** 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();
|
|
}
|