|
|
1.1 ! root 1: /*************************************************************************** ! 2: * * ! 3: * MODULE : dde.c * ! 4: * * ! 5: * PURPOSE : Contains routines for handling of DDE interaction with * ! 6: * DDEML. * ! 7: * * ! 8: ***************************************************************************/ ! 9: #include "client.h" ! 10: #include <string.h> ! 11: #include <memory.h> ! 12: #include "infoctrl.h" ! 13: ! 14: CHAR szT[100]; ! 15: ! 16: /**************************************************************************** ! 17: * * ! 18: * FUNCTION : CreateXactionWindow() * ! 19: * * ! 20: * PURPOSE : Creates a transaction window for the given transaction * ! 21: * under the given conversation window. * ! 22: * * ! 23: * RETURNS : TRUE - If successful. * ! 24: * FALSE - otherwise. * ! 25: * * ! 26: ****************************************************************************/ ! 27: HWND CreateXactionWindow( ! 28: HWND hwndMDI, ! 29: XACT *pxact) ! 30: { ! 31: PSTR pszFmt, pszItem; ! 32: PSTR pData; ! 33: HWND hwnd; ! 34: ! 35: pszItem = GetHSZName(pxact->hszItem); ! 36: pszFmt = GetFormatName(pxact->wFmt); ! 37: pData = GetTextData(pxact->hDdeData); ! 38: ! 39: /* ! 40: * �type/opts������� ITEM ���������������retĿ GWL_USER=pxact ! 41: * � � ! 42: * � � ! 43: * � � ! 44: * � � ! 45: * � DATA � ! 46: * � � ! 47: * � � ! 48: * � � ! 49: * � � ! 50: * �state/error����� FORMAT ����������Result�� ! 51: */ ! 52: hwnd = CreateInfoCtrl((LPSTR)pData, ! 53: (INT)SendMessage(hwndMDI, UM_GETNEXTCHILDX, 0, 0L), ! 54: (INT)SendMessage(hwndMDI, UM_GETNEXTCHILDY, 0, 0L), ! 55: 200, 100, hwndMDI, hInst, ! 56: Type2String(pxact->wType, pxact->fsOptions), pszItem, NULL, ! 57: "Starting", (LPSTR)pszFmt, NULL, ! 58: ICSTY_SHOWFOCUS, 0, (DWORD)(LPSTR)pxact); ! 59: MyFree(pszItem); ! 60: MyFree(pszFmt); ! 61: MyFree(pData); ! 62: return(hwnd); ! 63: } ! 64: ! 65: ! 66: ! 67: ! 68: /**************************************************************************** ! 69: * * ! 70: * FUNCTION : ProcessTransaction() * ! 71: * * ! 72: * PURPOSE : Processes synchronous transactions entirely and starts * ! 73: * async transactions. Transaction attempts result in a * ! 74: * transaction window being created which displays the state * ! 75: * or results of the transaction. (the callback function * ! 76: * updates these windows as it gets calls) Transaction * ! 77: * windows stay around until abandoned by the user or until * ! 78: * the conversation is disconnected. Advise Data and Advise * ! 79: * Stop transactions are special. We don't create a new * ! 80: * window if the associated advise start transaction window * ! 81: * can be found. * ! 82: * * ! 83: * RETURNS : TRUE - If successful. * ! 84: * FALSE - otherwise. * ! 85: * * ! 86: ****************************************************************************/ ! 87: BOOL ProcessTransaction( ! 88: XACT *pxact) ! 89: { ! 90: CONVINFO ci; ! 91: HWND hwndInfoCtrl = 0; ! 92: ! 93: /* create transaction window to show we tried (except in ADVSTOP case) */ ! 94: ! 95: pxact = (XACT *)memcpy(MyAlloc(sizeof(XACT)), (PSTR)pxact, sizeof(XACT)); ! 96: ci.cb = sizeof(CONVINFO); ! 97: DdeQueryConvInfo(pxact->hConv, QID_SYNC, &ci); // ci.hUser==hConv ! 98: if (pxact->wType == XTYP_ADVSTOP) { ! 99: hwndInfoCtrl = FindAdviseChild((HWND)ci.hUser, pxact->hszItem, ! 100: pxact->wFmt); ! 101: if (hwndInfoCtrl) { ! 102: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UL, ! 103: (DWORD)(LPSTR)Type2String(pxact->wType, pxact->fsOptions)); ! 104: DdeFreeStringHandle(idInst, pxact->hszItem); ! 105: } ! 106: } ! 107: /* ! 108: * If we still need to create a transaction window, do so here. ! 109: */ ! 110: if (!hwndInfoCtrl) { ! 111: hwndInfoCtrl = CreateXactionWindow((HWND)ci.hUser, pxact); ! 112: if (!hwndInfoCtrl) { ! 113: MyFree(pxact); ! 114: return 0; ! 115: } ! 116: SetFocus(hwndInfoCtrl); ! 117: } ! 118: /* ! 119: * Disable callbacks for this conversation now if the XOPT_DISABLEFIRST ! 120: * option is set. This tests disabling asynchronous transactions ! 121: * before they are completed. ! 122: */ ! 123: if (pxact->fsOptions & XOPT_DISABLEFIRST) ! 124: DdeEnableCallback(idInst, pxact->hConv, EC_DISABLE); ! 125: /* ! 126: * Adjust the timeout for asynchronous transactions. ! 127: */ ! 128: if (pxact->fsOptions & XOPT_ASYNC) ! 129: pxact->ulTimeout = TIMEOUT_ASYNC; ! 130: ! 131: /* ! 132: * start transaction with DDEML here ! 133: */ ! 134: pxact->ret = DdeClientTransaction((LPBYTE)pxact->hDdeData, 0xFFFFFFFF, ! 135: pxact->hConv, pxact->hszItem, pxact->wFmt, ! 136: pxact->wType, ! 137: pxact->ulTimeout, (LPDWORD)&pxact->Result); ! 138: ! 139: /* ! 140: * show return value in transaction window ! 141: */ ! 142: wsprintf(szT, "ret=%lx", pxact->ret); ! 143: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UR, (DWORD)(LPSTR)szT); ! 144: ! 145: /* ! 146: * show result or ID value in transaction window ! 147: */ ! 148: wsprintf(szT, pxact->fsOptions & XOPT_ASYNC ? "ID=%ld" : ! 149: "result=0x%lx", pxact->Result); ! 150: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LR, (DWORD)(LPSTR)szT); ! 151: ! 152: if ((pxact->fsOptions & XOPT_ASYNC) && pxact->ret) { ! 153: /* ! 154: * asynchronous successful start - link transaction to window. ! 155: */ ! 156: DdeSetUserHandle(pxact->hConv, pxact->Result, (DWORD)hwndInfoCtrl); ! 157: ! 158: /* ! 159: * Abandon started async transaction after initiated if ! 160: * XOPT_ABANDONAFTERSTART is chosen. This tests the mid-transaction ! 161: * abandoning code. ! 162: */ ! 163: if (pxact->fsOptions & XOPT_ABANDONAFTERSTART) ! 164: DdeAbandonTransaction(idInst, pxact->hConv, pxact->Result); ! 165: /* ! 166: * show actual status ! 167: */ ! 168: ci.cb = sizeof(CONVINFO); ! 169: DdeQueryConvInfo(pxact->hConv, pxact->Result, &ci); ! 170: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL, ! 171: (DWORD)(LPSTR)State2String(ci.wConvst)); ! 172: } else { ! 173: /* ! 174: * Synchronous transactions are completed already so pass on to ! 175: * CompleteTransaction right away. ! 176: */ ! 177: CompleteTransaction(hwndInfoCtrl, pxact); ! 178: } ! 179: return TRUE; ! 180: } ! 181: ! 182: ! 183: ! 184: ! 185: ! 186: /**************************************************************************** ! 187: * * ! 188: * FUNCTION : CompleteTransaction() * ! 189: * * ! 190: * PURPOSE : This handles completed synchronous and asynchronous * ! 191: * transactions as well as failed attempted transactions. * ! 192: * * ! 193: * RETURNS : TRUE - If successful. * ! 194: * FALSE - otherwise. * ! 195: * * ! 196: ****************************************************************************/ ! 197: VOID CompleteTransaction( ! 198: HWND hwndInfoCtrl, ! 199: XACT *pxact) ! 200: { ! 201: PSTR psz; ! 202: ! 203: if (pxact->ret) { ! 204: /* ! 205: * Successful transaction case ! 206: */ ! 207: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL, ! 208: (DWORD)(LPSTR)"Completed"); ! 209: ! 210: if (pxact->wType == XTYP_REQUEST) { ! 211: /* ! 212: * Show resulting data ! 213: */ ! 214: psz = GetTextData((HDDEDATA)pxact->ret); ! 215: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_CENTER, ! 216: (DWORD)(LPSTR)psz); ! 217: MyFree(psz); ! 218: /* ! 219: * free returned data since it is displayed. ! 220: */ ! 221: DdeFreeDataHandle(pxact->ret); ! 222: pxact->ret = 0L; ! 223: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_UR, NULL); ! 224: } ! 225: } else { ! 226: /* ! 227: * failed - show error result. ! 228: */ ! 229: SendMessage(hwndInfoCtrl, ICM_SETSTRING, ICSID_LL, ! 230: (DWORD)(LPSTR)Error2String(DdeGetLastError(idInst))); ! 231: } ! 232: pxact->fsOptions |= XOPT_COMPLETED; ! 233: } ! 234: ! 235: ! 236: ! 237: ! 238: /**************************************************************************** ! 239: * * ! 240: * FUNCTION : DdeCallback() * ! 241: * * ! 242: * PURPOSE : This handles all callbacks from the DDEML. This handles * ! 243: * updating of the associated conversation and any special * ! 244: * testing cases such as blocking callbacks etc. * ! 245: * * ! 246: * For the most part, clients only handle advise data and * ! 247: * asynchronous transaction completion here. * ! 248: * * ! 249: * RETURNS : Results vary depending on transaction type. * ! 250: * * ! 251: ****************************************************************************/ ! 252: HDDEDATA CALLBACK DdeCallback( ! 253: UINT wType, ! 254: UINT wFmt, ! 255: HCONV hConv, ! 256: HSZ hsz1, ! 257: HSZ hsz2, ! 258: HDDEDATA hData, ! 259: DWORD lData1, ! 260: DWORD lData2) ! 261: { ! 262: HWND hwnd; ! 263: CONVINFO ci; ! 264: XACT *pxact; ! 265: ! 266: if (hConv) { ! 267: /* ! 268: * update conversation status if it changed. ! 269: */ ! 270: MYCONVINFO *pmci; ! 271: ! 272: ci.cb = sizeof(CONVINFO); ! 273: if (!DdeQueryConvInfo(hConv, QID_SYNC, &ci) || (!IsWindow((HWND)ci.hUser))) { ! 274: /* ! 275: * This conversation does not yet have a corresponding MDI window ! 276: * or is disconnected. ! 277: */ ! 278: return 0; ! 279: } ! 280: if (pmci = (MYCONVINFO *)GetWindowLong((HWND)ci.hUser, 0)) { ! 281: if (pmci->ci.wStatus != ci.wStatus || ! 282: pmci->ci.wConvst != ci.wConvst || ! 283: pmci->ci.wLastError != ci.wLastError) { ! 284: /* ! 285: * Things have changed, updated the conversation window. ! 286: */ ! 287: InvalidateRect((HWND)ci.hUser, NULL, TRUE); ! 288: } ! 289: if (ci.wConvst & ST_INLIST) { ! 290: /* ! 291: * update the associated list window (if any) as well. ! 292: */ ! 293: if (hwnd = FindListWindow(ci.hConvList)) ! 294: InvalidateRect(hwnd, NULL, TRUE); ! 295: } ! 296: } ! 297: } ! 298: ! 299: /* ! 300: * handle special block on next callback option here. This demonstrates ! 301: * the CBR_BLOCK feature. ! 302: */ ! 303: if (fBlockNextCB && !(wType & XTYPF_NOBLOCK)) { ! 304: fBlockNextCB = FALSE; ! 305: return(CBR_BLOCK); ! 306: } ! 307: ! 308: /* ! 309: * handle special termination here. This demonstrates that at any time ! 310: * a client can drop a conversation. ! 311: */ ! 312: if (fTermNextCB && hConv && wType != XTYP_DISCONNECT) { ! 313: fTermNextCB = FALSE; ! 314: MyDisconnect(hConv); ! 315: return(0); ! 316: } ! 317: ! 318: /* ! 319: * Now we begin sort out what to do. ! 320: */ ! 321: switch (wType) { ! 322: case XTYP_REGISTER: ! 323: case XTYP_UNREGISTER: ! 324: /* ! 325: * This is where the client would insert code to keep track of ! 326: * what servers are available. This could cause the initiation ! 327: * of some conversations. ! 328: */ ! 329: break; ! 330: ! 331: case XTYP_DISCONNECT: ! 332: if (fAutoReconnect) { ! 333: /* ! 334: * attempt a reconnection ! 335: */ ! 336: if (hConv = DdeReconnect(hConv)) { ! 337: AddConv(ci.hszServiceReq, ci.hszTopic, hConv, FALSE); ! 338: return 0; ! 339: } ! 340: } ! 341: ! 342: /* ! 343: * update conv window to show its new state. ! 344: */ ! 345: SendMessage((HWND)ci.hUser, UM_DISCONNECTED, 0, 0); ! 346: return 0; ! 347: break; ! 348: ! 349: case XTYP_ADVDATA: ! 350: /* ! 351: * data from an active advise loop (from a server) ! 352: */ ! 353: Delay(wDelay); ! 354: hwnd = FindAdviseChild((HWND)ci.hUser, hsz2, wFmt); ! 355: if (!IsWindow(hwnd)) { ! 356: PSTR pszItem, pszFmt; ! 357: /* ! 358: * AdviseStart window is gone, make a new one. ! 359: */ ! 360: pxact = (XACT *)MyAlloc(sizeof(XACT)); ! 361: pxact->wType = wType; ! 362: pxact->hConv = hConv; ! 363: pxact->wFmt = wFmt; ! 364: pxact->hszItem = hsz2; ! 365: DdeKeepStringHandle(idInst, hsz2); ! 366: ! 367: pszItem = GetHSZName(hsz2); ! 368: pszFmt = GetFormatName(wFmt); ! 369: ! 370: hwnd = CreateInfoCtrl(NULL, ! 371: (INT)SendMessage((HWND)ci.hUser, UM_GETNEXTCHILDX, 0, 0L), ! 372: (INT)SendMessage((HWND)ci.hUser, UM_GETNEXTCHILDY, 0, 0L), ! 373: 200, 100, ! 374: (HWND)ci.hUser, hInst, ! 375: Type2String(wType, 0), (LPSTR)pszItem, NULL, ! 376: NULL, (LPSTR)pszFmt, NULL, ! 377: ICSTY_SHOWFOCUS, 0, (DWORD)(LPSTR)pxact); ! 378: ! 379: MyFree(pszFmt); ! 380: MyFree(pszItem); ! 381: ! 382: if (!IsWindow(hwnd)) ! 383: return(DDE_FNOTPROCESSED); ! 384: } ! 385: if (!hData) { ! 386: /* ! 387: * XTYPF_NODATA case - request the info. (we do this synchronously ! 388: * for simplicity) ! 389: */ ! 390: hData = DdeClientTransaction(NULL, 0L, hConv, hsz2, wFmt, ! 391: XTYP_REQUEST, DefTimeout, NULL); ! 392: } ! 393: if (hData) { ! 394: PSTR pData; ! 395: /* ! 396: * Show incomming data on corresponding transaction window. ! 397: */ ! 398: pData = GetTextData(hData); ! 399: SendMessage(hwnd, ICM_SETSTRING, ICSID_CENTER, (DWORD)(LPSTR)pData); ! 400: MyFree(pData); ! 401: DdeFreeDataHandle(hData); ! 402: } ! 403: SendMessage(hwnd, ICM_SETSTRING, ICSID_LL, (DWORD)(LPSTR)"Advised"); ! 404: return(DDE_FACK); ! 405: break; ! 406: ! 407: case XTYP_XACT_COMPLETE: ! 408: /* ! 409: * An asynchronous transaction has completed. Show the results. ! 410: * ! 411: * ...unless the XOPT_BLOCKRESULT is chosen. ! 412: */ ! 413: ! 414: ci.cb = sizeof(CONVINFO); ! 415: if (DdeQueryConvInfo(hConv, lData1, &ci) && ! 416: IsWindow((HWND)ci.hUser) && ! 417: (pxact = (XACT *)GetWindowLong((HWND)ci.hUser, GWL_USER))) { ! 418: ! 419: if (pxact->fsOptions & XOPT_BLOCKRESULT) { ! 420: pxact->fsOptions &= ~XOPT_BLOCKRESULT; ! 421: return(CBR_BLOCK); ! 422: } ! 423: ! 424: pxact->Result = lData2; ! 425: pxact->ret = hData; ! 426: CompleteTransaction((HWND)ci.hUser, pxact); ! 427: } ! 428: break; ! 429: } ! 430: } ! 431: ! 432: ! 433: ! 434: ! 435: ! 436: ! 437: ! 438: /**************************************************************************** ! 439: * * ! 440: * FUNCTION : FindAdviseChild() * ! 441: * * ! 442: * PURPOSE : Search through the child windows of hwndMDI for an info * ! 443: * ctrl that has the same Item and format and is an * ! 444: * ADVSTART ADVSTOP or ADVDATA transaction window. * ! 445: * * ! 446: * We use these to show the associated advise data. * ! 447: * * ! 448: * RETURNS : The transaction window handle or 0 on failure. * ! 449: * * ! 450: ****************************************************************************/ ! 451: HWND FindAdviseChild( ! 452: HWND hwndMDI, ! 453: HSZ hszItem, ! 454: DWORD wFmt) ! 455: { ! 456: HWND hwnd, hwndStart; ! 457: XACT *pxact; ! 458: ! 459: if (!IsWindow(hwndMDI)) ! 460: return 0; ! 461: ! 462: hwnd = hwndStart = GetWindow(hwndMDI, GW_CHILD); ! 463: while (hwnd && IsChild(hwndMDI, hwnd)) { ! 464: pxact = (XACT *)GetWindowLong(hwnd, GWL_USER); ! 465: if (pxact && ! 466: (pxact)->wFmt == wFmt && ! 467: (pxact)->hszItem == hszItem && ! 468: ( ! 469: ((pxact->wType & XTYP_ADVSTART) == XTYP_ADVSTART) || ! 470: (pxact->wType == XTYP_ADVSTOP) || ! 471: (pxact->wType == XTYP_ADVDATA) ! 472: ) ! 473: ) { ! 474: return(hwnd); ! 475: } ! 476: hwnd = GetWindow(hwnd, GW_HWNDNEXT); ! 477: if (hwnd == hwndStart) ! 478: return 0; ! 479: } ! 480: return 0; ! 481: } ! 482: ! 483: ! 484: ! 485: /**************************************************************************** ! 486: * * ! 487: * FUNCTION : FindListWindow() * ! 488: * * ! 489: * PURPOSE : Locates the list window associated with this conversation * ! 490: * list. * ! 491: * * ! 492: * RETURNS : The window handle of the list window or 0 on failure. * ! 493: * * ! 494: ****************************************************************************/ ! 495: HWND FindListWindow( ! 496: HCONVLIST hConvList) ! 497: { ! 498: HWND hwnd; ! 499: MYCONVINFO *pmci; ! 500: ! 501: hwnd = GetWindow(hwndMDIClient, GW_CHILD); ! 502: while (hwnd) { ! 503: if (GetWindowLong(hwnd, GWL_WNDPROC) == (LONG)MDIChildWndProc) { ! 504: pmci = (MYCONVINFO *)GetWindowLong(hwnd, 0); ! 505: if (pmci != NULL && pmci->fList && pmci->hConv == hConvList) ! 506: return(hwnd); ! 507: } ! 508: hwnd = GetWindow(hwnd, GW_HWNDNEXT); ! 509: } ! 510: return 0; ! 511: } ! 512: ! 513: ! 514: ! 515: ! 516: /**************************************************************************** ! 517: * * ! 518: * FUNCTION : GetTextData() * ! 519: * * ! 520: * PURPOSE : Allocates and returns a pointer to the data contained in * ! 521: * hData. This assumes that hData points to text data and * ! 522: * will properly handle huge text data by leaving out the * ! 523: * middle of the string and placing the size of the string * ! 524: * into this string portion. * ! 525: * * ! 526: * RETURNS : A pointer to the allocated string. * ! 527: * * ! 528: ****************************************************************************/ ! 529: PSTR GetTextData( ! 530: HDDEDATA hData) ! 531: { ! 532: PSTR psz; ! 533: DWORD cb; ! 534: ! 535: #define CBBUF 1024 ! 536: ! 537: if (hData == NULL) { ! 538: return(NULL); ! 539: } ! 540: ! 541: cb = DdeGetData(hData, NULL, 0, 0); ! 542: if (!hData || !cb) ! 543: return NULL; ! 544: ! 545: if (cb > CBBUF) { // possibly HUGE object! ! 546: psz = MyAlloc(CBBUF); ! 547: DdeGetData(hData, psz, CBBUF - 46, 0L); ! 548: wsprintf(&psz[CBBUF - 46], "<---Size=%ld", cb); ! 549: } else { ! 550: psz = MyAlloc((DWORD)cb); ! 551: DdeGetData(hData, (LPBYTE)psz, cb, 0L); ! 552: } ! 553: return psz; ! 554: #undef CBBUF ! 555: } ! 556: ! 557: ! 558: ! 559: ! 560: ! 561: ! 562: ! 563: /**************************************************************************** ! 564: * * ! 565: * FUNCTION : MyGetClipboardFormatName() * ! 566: * * ! 567: * PURPOSE : Properly retrieves the string associated with a clipboard * ! 568: * format. If the format does not have a string associated * ! 569: * with it, the string #dddd is returned. * ! 570: * * ! 571: * RETURNS : The number of characters copied into lpstr or 0 on error. * ! 572: * * ! 573: ****************************************************************************/ ! 574: INT MyGetClipboardFormatName( ! 575: DWORD fmt, ! 576: LPSTR lpstr, ! 577: INT cbMax) ! 578: { ! 579: if (fmt < 0xc000) { ! 580: // predefined or integer format - just get the atom string ! 581: // wierdly enough, GetClipboardFormatName() doesn't support this. ! 582: return(GlobalGetAtomName((ATOM)fmt, lpstr, cbMax)); ! 583: } else { ! 584: return(GetClipboardFormatName(fmt, lpstr, cbMax)); ! 585: } ! 586: } ! 587: ! 588: ! 589: ! 590: ! 591: ! 592: /**************************************************************************** ! 593: * * ! 594: * FUNCTION : GetFormatName() * ! 595: * * ! 596: * PURPOSE : allocates and returns a pointer to a string representing * ! 597: * a format. Use MyFree() to free this string. * ! 598: * * ! 599: * RETURNS : The number of characters copied into lpstr or 0 on error. * ! 600: * * ! 601: ****************************************************************************/ ! 602: PSTR GetFormatName( ! 603: DWORD wFmt) ! 604: { ! 605: PSTR psz; ! 606: DWORD cb; ! 607: ! 608: if (wFmt == 1) { ! 609: psz = MyAlloc(8); ! 610: strcpy(psz, "CF_TEXT"); ! 611: return psz; ! 612: } ! 613: psz = MyAlloc(255); ! 614: *psz = '\0'; ! 615: cb = GetClipboardFormatName(wFmt, psz, 255) + 1; ! 616: return((PSTR)LocalReAlloc((HANDLE)psz, cb, LMEM_MOVEABLE)); ! 617: } ! 618: ! 619: ! 620: ! 621: ! 622: /**************************************************************************** ! 623: * * ! 624: * FUNCTION : MyDisconnect() * ! 625: * * ! 626: * PURPOSE : Disconnects the given conversation after updating the * ! 627: * associated conversation window. * ! 628: * * ! 629: * RETURNS : TRUE on success, FALSE on failuer. * ! 630: * * ! 631: ****************************************************************************/ ! 632: BOOL MyDisconnect( ! 633: HCONV hConv) ! 634: { ! 635: CONVINFO ci; ! 636: HWND hwnd; ! 637: // before we disconnect, invalidate the associated list window - if ! 638: // applicable. ! 639: ! 640: ci.cb = sizeof(CONVINFO); ! 641: ! 642: if (DdeQueryConvInfo(hConv, QID_SYNC, &ci) && ci.hConvList && ! 643: (hwnd = FindListWindow(ci.hConvList))) ! 644: InvalidateRect(hwnd, NULL, TRUE); ! 645: return(DdeDisconnect(hConv)); ! 646: } ! 647:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.