home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 5 Edit
/
05-Edit.zip
/
idedll.zip
/
CPELINK.C
next >
Wrap
C/C++ Source or Header
|
1995-11-23
|
21KB
|
542 lines
/*----------------------------------------------------------------------
CPELINK.C
EDITConnect()
EDITDisconnect()
EDITFile( FileName, HelpFile)
EDITLocate( Row, Col, Len)
EDITLocateError( Row, Col, Len, Resource, ErrMsg)
EDITSaveFiles()
EDITShowWindow( ShowCmd)
Date: 9/18/95
Author: Eric Karlson
E-mail: 76512,3213@compuserve.com
This source code is distributed as freeware for any and all interested
parties. The source may be freely redistributed as long as it contains
this message and all the originally distributed files. The software
may be freely used as long as it is not used as part of a commercial
package. Use of this source or resulting DLL in any package that is
distributed as anything other than freeware requires the express, written
consent of the author. The source is distributed as-is and the author
assumes no responsibility or liablity for any consequences of its use.
I would be interested in seeing any improvements/enhancements that
people make to this code.
----------------------------------------------------------------------*/
#include <memory.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include "cpelink.h"
#define INCL_WINWINDOWMGR
#define INCL_WINDDE
#define INCL_DOSPROCESS
#define INCL_DOSSESMGR
#define INCL_DOSMEMMGR
#define INCL_DOSMODULEMGR
#include <os2.h>
/*journal
9-18-95 ebk
New. This is my first pass at writing the DLL interface for the Watcom IDE to the
PREDITOR/2 editor.
Design Issues:
The first issue is that the thread that calls the EDITxxx routines appears to be the
same thread that processes the message queue in the IDE. This means that the EDITxxx
routines cannot issue a DDE message to the CPE and then try to wait for a WM_DDE_ACK
message back because said message will be sitting in the message queue until the EDITxxx
routine returns and allows the thread to pick up the next message. The result is that
one cannot implement the EDITSaveFiles routine in the current paradigm (since you do not
want to let the IDE continue until the editor has saved the files). The only way I
see of getting around this problem is to create a new, invisible window (possibly a child
of the Desktop Object Window??) with its own message queue and thread. This window would
be the one that carried on the DDE conversation with the editor, which would then allow
the thread within the EDITxxx routine to wait for a response message from the editor.
The second issue is an apparent bug within PREDITOR/2. It appears that even if there is
an active DDE session with the editor, if you shutdown the editor, it doesn't issue a
WM_DDE_TERMINATE message to the clients. The result is that the routine that sends DDE
messages to the editor has to assume that if there is an error in sending a DDE message,
it is because the editor has been shutdown and the DDE connection is no longer valid. I
can only hope that the IDE handles the return values from the EDITxxx routines by attempting
to re-establish the DDE connection to the editor by calling EDITConnect. I have sent a
message to CompuWare about this issue.
The third issue deals with the environment under which EDITDisconnect is called. For a long
time I was having a problem that if I invoked the editor within the IDE, and then went into
the IDE and tried to change the 'Text Editor' setting to some other DLL or executable, the
IDE crashed with a SYS3175. The DosLoadModule line in the EDITConnect routine is what
solved this problem. I think what was happening was the following:
Person clicks on the 'Text Editor' item in the menu bar and the click is handled by the
Window Procedure for the IDE. The IDE calls the EDITDisconnect routine from within the
Window Procedure, and upon return, calls DosUnloadModule which then removed the DLL from
memory. The Window Procedure then finishes and tries to return to procedure that called
it. Unfortunately, the calling procedure was the LocalWinProc routine in the DLL that was
used to sub-class the IDE window. Since the DLL was no longer in memory, we crash. Issuing
the extra call to DosLoadModule forces the DLL to remain in memory and therefore avoids the
problem. The solution still feels like something of a kludge to me, but I cannot think of a
better way to solve the problem.
*/
/* Local Constants */
#define WAITTIME 20
/* Local Globals */
char CPE_App[] = "CPE";
BOOL CPE_Connected = FALSE;
HWND CPE_Handle = NULLHANDLE;
PID CPE_PID;
TID CPE_TID;
char CPE_Topic[] = "CPETOPIC";
PFNWP DdeWinProc = NULL;
BOOL DllLocked = FALSE;
HWND IDE_Handle = NULLHANDLE;
/*----------------------------------------------------------------------
DebugTrace( MsgStr)
Used to keep a running log of calls from the IDE, DDE messages etc for
debugging purposes. Just be removed for production compilation.
Parameters:
MsgStr Points to a string to be added to the running debug log.
----------------------------------------------------------------------*/
#ifdef __DEBUG__
static void DebugTrace (char* MsgStr)
{ FILE* fh;
PTIB myTIB;
PPIB myPIB;
DosGetInfoBlocks( &myTIB, &myPIB);
if (NULL != (fh = fopen( "E:\\DDE.TRACE", "at")))
{ fprintf( fh, "%s (%lu,%lu)\n", MsgStr, myPIB->pib_ulpid, myTIB->tib_ordinal);
fclose( fh);
}
}
#endif
/*----------------------------------------------------------------------
SpaceEncode( Source, Dest)
Encodes a string so that embedded spaces can be sent to the PREDITOR/2
DDE mechanism. The encoding is:
' ' -> '~'
'~' -> '$~'
'$' -> '$$'
Parameters:
Source Points to the source string to encode
Dest Points to a buffer to store the encoded string in
----------------------------------------------------------------------*/
static void SpaceEncode (char* Source, char* Dest)
{ for ( ; '\0' != *Source ; Source++)
switch (*Source)
{ case ' ': *Dest++ = '~';
break;
case '~': *Dest++ = '$';
*Dest++ = '~';
break;
case '$': *Dest++ = '$';
*Dest++ = '$';
break;
default: *Dest++ = *Source;
}
*Dest = '\0';
}
/*----------------------------------------------------------------------
SendDdeMsg( Item, Data)
Creates, formats and send a DDE message to the CPE Editor.
Parameters:
Item Points to the item name to use for the message
Data Points to the data string to package in the message
Returns:
TRUE If the message was sent
FALSE Otherwise
----------------------------------------------------------------------*/
static BOOL SendDdeMsg (char* Item, char* Data)
{ ULONG DataSz;
PDDESTRUCT Packet;
BOOL Sent = FALSE;
APIRET Res;
/* Size of data message */
DataSz = strlen( Data) + 1;
/* Get a block of shared memory to send the message in */
if (0 == (Res = DosAllocSharedMem( &Packet, NULL, sizeof( DDESTRUCT) + strlen( Item) + DataSz + 1,
PAG_READ | PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE)))
{ /* Fill in the packet with the required data */
Packet->cbData = DataSz;
Packet->fsStatus = 0;
Packet->usFormat = DDEFMT_TEXT;
Packet->offszItemName = sizeof( DDESTRUCT);
Packet->offabData = sizeof( DDESTRUCT) + strlen( Item) + 1;
strcpy( (char *)&(Packet[1]), Item);
strcpy( (char *)Packet + Packet->offabData, Data);
if (0 == (Res = DosGiveSharedMem( Packet, CPE_PID, PAG_READ | PAG_WRITE)))
/* Send the EXECUTE message */
if (!(Sent = WinDdePostMsg( CPE_Handle, IDE_Handle, WM_DDE_EXECUTE, Packet, DDEPM_RETRY)))
{ /* Post failed. This is probably because the editor terminated. Mark the DDE */
/* session as being terminated now. */
CPE_Connected = FALSE;
CPE_Handle = NULLHANDLE;
}
}
return( Sent);
}
/*----------------------------------------------------------------------
LocalWinProc()
Used to Sub-Class the IDE Window so that we can intercept DDE messages
here and act on them. This procedure is automatically unhooked when
a WM_DDE_TERMINATE message is received.
Parameters:
ReceiverHandle The Window Handle of the receiving window
Msg The type of message being sent
mp1 The first generic message parameter
mp2 The second generic message parameter
----------------------------------------------------------------------*/
MRESULT EXPENTRY LocalWinProc (HWND ReceiverHandle, ULONG Msg, MPARAM mp1, MPARAM mp2)
{
#ifdef __DEBUG__
char* ItemPtr;
char Buffer[80];
#endif
switch (Msg)
{ case WM_DDE_ACK :
if ((HWND)mp1 == CPE_Handle)
{ /* This is an acknowledgement to a previous WM_DDE_EXECUTE */
#ifdef __DEBUG__
ItemPtr = (char *)mp2 + ((PDDESTRUCT)mp2)->offszItemName;
sprintf( Buffer, "<<WM_DDE_ACK: Item = %s", ItemPtr);
DebugTrace( Buffer);
#endif
DosFreeMem( (void *)mp2);
return( (MRESULT)0);
}
break;
case WM_DDE_INITIATEACK :
if ((0 == strcmpi( CPE_App, ((PDDEINIT)mp2)->pszAppName)) &&
(0 == strcmpi( CPE_Topic, ((PDDEINIT)mp2)->pszTopic)))
{ /* Pick up the information about the server and save it */
CPE_Handle = (HWND)mp1;
WinQueryWindowProcess( CPE_Handle, &CPE_PID, &CPE_TID);
#ifdef __DEBUG__
sprintf( Buffer, "<<WM_DDE_INITIATEACK: Handle = %ld", CPE_Handle);
DebugTrace( Buffer);
#endif
CPE_Connected = TRUE;
DosFreeMem( (void *)mp2);
return( (MRESULT)TRUE);
}
case WM_DDE_TERMINATE :
if ((HWND)mp1 == CPE_Handle)
{
#ifdef __DEBUG__
DebugTrace( "<<WM_DDE_TERMINATE:");
#endif
/* According to the Warp Developer's Toolkit, a process is *supposed* to send */
/* back a WM_DDE_TERMINATE message when it receives one. However, that logic */
/* would result in an infinite loop if everyone followed it and furthermore */
/* IBM's own software (EPM for example) doesn't seem to behave this way, so */
/* neither will I. Just mark the CPE connection as being closed here. Of */
/* course, the PREDITOR/2 program doesn't seem to ever send WM_DDE_TERMINATE */
/* messages in any case, but I'm putting this here just for good form. */
CPE_Connected = FALSE;
CPE_Handle = NULLHANDLE;
return( (MRESULT)0);
}
}
/* If we get here then this message was not related to the CPE-IDE Link. Pass on to */
/* the default handler. */
return( (*DdeWinProc)( ReceiverHandle, Msg, mp1, mp2));
}
/*----------------------------------------------------------------------
EDITConnect()
Establishes a connection to the PREDITOR/2 Editor. If the editor is not
running, it will be started up. This assumes that the CPE.EXE program
is in the PATH.
----------------------------------------------------------------------*/
EDITAPI EDITConnect (void)
{ CONVCONTEXT Context = {sizeof( CONVCONTEXT),0};
PPIB IDE_PIB;
PTIB IDE_TIB;
short Loop;
PID ProcessID;
ULONG SessionID;
STARTDATA SessionInfo;
HWND ThisWin;
HENUM WinEnumHnd;
PID WinPID;
TID WinTID;
char ErrMod[256];
HMODULE ModHandle;
#ifdef __DEBUG__
DebugTrace( ">>EDITConnect");
#endif
/* If we are not already connected to a CPE Editor, get to work */
if (!CPE_Connected)
{ /* If we don't have the Window Handle for the IDE, get it now */
if (NULLHANDLE == IDE_Handle)
{ DosGetInfoBlocks( &IDE_TIB, &IDE_PIB);
WinEnumHnd = WinBeginEnumWindows( HWND_DESKTOP);
while (NULLHANDLE != (ThisWin = WinGetNextWindow( WinEnumHnd)))
if (WinQueryWindowProcess( ThisWin, &WinPID, &WinTID))
if (WinPID == IDE_PIB->pib_ulpid)
{ IDE_Handle = ThisWin;
break;
}
WinEndEnumWindows( WinEnumHnd);
}
/* Increment the use count on the DLL so that when the IDE attempts to unload the */
/* DLL, we don't get a SYS3175 from the IDE attempting to return through the */
/* LocalWinProc which is no longer in memory. This still feels like a kludge to */
/* me, but I cannot find a better way to do this at this point. */
if (!DllLocked)
DllLocked = DosLoadModule( ErrMod, sizeof( ErrMod), "CPELINK.DLL", &ModHandle);
/* If we haven't sub-classed the IDE window, do it now */
if (NULL == DdeWinProc)
DdeWinProc = WinSubclassWindow( IDE_Handle, LocalWinProc);
/* Now create the DDE connection */
WinDdeInitiate( IDE_Handle, CPE_App, CPE_Topic, &Context);
#ifdef __DEBUG__
{ char Buffer[200];
sprintf( Buffer, ">>EDITConnect: IDE_Handle = %ld, CPE_Connected = %lu",
IDE_Handle, CPE_Connected);
DebugTrace( Buffer);
}
#endif
/* If we didn't get a response, assume that we have to start up an */
/* instance of the editor. */
/* Note that there is a kludge here at the moment. Ideally, I should */
/* start up another thread that will talk to the editor. But that */
/* means figuring out how to create a thread within a process that */
/* has its own, private message queue, and I don't feel like dealing */
/* with that yet. So this code starts up the editor session, sends */
/* the WM_DDE_INITIATE and assumes everything will work out... */
if (!CPE_Connected)
{ memset( &SessionInfo, 0, sizeof( SessionInfo));
SessionInfo.Length = sizeof( SessionInfo);
SessionInfo.Related = SSF_RELATED_INDEPENDENT;
SessionInfo.FgBg = SSF_FGBG_FORE;
SessionInfo.TraceOpt = SSF_TRACEOPT_NONE;
SessionInfo.PgmTitle = "PREDITOR/2 [IDE]";
SessionInfo.PgmName = "CPE.EXE";
SessionInfo.InheritOpt = SSF_INHERTOPT_SHELL;
SessionInfo.SessionType = SSF_TYPE_DEFAULT;
SessionInfo.InitXPos = 30;
SessionInfo.InitYPos = 30;
SessionInfo.InitXSize = 500;
SessionInfo.InitYSize = 350;
if (0 == DosStartSession( &SessionInfo, &SessionID, &ProcessID))
for (Loop = WAITTIME + 1 ; !CPE_Connected && (0 != --Loop) ; )
{ DosSleep( 1000);
WinDdeInitiate( IDE_Handle, CPE_App, CPE_Topic, &Context);
DosSleep( 0);
}
}
}
return( CPE_Connected);
}
/*----------------------------------------------------------------------
EDITDisconnect()
Terminates the DDE link to the PREDITOR/2 and attempts to shutdown
the program if we had to spawn it in the first place.
----------------------------------------------------------------------*/
EDITAPI EDITDisconnect (void)
{
#ifdef __DEBUG__
DebugTrace( ">>EDITDisconnect");
#endif
if (CPE_Connected)
{ WinDdePostMsg( CPE_Handle, IDE_Handle, WM_DDE_TERMINATE, NULL, DDEPM_RETRY);
CPE_Connected = FALSE;
}
if (NULL != DdeWinProc)
{ WinSubclassWindow( IDE_Handle, DdeWinProc);
DdeWinProc = NULL;
}
return( !CPE_Connected);
}
/*----------------------------------------------------------------------
EDITFile( FileName, HelpFile)
Loads the indicated file into the editor. The HelpFile is setup as
the default help file for the edit session.
Parameters:
FileName Points to the name of the file to load
HelpFile Either NULL, or points to the name of the help file to load
----------------------------------------------------------------------*/
EDITAPI EDITFile (PSZ FileName, PSZ HelpFile)
{ BOOL Sent = FALSE;
char Buffer[550];
#ifdef __DEBUG__
sprintf( Buffer, ">>EDITFile: %s, %s", FileName, HelpFile);
DebugTrace( Buffer);
#endif
if (CPE_Connected)
if (SendDdeMsg( "EDITFILE", FileName))
{ sprintf( Buffer, "ide_sethelp %s", HelpFile);
Sent = SendDdeMsg( "EXECUTEPEL", Buffer);
}
return( Sent);
}
/*----------------------------------------------------------------------
EDITLocate( Row, Col, Len)
Moves the cursor to the indicated Row and Column in the current buffer.
If Len is non-zero, the indicated number of characters is also highlighted.
Parameters:
Row The row to move the cursor to
Col The column to more the cursor to
Len The number of characters to highlight at the cursor position
----------------------------------------------------------------------*/
EDITAPI EDITLocate (long Row, short Col, short Len)
{ char MsgBuf[50];
BOOL Sent = FALSE;
#ifdef __DEBUG__
sprintf( MsgBuf, ">>EDITLocate: %ld, %d, %d", Row, Col, Len);
DebugTrace( MsgBuf);
#endif
if (CPE_Connected)
{ sprintf( MsgBuf, "ide_hilite %ld %d %d", Row, Col, Len);
Sent = SendDdeMsg( "EXECUTEPEL", MsgBuf);
}
return( Sent);
}
/*----------------------------------------------------------------------
EDITLocateError( Row, Col, Len, Resource, ErrMsg)
Moves the cursor to the indicated Row and Column in the current buffer
in response to an error condition. If Len is non-zero, the indicated
number of characters is also highlighted. The Resource is a resource
ID for the help message for this error. ErrMsg is the text of the
error message that occured.
Parameters:
Row The row to move the cursor to
Col The column to more the cursor to
Len The number of characters to highlight at the cursor position
Resource The resource ID for the help message for the error condition
ErrMsg The error message in question
----------------------------------------------------------------------*/
EDITAPI EDITLocateError (long Row, short Col, short Len, short Resource, PSZ ErrMsg)
{ char Error[200];
char MsgBuf[400];
BOOL Sent = FALSE;
#ifdef __DEBUG__
sprintf( MsgBuf, ">>EDITLocateError: %ld, %d, %d, %d, %s", Row, Col, Len, Resource, ErrMsg);
DebugTrace( MsgBuf);
#endif
/* Since the PREDITOR/2 cannot take an argument with spaces, encode spaces as a single */
/* '~', encode '~' as '$~' and encode '$' as '$$'. Its painful, but it is the easiest */
/* way I can find to send a parameter with spaces. */
SpaceEncode( ErrMsg, Error);
if (CPE_Connected)
{ sprintf( MsgBuf, "ide_hilite_err %ld %d %d %d %s", Row, Col, Len, Resource, Error);
Sent = SendDdeMsg( "EXECUTEPEL", MsgBuf);
}
return( Sent);
}
/*----------------------------------------------------------------------
EDITSaveFiles()
Instructs the editor to save all modified files.
----------------------------------------------------------------------*/
EDITAPI EDITSaveFiles (void)
{ BOOL Sent = TRUE;
#ifdef __DEBUG__
DebugTrace( ">>EDITSaveFiles");
#endif
/* Currently this is a NOP as I would need to force the IDE to wait until the */
/* PREDITOR/2 pacakge responded that it had processed this message. I think */
/* that PREDITOR/2 will not send back a WM_DDE_ACK until it is finished, but */
/* since the thread that calls this DLL is the message processing thread for */
/* the IDE, return messages will not get processed until this routine returns.*/
return( Sent);
}
/*----------------------------------------------------------------------
EDITShowWindow( ShowCmd)
Instructs the editor to execute the indicated SHOW command.
Parameters:
ShowCmd The SHOW command to send to the editor.
----------------------------------------------------------------------*/
EDITAPI EDITShowWindow (short ShowCmd)
{ BOOL Sent = FALSE;
#ifdef __DEBUG__
char Buffer[50];
sprintf( Buffer, ">>EDITShowWindow: %d", ShowCmd);
DebugTrace( Buffer);
#endif
if (CPE_Connected)
switch (ShowCmd)
{ case 2: Sent = SendDdeMsg( "TOTOP", "");
break;
}
return( Sent);
}