home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / idedll.zip / CPELINK.C next >
C/C++ Source or Header  |  1995-11-23  |  21KB  |  542 lines

  1. /*----------------------------------------------------------------------
  2. CPELINK.C
  3.  
  4.    EDITConnect()
  5.    EDITDisconnect()
  6.    EDITFile( FileName, HelpFile)
  7.    EDITLocate( Row, Col, Len)
  8.    EDITLocateError( Row, Col, Len, Resource, ErrMsg)
  9.    EDITSaveFiles()
  10.    EDITShowWindow( ShowCmd)
  11.  
  12. Date:    9/18/95
  13. Author:  Eric Karlson
  14. E-mail:  76512,3213@compuserve.com
  15.  
  16. This source code is distributed as freeware for any and all interested
  17. parties.  The source may be freely redistributed as long as it contains
  18. this message and all the originally distributed files.  The software
  19. may be freely used as long as it is not used as part of a commercial
  20. package.  Use of this source or resulting DLL in any package that is
  21. distributed as anything other than freeware requires the express, written
  22. consent of the author.  The source is distributed as-is and the author
  23. assumes no responsibility or liablity for any consequences of its use.
  24.  
  25. I would be interested in seeing any improvements/enhancements that
  26. people make to this code.
  27. ----------------------------------------------------------------------*/
  28.  
  29. #include <memory.h>
  30. #include <stdio.h>
  31. #include <string.h>
  32. #include <stddef.h>
  33. #include "cpelink.h"
  34.  
  35. #define INCL_WINWINDOWMGR
  36. #define INCL_WINDDE
  37. #define INCL_DOSPROCESS
  38. #define INCL_DOSSESMGR
  39. #define INCL_DOSMEMMGR
  40. #define INCL_DOSMODULEMGR
  41. #include <os2.h>
  42.  
  43. /*journal
  44. 9-18-95  ebk
  45.    New.  This is my first pass at writing the DLL interface for the Watcom IDE to the
  46.    PREDITOR/2 editor.
  47.  
  48.    Design Issues:
  49.    The first issue is that the thread that calls the EDITxxx routines appears to be the
  50.    same thread that processes the message queue in the IDE.  This means that the EDITxxx
  51.    routines cannot issue a DDE message to the CPE and then try to wait for a WM_DDE_ACK
  52.    message back because said message will be sitting in the message queue until the EDITxxx
  53.    routine returns and allows the thread to pick up the next message.  The result is that
  54.    one cannot implement the EDITSaveFiles routine in the current paradigm (since you do not
  55.    want to let the IDE continue until the editor has saved the files).  The only way I
  56.    see of getting around this problem is to create a new, invisible window (possibly a child
  57.    of the Desktop Object Window??) with its own message queue and thread.  This window would
  58.    be the one that carried on the DDE conversation with the editor, which would then allow
  59.    the thread within the EDITxxx routine to wait for a response message from the editor.
  60.  
  61.    The second issue is an apparent bug within PREDITOR/2.  It appears that even if there is
  62.    an active DDE session with the editor, if you shutdown the editor, it doesn't issue a
  63.    WM_DDE_TERMINATE message to the clients.  The result is that the routine that sends DDE
  64.    messages to the editor has to assume that if there is an error in sending a DDE message,
  65.    it is because the editor has been shutdown and the DDE connection is no longer valid.  I
  66.    can only hope that the IDE handles the return values from the EDITxxx routines by attempting
  67.    to re-establish the DDE connection to the editor by calling EDITConnect.  I have sent a
  68.    message to CompuWare about this issue.
  69.  
  70.    The third issue deals with the environment under which EDITDisconnect is called.  For a long
  71.    time I was having a problem that if I invoked the editor within the IDE, and then went into
  72.    the IDE and tried to change the 'Text Editor' setting to some other DLL or executable, the
  73.    IDE crashed with a SYS3175.  The DosLoadModule line in the EDITConnect routine is what
  74.    solved this problem.  I think what was happening was the following:
  75.  
  76.    Person clicks on the 'Text Editor' item in the menu bar and the click is handled by the
  77.    Window Procedure for the IDE.  The IDE calls the EDITDisconnect routine from within the
  78.    Window Procedure, and upon return, calls DosUnloadModule which then removed the DLL from
  79.    memory.  The Window Procedure then finishes and tries to return to procedure that called
  80.    it.  Unfortunately, the calling procedure was the LocalWinProc routine in the DLL that was
  81.    used to sub-class the IDE window.  Since the DLL was no longer in memory, we crash.  Issuing
  82.    the extra call to DosLoadModule forces the DLL to remain in memory and therefore avoids the
  83.    problem.  The solution still feels like something of a kludge to me, but I cannot think of a
  84.    better way to solve the problem.
  85. */
  86.  
  87. /* Local Constants */
  88. #define  WAITTIME    20
  89.  
  90. /* Local Globals */
  91. char     CPE_App[] = "CPE";
  92. BOOL     CPE_Connected = FALSE;
  93. HWND     CPE_Handle = NULLHANDLE;
  94. PID      CPE_PID;
  95. TID      CPE_TID;
  96. char     CPE_Topic[] = "CPETOPIC";
  97. PFNWP    DdeWinProc = NULL;
  98. BOOL     DllLocked = FALSE;
  99. HWND     IDE_Handle = NULLHANDLE;
  100.  
  101. /*----------------------------------------------------------------------
  102. DebugTrace( MsgStr)
  103.  
  104. Used to keep a running log of calls from the IDE, DDE messages etc for
  105. debugging purposes.  Just be removed for production compilation.
  106.  
  107. Parameters:
  108. MsgStr      Points to a string to be added to the running debug log.
  109. ----------------------------------------------------------------------*/
  110.  
  111. #ifdef __DEBUG__
  112. static void  DebugTrace (char* MsgStr)
  113.  { FILE* fh;
  114.    PTIB   myTIB;
  115.    PPIB   myPIB;
  116.    
  117.    DosGetInfoBlocks( &myTIB, &myPIB);
  118.    if (NULL != (fh = fopen( "E:\\DDE.TRACE", "at")))
  119.     { fprintf( fh, "%s (%lu,%lu)\n", MsgStr, myPIB->pib_ulpid, myTIB->tib_ordinal);
  120.       fclose( fh);
  121.     }
  122.  }
  123. #endif
  124.  
  125. /*----------------------------------------------------------------------
  126. SpaceEncode( Source, Dest)
  127.  
  128. Encodes a string so that embedded spaces can be sent to the PREDITOR/2
  129. DDE mechanism.  The encoding is:
  130.  
  131.    ' ' -> '~'
  132.    '~' -> '$~'
  133.    '$' -> '$$'
  134.  
  135. Parameters:
  136. Source   Points to the source string to encode
  137. Dest     Points to a buffer to store the encoded string in
  138. ----------------------------------------------------------------------*/
  139.  
  140. static void  SpaceEncode (char* Source, char* Dest)
  141.  { for ( ; '\0' != *Source ; Source++)
  142.       switch (*Source)
  143.        { case ' ':   *Dest++ = '~';
  144.                      break;
  145.  
  146.          case '~':   *Dest++ = '$';
  147.                      *Dest++ = '~';
  148.                      break;
  149.  
  150.          case '$':   *Dest++ = '$';
  151.                      *Dest++ = '$';
  152.                      break;
  153.  
  154.          default:    *Dest++ = *Source;
  155.        }
  156.    *Dest = '\0';
  157.  }
  158.  
  159. /*----------------------------------------------------------------------
  160. SendDdeMsg( Item, Data)
  161.  
  162. Creates, formats and send a DDE message to the CPE Editor.
  163.  
  164. Parameters:
  165. Item     Points to the item name to use for the message
  166. Data     Points to the data string to package in the message
  167.  
  168. Returns:
  169. TRUE     If the message was sent
  170. FALSE    Otherwise
  171. ----------------------------------------------------------------------*/
  172.  
  173. static BOOL  SendDdeMsg (char* Item, char* Data)
  174.  { ULONG       DataSz;
  175.    PDDESTRUCT  Packet;
  176.    BOOL        Sent = FALSE;
  177.    APIRET      Res;
  178.  
  179.    /* Size of data message */
  180.    DataSz = strlen( Data) + 1;
  181.  
  182.    /* Get a block of shared memory to send the message in */
  183.    if (0 == (Res = DosAllocSharedMem( &Packet, NULL, sizeof( DDESTRUCT) + strlen( Item) + DataSz + 1,
  184.                                       PAG_READ | PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE)))
  185.     { /* Fill in the packet with the required data */
  186.       Packet->cbData = DataSz;
  187.       Packet->fsStatus = 0;
  188.       Packet->usFormat = DDEFMT_TEXT;
  189.       Packet->offszItemName = sizeof( DDESTRUCT);
  190.       Packet->offabData = sizeof( DDESTRUCT) + strlen( Item) + 1;
  191.       strcpy( (char *)&(Packet[1]), Item);
  192.       strcpy( (char *)Packet + Packet->offabData, Data);
  193.       if (0 == (Res = DosGiveSharedMem( Packet, CPE_PID, PAG_READ | PAG_WRITE)))
  194.          /* Send the EXECUTE message */
  195.          if (!(Sent = WinDdePostMsg( CPE_Handle, IDE_Handle, WM_DDE_EXECUTE, Packet, DDEPM_RETRY)))
  196.           { /* Post failed.  This is probably because the editor terminated.  Mark the DDE */
  197.             /* session as being terminated now.                                            */
  198.             CPE_Connected = FALSE;
  199.             CPE_Handle = NULLHANDLE;
  200.           }
  201.     }
  202.    return( Sent);
  203.  }
  204.  
  205. /*----------------------------------------------------------------------
  206. LocalWinProc()
  207.  
  208. Used to Sub-Class the IDE Window so that we can intercept DDE messages
  209. here and act on them.  This procedure is automatically unhooked when
  210. a WM_DDE_TERMINATE message is received.
  211.  
  212. Parameters:
  213. ReceiverHandle The Window Handle of the receiving window
  214. Msg            The type of message being sent
  215. mp1            The first generic message parameter
  216. mp2            The second generic message parameter
  217. ----------------------------------------------------------------------*/
  218.  
  219. MRESULT EXPENTRY  LocalWinProc (HWND ReceiverHandle, ULONG Msg, MPARAM mp1, MPARAM mp2)
  220.  {
  221. #ifdef __DEBUG__
  222.    char*    ItemPtr;
  223.    char     Buffer[80];
  224. #endif
  225.  
  226.    switch (Msg)
  227.     { case WM_DDE_ACK :
  228.          if ((HWND)mp1 == CPE_Handle)
  229.           { /* This is an acknowledgement to a previous WM_DDE_EXECUTE */
  230. #ifdef __DEBUG__
  231.             ItemPtr = (char *)mp2 + ((PDDESTRUCT)mp2)->offszItemName;
  232.             sprintf( Buffer, "<<WM_DDE_ACK: Item = %s", ItemPtr);
  233.             DebugTrace( Buffer);
  234. #endif
  235.             DosFreeMem( (void *)mp2);
  236.             return( (MRESULT)0);
  237.           }
  238.          break;
  239.  
  240.       case WM_DDE_INITIATEACK :
  241.          if ((0 == strcmpi( CPE_App, ((PDDEINIT)mp2)->pszAppName)) &&
  242.              (0 == strcmpi( CPE_Topic, ((PDDEINIT)mp2)->pszTopic)))
  243.           { /* Pick up the information about the server and save it */
  244.             CPE_Handle = (HWND)mp1;
  245.             WinQueryWindowProcess( CPE_Handle, &CPE_PID, &CPE_TID);
  246. #ifdef __DEBUG__
  247.             sprintf( Buffer, "<<WM_DDE_INITIATEACK: Handle = %ld", CPE_Handle);
  248.             DebugTrace( Buffer);
  249. #endif
  250.             CPE_Connected = TRUE;
  251.             DosFreeMem( (void *)mp2);
  252.             return( (MRESULT)TRUE);
  253.           }
  254.  
  255.          case WM_DDE_TERMINATE :
  256.             if ((HWND)mp1 == CPE_Handle)
  257.              {
  258. #ifdef __DEBUG__
  259.                DebugTrace( "<<WM_DDE_TERMINATE:");
  260. #endif
  261.                /* According to the Warp Developer's Toolkit, a process is *supposed* to send */
  262.                /* back a WM_DDE_TERMINATE message when it receives one.  However, that logic */
  263.                /* would result in an infinite loop if everyone followed it and furthermore   */
  264.                /* IBM's own software (EPM for example) doesn't seem to behave this way, so   */
  265.                /* neither will I.  Just mark the CPE connection as being closed here.  Of    */
  266.                /* course, the PREDITOR/2 program doesn't seem to ever send WM_DDE_TERMINATE  */
  267.                /* messages in any case, but I'm putting this here just for good form.        */
  268.                CPE_Connected = FALSE;
  269.                CPE_Handle = NULLHANDLE;
  270.                return( (MRESULT)0);
  271.              }
  272.        }
  273.  
  274.    /* If we get here then this message was not related to the CPE-IDE Link.  Pass on to */
  275.    /* the default handler.                                                              */
  276.    return( (*DdeWinProc)( ReceiverHandle, Msg, mp1, mp2));
  277.  }
  278.  
  279. /*----------------------------------------------------------------------
  280. EDITConnect()
  281.  
  282. Establishes a connection to the PREDITOR/2 Editor.  If the editor is not
  283. running, it will be started up.  This assumes that the CPE.EXE program
  284. is in the PATH.
  285. ----------------------------------------------------------------------*/
  286.  
  287. EDITAPI  EDITConnect (void)
  288.  { CONVCONTEXT Context = {sizeof( CONVCONTEXT),0};
  289.    PPIB        IDE_PIB;
  290.    PTIB        IDE_TIB;
  291.    short       Loop;
  292.    PID         ProcessID;
  293.    ULONG       SessionID;
  294.    STARTDATA   SessionInfo;
  295.    HWND        ThisWin;
  296.    HENUM       WinEnumHnd;
  297.    PID         WinPID;
  298.    TID         WinTID;
  299.    char        ErrMod[256];
  300.    HMODULE     ModHandle;
  301.  
  302. #ifdef __DEBUG__
  303.    DebugTrace( ">>EDITConnect");
  304. #endif
  305.  
  306.    /* If we are not already connected to a CPE Editor, get to work */
  307.    if (!CPE_Connected)
  308.     { /* If we don't have the Window Handle for the IDE, get it now */
  309.       if (NULLHANDLE == IDE_Handle)
  310.        { DosGetInfoBlocks( &IDE_TIB, &IDE_PIB);
  311.          WinEnumHnd = WinBeginEnumWindows( HWND_DESKTOP);
  312.          while (NULLHANDLE != (ThisWin = WinGetNextWindow( WinEnumHnd)))
  313.             if (WinQueryWindowProcess( ThisWin, &WinPID, &WinTID))
  314.                if (WinPID == IDE_PIB->pib_ulpid)
  315.                 { IDE_Handle = ThisWin;
  316.                   break;
  317.                 }
  318.          WinEndEnumWindows( WinEnumHnd);
  319.        }
  320.  
  321.       /* Increment the use count on the DLL so that when the IDE attempts to unload the */
  322.       /* DLL, we don't get a SYS3175 from the IDE attempting to return through the      */
  323.       /* LocalWinProc which is no longer in memory.  This still feels like a kludge to  */
  324.       /* me, but I cannot find a better way to do this at this point.                   */
  325.       if (!DllLocked)
  326.          DllLocked = DosLoadModule( ErrMod, sizeof( ErrMod), "CPELINK.DLL", &ModHandle);
  327.  
  328.       /* If we haven't sub-classed the IDE window, do it now */
  329.       if (NULL == DdeWinProc)
  330.         DdeWinProc = WinSubclassWindow( IDE_Handle, LocalWinProc);
  331.  
  332.       /* Now create the DDE connection */
  333.       WinDdeInitiate( IDE_Handle, CPE_App, CPE_Topic, &Context);
  334.  
  335. #ifdef __DEBUG__
  336.        { char  Buffer[200];
  337.  
  338.          sprintf( Buffer, ">>EDITConnect: IDE_Handle = %ld, CPE_Connected = %lu",
  339.                   IDE_Handle, CPE_Connected);
  340.          DebugTrace( Buffer);
  341.        }
  342. #endif
  343.  
  344.       /* If we didn't get a response, assume that we have to start up an */
  345.       /* instance of the editor.                                         */
  346.       /* Note that there is a kludge here at the moment.  Ideally, I should */
  347.       /* start up another thread that will talk to the editor.  But that    */
  348.       /* means figuring out how to create a thread within a process that    */
  349.       /* has its own, private message queue, and I don't feel like dealing  */
  350.       /* with that yet.  So this code starts up the editor session, sends   */
  351.       /* the WM_DDE_INITIATE and assumes everything will work out...        */
  352.       if (!CPE_Connected)
  353.        { memset( &SessionInfo, 0, sizeof( SessionInfo));
  354.          SessionInfo.Length = sizeof( SessionInfo);
  355.          SessionInfo.Related = SSF_RELATED_INDEPENDENT;
  356.          SessionInfo.FgBg = SSF_FGBG_FORE;
  357.          SessionInfo.TraceOpt = SSF_TRACEOPT_NONE;
  358.          SessionInfo.PgmTitle = "PREDITOR/2 [IDE]";
  359.          SessionInfo.PgmName = "CPE.EXE";
  360.          SessionInfo.InheritOpt = SSF_INHERTOPT_SHELL;
  361.          SessionInfo.SessionType = SSF_TYPE_DEFAULT;
  362.          SessionInfo.InitXPos = 30;
  363.          SessionInfo.InitYPos = 30;
  364.          SessionInfo.InitXSize = 500;
  365.          SessionInfo.InitYSize = 350;
  366.          if (0 == DosStartSession( &SessionInfo, &SessionID, &ProcessID))
  367.             for (Loop = WAITTIME + 1 ; !CPE_Connected && (0 != --Loop) ; )
  368.              { DosSleep( 1000);
  369.                WinDdeInitiate( IDE_Handle, CPE_App, CPE_Topic, &Context);
  370.                DosSleep( 0);
  371.              }
  372.        }
  373.     }
  374.    return( CPE_Connected);
  375.  }
  376.  
  377. /*----------------------------------------------------------------------
  378. EDITDisconnect()
  379.  
  380. Terminates the DDE link to the PREDITOR/2 and attempts to shutdown
  381. the program if we had to spawn it in the first place.
  382. ----------------------------------------------------------------------*/
  383.  
  384. EDITAPI  EDITDisconnect (void)
  385.  {
  386. #ifdef __DEBUG__
  387.    DebugTrace( ">>EDITDisconnect");
  388. #endif
  389.  
  390.    if (CPE_Connected)
  391.     { WinDdePostMsg( CPE_Handle, IDE_Handle, WM_DDE_TERMINATE, NULL, DDEPM_RETRY);
  392.       CPE_Connected = FALSE;
  393.     }
  394.    if (NULL != DdeWinProc)
  395.     { WinSubclassWindow( IDE_Handle, DdeWinProc);
  396.       DdeWinProc = NULL;
  397.     }
  398.    return( !CPE_Connected);
  399.  }
  400.  
  401. /*----------------------------------------------------------------------
  402. EDITFile( FileName, HelpFile)
  403.  
  404. Loads the indicated file into the editor.  The HelpFile is setup as
  405. the default help file for the edit session.
  406.  
  407. Parameters:
  408. FileName    Points to the name of the file to load
  409. HelpFile    Either NULL, or points to the name of the help file to load
  410. ----------------------------------------------------------------------*/
  411.  
  412. EDITAPI  EDITFile (PSZ FileName, PSZ HelpFile)
  413.  { BOOL     Sent = FALSE;
  414.    char     Buffer[550];
  415.  
  416. #ifdef __DEBUG__
  417.    sprintf( Buffer, ">>EDITFile: %s, %s", FileName, HelpFile);
  418.    DebugTrace( Buffer);
  419. #endif
  420.  
  421.    if (CPE_Connected)
  422.       if (SendDdeMsg( "EDITFILE", FileName))
  423.        { sprintf( Buffer, "ide_sethelp %s", HelpFile);
  424.          Sent = SendDdeMsg( "EXECUTEPEL", Buffer);
  425.        }
  426.    return( Sent);
  427.  }
  428.  
  429. /*----------------------------------------------------------------------
  430. EDITLocate( Row, Col, Len)
  431.  
  432. Moves the cursor to the indicated Row and Column in the current buffer.
  433. If Len is non-zero, the indicated number of characters is also highlighted.
  434.  
  435. Parameters:
  436. Row      The row to move the cursor to
  437. Col      The column to more the cursor to
  438. Len      The number of characters to highlight at the cursor position
  439. ----------------------------------------------------------------------*/
  440.  
  441. EDITAPI  EDITLocate (long Row, short Col, short Len)
  442.  { char     MsgBuf[50];
  443.    BOOL     Sent = FALSE;
  444.  
  445. #ifdef __DEBUG__
  446.    sprintf( MsgBuf, ">>EDITLocate: %ld, %d, %d", Row, Col, Len);
  447.    DebugTrace( MsgBuf);
  448. #endif
  449.  
  450.    if (CPE_Connected)
  451.     { sprintf( MsgBuf, "ide_hilite %ld %d %d", Row, Col, Len);
  452.       Sent = SendDdeMsg( "EXECUTEPEL", MsgBuf);
  453.     }
  454.    return( Sent);
  455.  }
  456.  
  457. /*----------------------------------------------------------------------
  458. EDITLocateError( Row, Col, Len, Resource, ErrMsg)
  459.  
  460. Moves the cursor to the indicated Row and Column in the current buffer
  461. in response to an error condition.  If Len is non-zero, the indicated
  462. number of characters is also highlighted.  The Resource is a resource
  463. ID for the help message for this error.  ErrMsg is the text of the
  464. error message that occured.
  465.  
  466. Parameters:
  467. Row      The row to move the cursor to
  468. Col      The column to more the cursor to
  469. Len      The number of characters to highlight at the cursor position
  470. Resource The resource ID for the help message for the error condition
  471. ErrMsg   The error message in question
  472. ----------------------------------------------------------------------*/
  473.  
  474. EDITAPI  EDITLocateError (long Row, short Col, short Len, short Resource, PSZ ErrMsg)
  475.  { char     Error[200];
  476.    char     MsgBuf[400];
  477.    BOOL     Sent = FALSE;
  478.  
  479. #ifdef __DEBUG__
  480.    sprintf( MsgBuf, ">>EDITLocateError: %ld, %d, %d, %d, %s", Row, Col, Len, Resource, ErrMsg);
  481.    DebugTrace( MsgBuf);
  482. #endif
  483.  
  484.    /* Since the PREDITOR/2 cannot take an argument with spaces, encode spaces as a single */
  485.    /* '~', encode '~' as '$~' and encode '$' as '$$'.  Its painful, but it is the easiest */
  486.    /* way I can find to send a parameter with spaces.                                     */
  487.    SpaceEncode( ErrMsg, Error);
  488.  
  489.    if (CPE_Connected)
  490.     { sprintf( MsgBuf, "ide_hilite_err %ld %d %d %d %s", Row, Col, Len, Resource, Error);
  491.       Sent = SendDdeMsg( "EXECUTEPEL", MsgBuf);
  492.     }
  493.    return( Sent);
  494.  }
  495.  
  496. /*----------------------------------------------------------------------
  497. EDITSaveFiles()
  498.  
  499. Instructs the editor to save all modified files.
  500. ----------------------------------------------------------------------*/
  501.  
  502. EDITAPI  EDITSaveFiles (void)
  503.  { BOOL  Sent = TRUE;
  504.  
  505. #ifdef __DEBUG__
  506.    DebugTrace( ">>EDITSaveFiles");
  507. #endif
  508.  
  509.    /* Currently this is a NOP as I would need to force the IDE to wait until the */
  510.    /* PREDITOR/2 pacakge responded that it had processed this message.  I think  */
  511.    /* that PREDITOR/2 will not send back a WM_DDE_ACK until it is finished, but  */
  512.    /* since the thread that calls this DLL is the message processing thread for  */
  513.    /* the IDE, return messages will not get processed until this routine returns.*/
  514.    return( Sent);
  515.  }
  516.  
  517. /*----------------------------------------------------------------------
  518. EDITShowWindow( ShowCmd)
  519.  
  520. Instructs the editor to execute the indicated SHOW command.
  521.  
  522. Parameters:
  523. ShowCmd  The SHOW command to send to the editor.
  524. ----------------------------------------------------------------------*/
  525.  
  526. EDITAPI  EDITShowWindow (short ShowCmd)
  527.  { BOOL     Sent = FALSE;
  528.  
  529. #ifdef __DEBUG__
  530.    char     Buffer[50];
  531.  
  532.    sprintf( Buffer, ">>EDITShowWindow: %d", ShowCmd);
  533.    DebugTrace( Buffer);
  534. #endif
  535.  
  536.    if (CPE_Connected)
  537.       switch (ShowCmd)
  538.        { case 2:  Sent = SendDdeMsg( "TOTOP", "");
  539.                   break;
  540.        }
  541.    return( Sent);
  542.  }