/[dtapublic]/sf_code/esrgpcpj/shared/tcl_base/tclwindde.c
ViewVC logotype

Annotation of /sf_code/esrgpcpj/shared/tcl_base/tclwindde.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 25 - (hide annotations) (download)
Sat Oct 8 06:43:03 2016 UTC (7 years, 10 months ago) by dashley
File MIME type: text/plain
File size: 38884 byte(s)
Initial commit.
1 dashley 25 /* $Header: /cvsroot/esrg/sfesrg/esrgpcpj/shared/tcl_base/tclwindde.c,v 1.1.1.1 2001/06/13 04:48:43 dtashley Exp $ */
2    
3     /*
4     * tclWinDde.c --
5     *
6     * This file provides procedures that implement the "send"
7     * command, allowing commands to be passed from interpreter
8     * to interpreter.
9     *
10     * Copyright (c) 1997 by Sun Microsystems, Inc.
11     *
12     * See the file "license.terms" for information on usage and redistribution
13     * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14     *
15     * RCS: @(#) $Id: tclwindde.c,v 1.1.1.1 2001/06/13 04:48:43 dtashley Exp $
16     */
17    
18     #include "tclPort.h"
19     #include <ddeml.h>
20    
21     /*
22     * TCL_STORAGE_CLASS is set unconditionally to DLLEXPORT because the
23     * Registry_Init declaration is in the source file itself, which is only
24     * accessed when we are building a library.
25     */
26    
27     #undef TCL_STORAGE_CLASS
28     #define TCL_STORAGE_CLASS DLLEXPORT
29    
30     /*
31     * The following structure is used to keep track of the interpreters
32     * registered by this process.
33     */
34    
35     typedef struct RegisteredInterp {
36     struct RegisteredInterp *nextPtr;
37     /* The next interp this application knows
38     * about. */
39     char *name; /* Interpreter's name (malloc-ed). */
40     Tcl_Interp *interp; /* The interpreter attached to this name. */
41     } RegisteredInterp;
42    
43     /*
44     * Used to keep track of conversations.
45     */
46    
47     typedef struct Conversation {
48     struct Conversation *nextPtr;
49     /* The next conversation in the list. */
50     RegisteredInterp *riPtr; /* The info we know about the conversation. */
51     HCONV hConv; /* The DDE handle for this conversation. */
52     Tcl_Obj *returnPackagePtr; /* The result package for this conversation. */
53     } Conversation;
54    
55     typedef struct ThreadSpecificData {
56     Conversation *currentConversations;
57     /* A list of conversations currently
58     * being processed. */
59     RegisteredInterp *interpListPtr;
60     /* List of all interpreters registered
61     * in the current process. */
62     } ThreadSpecificData;
63     static Tcl_ThreadDataKey dataKey;
64    
65     /*
66     * The following variables cannot be placed in thread-local storage.
67     * The Mutex ddeMutex guards access to the ddeInstance.
68     */
69     static HSZ ddeServiceGlobal = 0;
70     static DWORD ddeInstance; /* The application instance handle given
71     * to us by DdeInitialize. */
72     static int ddeIsServer = 0;
73    
74     #define TCL_DDE_VERSION "1.1"
75     #define TCL_DDE_PACKAGE_NAME "dde"
76     #define TCL_DDE_SERVICE_NAME "TclEval"
77    
78     TCL_DECLARE_MUTEX(ddeMutex)
79    
80     /*
81     * Forward declarations for procedures defined later in this file.
82     */
83    
84     static void DdeExitProc _ANSI_ARGS_((ClientData clientData));
85     static void DeleteProc _ANSI_ARGS_((ClientData clientData));
86     static Tcl_Obj * ExecuteRemoteObject _ANSI_ARGS_((
87     RegisteredInterp *riPtr,
88     Tcl_Obj *ddeObjectPtr));
89     static int MakeDdeConnection _ANSI_ARGS_((Tcl_Interp *interp,
90     char *name, HCONV *ddeConvPtr));
91     static HDDEDATA CALLBACK DdeServerProc _ANSI_ARGS_((UINT uType,
92     UINT uFmt, HCONV hConv, HSZ ddeTopic,
93     HSZ ddeItem, HDDEDATA hData, DWORD dwData1,
94     DWORD dwData2));
95     static void SetDdeError _ANSI_ARGS_((Tcl_Interp *interp));
96     int Tcl_DdeObjCmd(ClientData clientData, /* Used only for deletion */
97     Tcl_Interp *interp, /* The interp we are sending from */
98     int objc, /* Number of arguments */
99     Tcl_Obj *CONST objv[]); /* The arguments */
100    
101     EXTERN int Dde_Init(Tcl_Interp *interp);
102    
103     /*
104     *----------------------------------------------------------------------
105     *
106     * Dde_Init --
107     *
108     * This procedure initializes the dde command.
109     *
110     * Results:
111     * A standard Tcl result.
112     *
113     * Side effects:
114     * None.
115     *
116     *----------------------------------------------------------------------
117     */
118    
119     int
120     Dde_Init(
121     Tcl_Interp *interp)
122     {
123     ThreadSpecificData *tsdPtr;
124    
125     if (!Tcl_InitStubs(interp, "8.0", 0)) {
126     return TCL_ERROR;
127     }
128    
129     Tcl_CreateObjCommand(interp, "dde", Tcl_DdeObjCmd, NULL, NULL);
130    
131     tsdPtr = (ThreadSpecificData *)
132     Tcl_GetThreadData((Tcl_ThreadDataKey *) &dataKey, sizeof(ThreadSpecificData));
133    
134     if (tsdPtr == NULL) {
135     tsdPtr = TCL_TSD_INIT(&dataKey);
136     tsdPtr->currentConversations = NULL;
137     tsdPtr->interpListPtr = NULL;
138     }
139     Tcl_CreateExitHandler(DdeExitProc, NULL);
140    
141     return Tcl_PkgProvide(interp, TCL_DDE_PACKAGE_NAME, TCL_DDE_VERSION);
142     }
143    
144    
145     /*
146     *----------------------------------------------------------------------
147     *
148     * Initialize --
149     *
150     * Initialize the global DDE instance.
151     *
152     * Results:
153     * None.
154     *
155     * Side effects:
156     * Registers the DDE server proc.
157     *
158     *----------------------------------------------------------------------
159     */
160    
161     static void
162     Initialize(void)
163     {
164     int nameFound = 0;
165     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
166    
167     /*
168     * See if the application is already registered; if so, remove its
169     * current name from the registry. The deletion of the command
170     * will take care of disposing of this entry.
171     */
172    
173     if (tsdPtr->interpListPtr != NULL) {
174     nameFound = 1;
175     }
176    
177     /*
178     * Make sure that the DDE server is there. This is done only once,
179     * add an exit handler tear it down.
180     */
181    
182     if (ddeInstance == 0) {
183     Tcl_MutexLock(&ddeMutex);
184     if (ddeInstance == 0) {
185     if (DdeInitialize(&ddeInstance, DdeServerProc,
186     CBF_SKIP_REGISTRATIONS
187     | CBF_SKIP_UNREGISTRATIONS
188     | CBF_FAIL_POKES, 0)
189     != DMLERR_NO_ERROR) {
190     ddeInstance = 0;
191     }
192     }
193     Tcl_MutexUnlock(&ddeMutex);
194     }
195     if ((ddeServiceGlobal == 0) && (nameFound != 0)) {
196     Tcl_MutexLock(&ddeMutex);
197     if ((ddeServiceGlobal == 0) && (nameFound != 0)) {
198     ddeIsServer = 1;
199     Tcl_CreateExitHandler(DdeExitProc, NULL);
200     ddeServiceGlobal = DdeCreateStringHandle(ddeInstance, \
201     TCL_DDE_SERVICE_NAME, 0);
202     DdeNameService(ddeInstance, ddeServiceGlobal, 0L, DNS_REGISTER);
203     } else {
204     ddeIsServer = 0;
205     }
206     Tcl_MutexUnlock(&ddeMutex);
207     }
208     }
209    
210     /*
211     *--------------------------------------------------------------
212     *
213     * DdeSetServerName --
214     *
215     * This procedure is called to associate an ASCII name with a Dde
216     * server. If the interpreter has already been named, the
217     * name replaces the old one.
218     *
219     * Results:
220     * The return value is the name actually given to the interp.
221     * This will normally be the same as name, but if name was already
222     * in use for a Dde Server then a name of the form "name #2" will
223     * be chosen, with a high enough number to make the name unique.
224     *
225     * Side effects:
226     * Registration info is saved, thereby allowing the "send" command
227     * to be used later to invoke commands in the application. In
228     * addition, the "send" command is created in the application's
229     * interpreter. The registration will be removed automatically
230     * if the interpreter is deleted or the "send" command is removed.
231     *
232     *--------------------------------------------------------------
233     */
234    
235     static char *
236     DdeSetServerName(
237     Tcl_Interp *interp,
238     char *name /* The name that will be used to
239     * refer to the interpreter in later
240     * "send" commands. Must be globally
241     * unique. */
242     )
243     {
244     int suffix, offset;
245     RegisteredInterp *riPtr, *prevPtr;
246     Tcl_DString dString;
247     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
248    
249     /*
250     * See if the application is already registered; if so, remove its
251     * current name from the registry. The deletion of the command
252     * will take care of disposing of this entry.
253     */
254    
255     for (riPtr = tsdPtr->interpListPtr, prevPtr = NULL; riPtr != NULL;
256     prevPtr = riPtr, riPtr = riPtr->nextPtr) {
257     if (riPtr->interp == interp) {
258     if (name != NULL) {
259     if (prevPtr == NULL) {
260     tsdPtr->interpListPtr = tsdPtr->interpListPtr->nextPtr;
261     } else {
262     prevPtr->nextPtr = riPtr->nextPtr;
263     }
264     break;
265     } else {
266     /*
267     * the name was NULL, so the caller is asking for
268     * the name of the current interp.
269     */
270    
271     return riPtr->name;
272     }
273     }
274     }
275    
276     if (name == NULL) {
277     /*
278     * the name was NULL, so the caller is asking for
279     * the name of the current interp, but it doesn't
280     * have a name.
281     */
282    
283     return "";
284     }
285    
286     /*
287     * Pick a name to use for the application. Use "name" if it's not
288     * already in use. Otherwise add a suffix such as " #2", trying
289     * larger and larger numbers until we eventually find one that is
290     * unique.
291     */
292    
293     suffix = 1;
294     offset = 0;
295     Tcl_DStringInit(&dString);
296    
297     /*
298     * We have found a unique name. Now add it to the registry.
299     */
300    
301     riPtr = (RegisteredInterp *) ckalloc(sizeof(RegisteredInterp));
302     riPtr->interp = interp;
303     riPtr->name = ckalloc(strlen(name) + 1);
304     riPtr->nextPtr = tsdPtr->interpListPtr;
305     tsdPtr->interpListPtr = riPtr;
306     strcpy(riPtr->name, name);
307    
308     Tcl_CreateObjCommand(interp, "dde", Tcl_DdeObjCmd,
309     (ClientData) riPtr, DeleteProc);
310     if (Tcl_IsSafe(interp)) {
311     Tcl_HideCommand(interp, "dde", "dde");
312     }
313     Tcl_DStringFree(&dString);
314    
315     /*
316     * re-initialize with the new name
317     */
318     Initialize();
319    
320     return riPtr->name;
321     }
322    
323     /*
324     *--------------------------------------------------------------
325     *
326     * DeleteProc
327     *
328     * This procedure is called when the command "dde" is destroyed.
329     *
330     * Results:
331     * none
332     *
333     * Side effects:
334     * The interpreter given by riPtr is unregistered.
335     *
336     *--------------------------------------------------------------
337     */
338    
339     static void
340     DeleteProc(clientData)
341     ClientData clientData; /* The interp we are deleting passed
342     * as ClientData. */
343     {
344     RegisteredInterp *riPtr = (RegisteredInterp *) clientData;
345     RegisteredInterp *searchPtr, *prevPtr;
346     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
347    
348     for (searchPtr = tsdPtr->interpListPtr, prevPtr = NULL;
349     (searchPtr != NULL) && (searchPtr != riPtr);
350     prevPtr = searchPtr, searchPtr = searchPtr->nextPtr) {
351     /*
352     * Empty loop body.
353     */
354     }
355    
356     if (searchPtr != NULL) {
357     if (prevPtr == NULL) {
358     tsdPtr->interpListPtr = tsdPtr->interpListPtr->nextPtr;
359     } else {
360     prevPtr->nextPtr = searchPtr->nextPtr;
361     }
362     }
363     ckfree(riPtr->name);
364     Tcl_EventuallyFree(clientData, TCL_DYNAMIC);
365     }
366    
367     /*
368     *--------------------------------------------------------------
369     *
370     * ExecuteRemoteObject --
371     *
372     * Takes the package delivered by DDE and executes it in
373     * the server's interpreter.
374     *
375     * Results:
376     * A list Tcl_Obj * that describes what happened. The first
377     * element is the numerical return code (TCL_ERROR, etc.).
378     * The second element is the result of the script. If the
379     * return result was TCL_ERROR, then the third element
380     * will be the value of the global "errorCode", and the
381     * fourth will be the value of the global "errorInfo".
382     * The return result will have a refCount of 0.
383     *
384     * Side effects:
385     * A Tcl script is run, which can cause all kinds of other
386     * things to happen.
387     *
388     *--------------------------------------------------------------
389     */
390    
391     static Tcl_Obj *
392     ExecuteRemoteObject(
393     RegisteredInterp *riPtr, /* Info about this server. */
394     Tcl_Obj *ddeObjectPtr) /* The object to execute. */
395     {
396     Tcl_Obj *errorObjPtr;
397     Tcl_Obj *returnPackagePtr;
398     int result;
399    
400     result = Tcl_EvalObjEx(riPtr->interp, ddeObjectPtr, TCL_EVAL_GLOBAL);
401     returnPackagePtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
402     Tcl_ListObjAppendElement(NULL, returnPackagePtr,
403     Tcl_NewIntObj(result));
404     Tcl_ListObjAppendElement(NULL, returnPackagePtr,
405     Tcl_GetObjResult(riPtr->interp));
406     if (result == TCL_ERROR) {
407     errorObjPtr = Tcl_GetVar2Ex(riPtr->interp, "errorCode", NULL,
408     TCL_GLOBAL_ONLY);
409     Tcl_ListObjAppendElement(NULL, returnPackagePtr, errorObjPtr);
410     errorObjPtr = Tcl_GetVar2Ex(riPtr->interp, "errorInfo", NULL,
411     TCL_GLOBAL_ONLY);
412     Tcl_ListObjAppendElement(NULL, returnPackagePtr, errorObjPtr);
413     }
414    
415     return returnPackagePtr;
416     }
417    
418     /*
419     *--------------------------------------------------------------
420     *
421     * DdeServerProc --
422     *
423     * Handles all transactions for this server. Can handle
424     * execute, request, and connect protocols. Dde will
425     * call this routine when a client attempts to run a dde
426     * command using this server.
427     *
428     * Results:
429     * A DDE Handle with the result of the dde command.
430     *
431     * Side effects:
432     * Depending on which command is executed, arbitrary
433     * Tcl scripts can be run.
434     *
435     *--------------------------------------------------------------
436     */
437    
438     static HDDEDATA CALLBACK
439     DdeServerProc (
440     UINT uType, /* The type of DDE transaction we
441     * are performing. */
442     UINT uFmt, /* The format that data is sent or
443     * received. */
444     HCONV hConv, /* The conversation associated with the
445     * current transaction. */
446     HSZ ddeTopic, /* A string handle. Transaction-type
447     * dependent. */
448     HSZ ddeItem, /* A string handle. Transaction-type
449     * dependent. */
450     HDDEDATA hData, /* DDE data. Transaction-type dependent. */
451     DWORD dwData1, /* Transaction-dependent data. */
452     DWORD dwData2) /* Transaction-dependent data. */
453     {
454     Tcl_DString dString;
455     int len;
456     char *utilString;
457     Tcl_Obj *ddeObjectPtr;
458     HDDEDATA ddeReturn = NULL;
459     RegisteredInterp *riPtr;
460     Conversation *convPtr, *prevConvPtr;
461     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
462    
463     switch(uType) {
464     case XTYP_CONNECT:
465    
466     /*
467     * Dde is trying to initialize a conversation with us. Check
468     * and make sure we have a valid topic.
469     */
470    
471     len = DdeQueryString(ddeInstance, ddeTopic, NULL, 0, 0);
472     Tcl_DStringInit(&dString);
473     Tcl_DStringSetLength(&dString, len);
474     utilString = Tcl_DStringValue(&dString);
475     DdeQueryString(ddeInstance, ddeTopic, utilString, len + 1,
476     CP_WINANSI);
477    
478     for (riPtr = tsdPtr->interpListPtr; riPtr != NULL;
479     riPtr = riPtr->nextPtr) {
480     if (stricmp(utilString, riPtr->name) == 0) {
481     Tcl_DStringFree(&dString);
482     return (HDDEDATA) TRUE;
483     }
484     }
485    
486     Tcl_DStringFree(&dString);
487     return (HDDEDATA) FALSE;
488    
489     case XTYP_CONNECT_CONFIRM:
490    
491     /*
492     * Dde has decided that we can connect, so it gives us a
493     * conversation handle. We need to keep track of it
494     * so we know which execution result to return in an
495     * XTYP_REQUEST.
496     */
497    
498     len = DdeQueryString(ddeInstance, ddeTopic, NULL, 0, 0);
499     Tcl_DStringInit(&dString);
500     Tcl_DStringSetLength(&dString, len);
501     utilString = Tcl_DStringValue(&dString);
502     DdeQueryString(ddeInstance, ddeTopic, utilString, len + 1,
503     CP_WINANSI);
504     for (riPtr = tsdPtr->interpListPtr; riPtr != NULL;
505     riPtr = riPtr->nextPtr) {
506     if (stricmp(riPtr->name, utilString) == 0) {
507     convPtr = (Conversation *) ckalloc(sizeof(Conversation));
508     convPtr->nextPtr = tsdPtr->currentConversations;
509     convPtr->returnPackagePtr = NULL;
510     convPtr->hConv = hConv;
511     convPtr->riPtr = riPtr;
512     tsdPtr->currentConversations = convPtr;
513     break;
514     }
515     }
516     Tcl_DStringFree(&dString);
517     return (HDDEDATA) TRUE;
518    
519     case XTYP_DISCONNECT:
520    
521     /*
522     * The client has disconnected from our server. Forget this
523     * conversation.
524     */
525    
526     for (convPtr = tsdPtr->currentConversations, prevConvPtr = NULL;
527     convPtr != NULL;
528     prevConvPtr = convPtr, convPtr = convPtr->nextPtr) {
529     if (hConv == convPtr->hConv) {
530     if (prevConvPtr == NULL) {
531     tsdPtr->currentConversations = convPtr->nextPtr;
532     } else {
533     prevConvPtr->nextPtr = convPtr->nextPtr;
534     }
535     if (convPtr->returnPackagePtr != NULL) {
536     Tcl_DecrRefCount(convPtr->returnPackagePtr);
537     }
538     ckfree((char *) convPtr);
539     break;
540     }
541     }
542     return (HDDEDATA) TRUE;
543    
544     case XTYP_REQUEST:
545    
546     /*
547     * This could be either a request for a value of a Tcl variable,
548     * or it could be the send command requesting the results of the
549     * last execute.
550     */
551    
552     if (uFmt != CF_TEXT) {
553     return (HDDEDATA) FALSE;
554     }
555    
556     ddeReturn = (HDDEDATA) FALSE;
557     for (convPtr = tsdPtr->currentConversations; (convPtr != NULL)
558     && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
559     /*
560     * Empty loop body.
561     */
562     }
563    
564     if (convPtr != NULL) {
565     char *returnString;
566    
567     len = DdeQueryString(ddeInstance, ddeItem, NULL, 0,
568     CP_WINANSI);
569     Tcl_DStringInit(&dString);
570     Tcl_DStringSetLength(&dString, len);
571     utilString = Tcl_DStringValue(&dString);
572     DdeQueryString(ddeInstance, ddeItem, utilString,
573     len + 1, CP_WINANSI);
574     if (stricmp(utilString, "$TCLEVAL$EXECUTE$RESULT") == 0) {
575     returnString =
576     Tcl_GetStringFromObj(convPtr->returnPackagePtr, &len);
577     ddeReturn = DdeCreateDataHandle(ddeInstance,
578     returnString, len+1, 0, ddeItem, CF_TEXT,
579     0);
580     } else {
581     Tcl_Obj *variableObjPtr = Tcl_GetVar2Ex(
582     convPtr->riPtr->interp, utilString, NULL,
583     TCL_GLOBAL_ONLY);
584     if (variableObjPtr != NULL) {
585     returnString = Tcl_GetStringFromObj(variableObjPtr,
586     &len);
587     ddeReturn = DdeCreateDataHandle(ddeInstance,
588     returnString, len+1, 0, ddeItem, CF_TEXT, 0);
589     } else {
590     ddeReturn = NULL;
591     }
592     }
593     Tcl_DStringFree(&dString);
594     }
595     return ddeReturn;
596    
597     case XTYP_EXECUTE: {
598    
599     /*
600     * Execute this script. The results will be saved into
601     * a list object which will be retreived later. See
602     * ExecuteRemoteObject.
603     */
604    
605     Tcl_Obj *returnPackagePtr;
606    
607     for (convPtr = tsdPtr->currentConversations; (convPtr != NULL)
608     && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
609     /*
610     * Empty loop body.
611     */
612    
613     }
614    
615     if (convPtr == NULL) {
616     return (HDDEDATA) DDE_FNOTPROCESSED;
617     }
618    
619     utilString = (char *) DdeAccessData(hData, &len);
620     ddeObjectPtr = Tcl_NewStringObj(utilString, -1);
621     Tcl_IncrRefCount(ddeObjectPtr);
622     DdeUnaccessData(hData);
623     if (convPtr->returnPackagePtr != NULL) {
624     Tcl_DecrRefCount(convPtr->returnPackagePtr);
625     }
626     convPtr->returnPackagePtr = NULL;
627     returnPackagePtr =
628     ExecuteRemoteObject(convPtr->riPtr, ddeObjectPtr);
629     for (convPtr = tsdPtr->currentConversations; (convPtr != NULL)
630     && (convPtr->hConv != hConv); convPtr = convPtr->nextPtr) {
631     /*
632     * Empty loop body.
633     */
634    
635     }
636     if (convPtr != NULL) {
637     Tcl_IncrRefCount(returnPackagePtr);
638     convPtr->returnPackagePtr = returnPackagePtr;
639     }
640     Tcl_DecrRefCount(ddeObjectPtr);
641     if (returnPackagePtr == NULL) {
642     return (HDDEDATA) DDE_FNOTPROCESSED;
643     } else {
644     return (HDDEDATA) DDE_FACK;
645     }
646     }
647    
648     case XTYP_WILDCONNECT: {
649    
650     /*
651     * Dde wants a list of services and topics that we support.
652     */
653    
654     HSZPAIR *returnPtr;
655     int i;
656     int numItems;
657    
658     for (i = 0, riPtr = tsdPtr->interpListPtr; riPtr != NULL;
659     i++, riPtr = riPtr->nextPtr) {
660     /*
661     * Empty loop body.
662     */
663    
664     }
665    
666     numItems = i;
667     ddeReturn = DdeCreateDataHandle(ddeInstance, NULL,
668     (numItems + 1) * sizeof(HSZPAIR), 0, 0, 0, 0);
669     returnPtr = (HSZPAIR *) DdeAccessData(ddeReturn, &len);
670     for (i = 0, riPtr = tsdPtr->interpListPtr; i < numItems;
671     i++, riPtr = riPtr->nextPtr) {
672     returnPtr[i].hszSvc = DdeCreateStringHandle(
673     ddeInstance, "TclEval", CP_WINANSI);
674     returnPtr[i].hszTopic = DdeCreateStringHandle(
675     ddeInstance, riPtr->name, CP_WINANSI);
676     }
677     returnPtr[i].hszSvc = NULL;
678     returnPtr[i].hszTopic = NULL;
679     DdeUnaccessData(ddeReturn);
680     return ddeReturn;
681     }
682    
683     }
684     return NULL;
685     }
686    
687     /*
688     *--------------------------------------------------------------
689     *
690     * DdeExitProc --
691     *
692     * Gets rid of our DDE server when we go away.
693     *
694     * Results:
695     * None.
696     *
697     * Side effects:
698     * The DDE server is deleted.
699     *
700     *--------------------------------------------------------------
701     */
702    
703     static void
704     DdeExitProc(
705     ClientData clientData) /* Not used in this handler. */
706     {
707     DdeNameService(ddeInstance, NULL, 0, DNS_UNREGISTER);
708     DdeUninitialize(ddeInstance);
709     ddeInstance = 0;
710     }
711    
712     /*
713     *--------------------------------------------------------------
714     *
715     * MakeDdeConnection --
716     *
717     * This procedure is a utility used to connect to a DDE
718     * server when given a server name and a topic name.
719     *
720     * Results:
721     * A standard Tcl result.
722     *
723     *
724     * Side effects:
725     * Passes back a conversation through ddeConvPtr
726     *
727     *--------------------------------------------------------------
728     */
729    
730     static int
731     MakeDdeConnection(
732     Tcl_Interp *interp, /* Used to report errors. */
733     char *name, /* The connection to use. */
734     HCONV *ddeConvPtr)
735     {
736     HSZ ddeTopic, ddeService;
737     HCONV ddeConv;
738     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
739    
740     ddeService = DdeCreateStringHandle(ddeInstance, "TclEval", 0);
741     ddeTopic = DdeCreateStringHandle(ddeInstance, name, 0);
742    
743     ddeConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL);
744     DdeFreeStringHandle(ddeInstance, ddeService);
745     DdeFreeStringHandle(ddeInstance, ddeTopic);
746    
747     if (ddeConv == (HCONV) NULL) {
748     if (interp != NULL) {
749     Tcl_AppendResult(interp, "no registered server named \"",
750     name, "\"", (char *) NULL);
751     }
752     return TCL_ERROR;
753     }
754    
755     *ddeConvPtr = ddeConv;
756     return TCL_OK;
757     }
758    
759     /*
760     *--------------------------------------------------------------
761     *
762     * SetDdeError --
763     *
764     * Sets the interp result to a cogent error message
765     * describing the last DDE error.
766     *
767     * Results:
768     * None.
769     *
770     *
771     * Side effects:
772     * The interp's result object is changed.
773     *
774     *--------------------------------------------------------------
775     */
776    
777     static void
778     SetDdeError(
779     Tcl_Interp *interp) /* The interp to put the message in.*/
780     {
781     Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);
782     int err;
783    
784     err = DdeGetLastError(ddeInstance);
785     switch (err) {
786     case DMLERR_DATAACKTIMEOUT:
787     case DMLERR_EXECACKTIMEOUT:
788     case DMLERR_POKEACKTIMEOUT:
789     Tcl_SetStringObj(resultPtr,
790     "remote interpreter did not respond", -1);
791     break;
792    
793     case DMLERR_BUSY:
794     Tcl_SetStringObj(resultPtr, "remote server is busy", -1);
795     break;
796    
797     case DMLERR_NOTPROCESSED:
798     Tcl_SetStringObj(resultPtr,
799     "remote server cannot handle this command", -1);
800     break;
801    
802     default:
803     Tcl_SetStringObj(resultPtr, "dde command failed", -1);
804     }
805     }
806    
807     /*
808     *--------------------------------------------------------------
809     *
810     * Tcl_DdeObjCmd --
811     *
812     * This procedure is invoked to process the "dde" Tcl command.
813     * See the user documentation for details on what it does.
814     *
815     * Results:
816     * A standard Tcl result.
817     *
818     * Side effects:
819     * See the user documentation.
820     *
821     *--------------------------------------------------------------
822     */
823    
824     int
825     Tcl_DdeObjCmd(
826     ClientData clientData, /* Used only for deletion */
827     Tcl_Interp *interp, /* The interp we are sending from */
828     int objc, /* Number of arguments */
829     Tcl_Obj *CONST objv[]) /* The arguments */
830     {
831     enum {
832     DDE_SERVERNAME,
833     DDE_EXECUTE,
834     DDE_POKE,
835     DDE_REQUEST,
836     DDE_SERVICES,
837     DDE_EVAL
838     };
839    
840     static char *ddeCommands[] = {"servername", "execute", "poke",
841     "request", "services", "eval",
842     (char *) NULL};
843     static char *ddeOptions[] = {"-async", (char *) NULL};
844     int index, argIndex;
845     int async = 0;
846     int result = TCL_OK;
847     HSZ ddeService = NULL;
848     HSZ ddeTopic = NULL;
849     HSZ ddeItem = NULL;
850     HDDEDATA ddeData = NULL;
851     HDDEDATA ddeItemData = NULL;
852     HCONV hConv = NULL;
853     HSZ ddeCookie = 0;
854     char *serviceName, *topicName, *itemString, *dataString;
855     char *string;
856     int firstArg, length, dataLength;
857     DWORD ddeResult;
858     HDDEDATA ddeReturn;
859     RegisteredInterp *riPtr;
860     Tcl_Interp *sendInterp;
861     Tcl_Obj *objPtr;
862     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
863    
864     /*
865     * Initialize DDE server/client
866     */
867    
868     if (objc < 2) {
869     Tcl_WrongNumArgs(interp, 1, objv,
870     "?-async? serviceName topicName value");
871     return TCL_ERROR;
872     }
873    
874     if (Tcl_GetIndexFromObj(interp, objv[1], ddeCommands, "command", 0,
875     &index) != TCL_OK) {
876     return TCL_ERROR;
877     }
878    
879     switch (index) {
880     case DDE_SERVERNAME:
881     if ((objc != 3) && (objc != 2)) {
882     Tcl_WrongNumArgs(interp, 1, objv,
883     "servername ?serverName?");
884     return TCL_ERROR;
885     }
886     firstArg = (objc - 1);
887     break;
888     case DDE_EXECUTE:
889     if ((objc < 5) || (objc > 6)) {
890     Tcl_WrongNumArgs(interp, 1, objv,
891     "execute ?-async? serviceName topicName value");
892     return TCL_ERROR;
893     }
894     if (Tcl_GetIndexFromObj(NULL, objv[2], ddeOptions, "option", 0,
895     &argIndex) != TCL_OK) {
896     if (objc != 5) {
897     Tcl_WrongNumArgs(interp, 1, objv,
898     "execute ?-async? serviceName topicName value");
899     return TCL_ERROR;
900     }
901     async = 0;
902     firstArg = 2;
903     } else {
904     if (objc != 6) {
905     Tcl_WrongNumArgs(interp, 1, objv,
906     "execute ?-async? serviceName topicName value");
907     return TCL_ERROR;
908     }
909     async = 1;
910     firstArg = 3;
911     }
912     break;
913     case DDE_POKE:
914     if (objc != 6) {
915     Tcl_WrongNumArgs(interp, 1, objv,
916     "poke serviceName topicName item value");
917     return TCL_ERROR;
918     }
919     firstArg = 2;
920     break;
921     case DDE_REQUEST:
922     if (objc != 5) {
923     Tcl_WrongNumArgs(interp, 1, objv,
924     "request serviceName topicName value");
925     return TCL_ERROR;
926     }
927     firstArg = 2;
928     break;
929     case DDE_SERVICES:
930     if (objc != 4) {
931     Tcl_WrongNumArgs(interp, 1, objv,
932     "services serviceName topicName");
933     return TCL_ERROR;
934     }
935     firstArg = 2;
936     break;
937     case DDE_EVAL:
938     if (objc < 4) {
939     Tcl_WrongNumArgs(interp, 1, objv,
940     "eval ?-async? serviceName args");
941     return TCL_ERROR;
942     }
943     if (Tcl_GetIndexFromObj(NULL, objv[2], ddeOptions, "option", 0,
944     &argIndex) != TCL_OK) {
945     if (objc < 4) {
946     Tcl_WrongNumArgs(interp, 1, objv,
947     "eval ?-async? serviceName args");
948     return TCL_ERROR;
949     }
950     async = 0;
951     firstArg = 2;
952     } else {
953     if (objc < 5) {
954     Tcl_WrongNumArgs(interp, 1, objv,
955     "eval ?-async? serviceName args");
956     return TCL_ERROR;
957     }
958     async = 1;
959     firstArg = 3;
960     }
961     break;
962     }
963    
964     Initialize();
965    
966     if (firstArg != 1) {
967     serviceName = Tcl_GetStringFromObj(objv[firstArg], &length);
968     } else {
969     length = 0;
970     }
971    
972     if (length == 0) {
973     serviceName = NULL;
974     } else if ((index != DDE_SERVERNAME) && (index != DDE_EVAL)) {
975     ddeService = DdeCreateStringHandle(ddeInstance, serviceName,
976     CP_WINANSI);
977     }
978    
979     if ((index != DDE_SERVERNAME) &&(index != DDE_EVAL)) {
980     topicName = Tcl_GetStringFromObj(objv[firstArg + 1], &length);
981     if (length == 0) {
982     topicName = NULL;
983     } else {
984     ddeTopic = DdeCreateStringHandle(ddeInstance,
985     topicName, CP_WINANSI);
986     }
987     }
988    
989     switch (index) {
990     case DDE_SERVERNAME: {
991     serviceName = DdeSetServerName(interp, serviceName);
992     if (serviceName != NULL) {
993     Tcl_SetStringObj(Tcl_GetObjResult(interp),
994     serviceName, -1);
995     } else {
996     Tcl_ResetResult(interp);
997     }
998     break;
999     }
1000     case DDE_EXECUTE: {
1001     dataString = Tcl_GetStringFromObj(objv[firstArg + 2], &dataLength);
1002     if (dataLength == 0) {
1003     Tcl_SetStringObj(Tcl_GetObjResult(interp),
1004     "cannot execute null data", -1);
1005     result = TCL_ERROR;
1006     break;
1007     }
1008     hConv = DdeConnect(ddeInstance, ddeService, ddeTopic,
1009     NULL);
1010     DdeFreeStringHandle (ddeInstance, ddeService) ;
1011     DdeFreeStringHandle (ddeInstance, ddeTopic) ;
1012    
1013     if (hConv == NULL) {
1014     SetDdeError(interp);
1015     result = TCL_ERROR;
1016     break;
1017     }
1018    
1019     ddeData = DdeCreateDataHandle(ddeInstance, dataString,
1020     dataLength+1, 0, 0, CF_TEXT, 0);
1021     if (ddeData != NULL) {
1022     if (async) {
1023     DdeClientTransaction((LPBYTE) ddeData, 0xFFFFFFFF, hConv, 0,
1024     CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, &ddeResult);
1025     DdeAbandonTransaction(ddeInstance, hConv,
1026     ddeResult);
1027     } else {
1028     ddeReturn = DdeClientTransaction((LPBYTE) ddeData, 0xFFFFFFFF,
1029     hConv, 0, CF_TEXT, XTYP_EXECUTE, 30000, NULL);
1030     if (ddeReturn == 0) {
1031     SetDdeError(interp);
1032     result = TCL_ERROR;
1033     }
1034     }
1035     DdeFreeDataHandle(ddeData);
1036     } else {
1037     SetDdeError(interp);
1038     result = TCL_ERROR;
1039     }
1040     break;
1041     }
1042     case DDE_REQUEST: {
1043     itemString = Tcl_GetStringFromObj(objv[firstArg + 2], &length);
1044     if (length == 0) {
1045     Tcl_SetStringObj(Tcl_GetObjResult(interp),
1046     "cannot request value of null data", -1);
1047     return TCL_ERROR;
1048     }
1049     hConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL);
1050     DdeFreeStringHandle (ddeInstance, ddeService) ;
1051     DdeFreeStringHandle (ddeInstance, ddeTopic) ;
1052    
1053     if (hConv == NULL) {
1054     SetDdeError(interp);
1055     result = TCL_ERROR;
1056     } else {
1057     Tcl_Obj *returnObjPtr;
1058     ddeItem = DdeCreateStringHandle(ddeInstance,
1059     itemString, CP_WINANSI);
1060     if (ddeItem != NULL) {
1061     ddeData = DdeClientTransaction(NULL, 0, hConv, ddeItem,
1062     CF_TEXT, XTYP_REQUEST, 5000, NULL);
1063     if (ddeData == NULL) {
1064     SetDdeError(interp);
1065     result = TCL_ERROR;
1066     } else {
1067     dataString = DdeAccessData(ddeData, &dataLength);
1068     returnObjPtr = Tcl_NewStringObj(dataString, -1);
1069     DdeUnaccessData(ddeData);
1070     DdeFreeDataHandle(ddeData);
1071     Tcl_SetObjResult(interp, returnObjPtr);
1072     }
1073     } else {
1074     SetDdeError(interp);
1075     result = TCL_ERROR;
1076     }
1077     }
1078    
1079     break;
1080     }
1081     case DDE_POKE: {
1082     itemString = Tcl_GetStringFromObj(objv[firstArg + 2], &length);
1083     if (length == 0) {
1084     Tcl_SetStringObj(Tcl_GetObjResult(interp),
1085     "cannot have a null item", -1);
1086     return TCL_ERROR;
1087     }
1088     dataString = Tcl_GetStringFromObj(objv[firstArg + 3], &length);
1089    
1090     hConv = DdeConnect(ddeInstance, ddeService, ddeTopic, NULL);
1091     DdeFreeStringHandle (ddeInstance,ddeService) ;
1092     DdeFreeStringHandle (ddeInstance, ddeTopic) ;
1093    
1094     if (hConv == NULL) {
1095     SetDdeError(interp);
1096     result = TCL_ERROR;
1097     } else {
1098     ddeItem = DdeCreateStringHandle(ddeInstance, itemString, \
1099     CP_WINANSI);
1100     if (ddeItem != NULL) {
1101     ddeData = DdeClientTransaction(dataString,length+1, \
1102     hConv, ddeItem,
1103     CF_TEXT, XTYP_POKE, 5000, NULL);
1104     if (ddeData == NULL) {
1105     SetDdeError(interp);
1106     result = TCL_ERROR;
1107     }
1108     } else {
1109     SetDdeError(interp);
1110     result = TCL_ERROR;
1111     }
1112     }
1113     break;
1114     }
1115    
1116     case DDE_SERVICES: {
1117     HCONVLIST hConvList;
1118     CONVINFO convInfo;
1119     Tcl_Obj *convListObjPtr, *elementObjPtr;
1120     Tcl_DString dString;
1121     char *name;
1122    
1123     convInfo.cb = sizeof(CONVINFO);
1124     hConvList = DdeConnectList(ddeInstance, ddeService,
1125     ddeTopic, 0, NULL);
1126     DdeFreeStringHandle (ddeInstance,ddeService) ;
1127     DdeFreeStringHandle (ddeInstance, ddeTopic) ;
1128     hConv = 0;
1129     convListObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1130     Tcl_DStringInit(&dString);
1131    
1132     while (hConv = DdeQueryNextServer(hConvList, hConv), hConv != 0) {
1133     elementObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL);
1134     DdeQueryConvInfo(hConv, QID_SYNC, &convInfo);
1135     length = DdeQueryString(ddeInstance,
1136     convInfo.hszSvcPartner, NULL, 0, CP_WINANSI);
1137     Tcl_DStringSetLength(&dString, length);
1138     name = Tcl_DStringValue(&dString);
1139     DdeQueryString(ddeInstance, convInfo.hszSvcPartner,
1140     name, length + 1, CP_WINANSI);
1141     Tcl_ListObjAppendElement(interp, elementObjPtr,
1142     Tcl_NewStringObj(name, length));
1143     length = DdeQueryString(ddeInstance, convInfo.hszTopic,
1144     NULL, 0, CP_WINANSI);
1145     Tcl_DStringSetLength(&dString, length);
1146     name = Tcl_DStringValue(&dString);
1147     DdeQueryString(ddeInstance, convInfo.hszTopic, name,
1148     length + 1, CP_WINANSI);
1149     Tcl_ListObjAppendElement(interp, elementObjPtr,
1150     Tcl_NewStringObj(name, length));
1151     Tcl_ListObjAppendElement(interp, convListObjPtr, elementObjPtr);
1152     }
1153     DdeDisconnectList(hConvList);
1154     Tcl_SetObjResult(interp, convListObjPtr);
1155     Tcl_DStringFree(&dString);
1156     break;
1157     }
1158     case DDE_EVAL: {
1159     objc -= (async + 3);
1160     ((Tcl_Obj **) objv) += (async + 3);
1161    
1162     /*
1163     * See if the target interpreter is local. If so, execute
1164     * the command directly without going through the DDE server.
1165     * Don't exchange objects between interps. The target interp could
1166     * compile an object, producing a bytecode structure that refers to
1167     * other objects owned by the target interp. If the target interp
1168     * is then deleted, the bytecode structure would be referring to
1169     * deallocated objects.
1170     */
1171    
1172     for (riPtr = tsdPtr->interpListPtr; riPtr != NULL; riPtr
1173     = riPtr->nextPtr) {
1174     if (stricmp(serviceName, riPtr->name) == 0) {
1175     break;
1176     }
1177     }
1178    
1179     if (riPtr != NULL) {
1180     /*
1181     * This command is to a local interp. No need to go through
1182     * the server.
1183     */
1184    
1185     Tcl_Preserve((ClientData) riPtr);
1186     sendInterp = riPtr->interp;
1187     Tcl_Preserve((ClientData) sendInterp);
1188    
1189     /*
1190     * Don't exchange objects between interps. The target interp would
1191     * compile an object, producing a bytecode structure that refers to
1192     * other objects owned by the target interp. If the target interp
1193     * is then deleted, the bytecode structure would be referring to
1194     * deallocated objects.
1195     */
1196    
1197     if (objc == 1) {
1198     result = Tcl_EvalObjEx(sendInterp, objv[0], TCL_EVAL_GLOBAL);
1199     } else {
1200     objPtr = Tcl_ConcatObj(objc, objv);
1201     Tcl_IncrRefCount(objPtr);
1202     result = Tcl_EvalObjEx(sendInterp, objPtr, TCL_EVAL_GLOBAL);
1203     Tcl_DecrRefCount(objPtr);
1204     }
1205     if (interp != sendInterp) {
1206     if (result == TCL_ERROR) {
1207     /*
1208     * An error occurred, so transfer error information from the
1209     * destination interpreter back to our interpreter.
1210     */
1211    
1212     Tcl_ResetResult(interp);
1213     objPtr = Tcl_GetVar2Ex(sendInterp, "errorInfo", NULL,
1214     TCL_GLOBAL_ONLY);
1215     string = Tcl_GetStringFromObj(objPtr, &length);
1216     Tcl_AddObjErrorInfo(interp, string, length);
1217    
1218     objPtr = Tcl_GetVar2Ex(sendInterp, "errorCode", NULL,
1219     TCL_GLOBAL_ONLY);
1220     Tcl_SetObjErrorCode(interp, objPtr);
1221     }
1222     Tcl_SetObjResult(interp, Tcl_GetObjResult(sendInterp));
1223     }
1224     Tcl_Release((ClientData) riPtr);
1225     Tcl_Release((ClientData) sendInterp);
1226     } else {
1227     /*
1228     * This is a non-local request. Send the script to the server and poll
1229     * it for a result.
1230     */
1231    
1232     if (MakeDdeConnection(interp, serviceName, &hConv) != TCL_OK) {
1233     goto error;
1234     }
1235    
1236     objPtr = Tcl_ConcatObj(objc, objv);
1237     string = Tcl_GetStringFromObj(objPtr, &length);
1238     ddeItemData = DdeCreateDataHandle(ddeInstance, string, length+1, 0, 0,
1239     CF_TEXT, 0);
1240    
1241     if (async) {
1242     ddeData = DdeClientTransaction((LPBYTE) ddeItemData, 0xFFFFFFFF, hConv, 0,
1243     CF_TEXT, XTYP_EXECUTE, TIMEOUT_ASYNC, &ddeResult);
1244     DdeAbandonTransaction(ddeInstance, hConv, ddeResult);
1245     } else {
1246     ddeData = DdeClientTransaction((LPBYTE) ddeItemData, 0xFFFFFFFF, hConv, 0,
1247     CF_TEXT, XTYP_EXECUTE, 30000, NULL);
1248     if (ddeData != 0) {
1249    
1250     ddeCookie = DdeCreateStringHandle(ddeInstance,
1251     "$TCLEVAL$EXECUTE$RESULT", CP_WINANSI);
1252     ddeData = DdeClientTransaction(NULL, 0, hConv, ddeCookie,
1253     CF_TEXT, XTYP_REQUEST, 30000, NULL);
1254     }
1255     }
1256    
1257    
1258     Tcl_DecrRefCount(objPtr);
1259    
1260     if (ddeData == 0) {
1261     SetDdeError(interp);
1262     goto errorNoResult;
1263     }
1264    
1265     if (async == 0) {
1266     Tcl_Obj *resultPtr;
1267    
1268     /*
1269     * The return handle has a two or four element list in it. The first
1270     * element is the return code (TCL_OK, TCL_ERROR, etc.). The
1271     * second is the result of the script. If the return code is TCL_ERROR,
1272     * then the third element is the value of the variable "errorCode",
1273     * and the fourth is the value of the variable "errorInfo".
1274     */
1275    
1276     resultPtr = Tcl_NewObj();
1277     length = DdeGetData(ddeData, NULL, 0, 0);
1278     Tcl_SetObjLength(resultPtr, length);
1279     string = Tcl_GetString(resultPtr);
1280     DdeGetData(ddeData, string, length, 0);
1281     Tcl_SetObjLength(resultPtr, strlen(string));
1282    
1283     if (Tcl_ListObjIndex(NULL, resultPtr, 0, &objPtr) != TCL_OK) {
1284     Tcl_DecrRefCount(resultPtr);
1285     goto error;
1286     }
1287     if (Tcl_GetIntFromObj(NULL, objPtr, &result) != TCL_OK) {
1288     Tcl_DecrRefCount(resultPtr);
1289     goto error;
1290     }
1291     if (result == TCL_ERROR) {
1292     Tcl_ResetResult(interp);
1293    
1294     if (Tcl_ListObjIndex(NULL, resultPtr, 3, &objPtr) != TCL_OK) {
1295     Tcl_DecrRefCount(resultPtr);
1296     goto error;
1297     }
1298     length = -1;
1299     string = Tcl_GetStringFromObj(objPtr, &length);
1300     Tcl_AddObjErrorInfo(interp, string, length);
1301    
1302     Tcl_ListObjIndex(NULL, resultPtr, 2, &objPtr);
1303     Tcl_SetObjErrorCode(interp, objPtr);
1304     }
1305     if (Tcl_ListObjIndex(NULL, resultPtr, 1, &objPtr) != TCL_OK) {
1306     Tcl_DecrRefCount(resultPtr);
1307     goto error;
1308     }
1309     Tcl_SetObjResult(interp, objPtr);
1310     Tcl_DecrRefCount(resultPtr);
1311     }
1312     }
1313     }
1314     }
1315     if (ddeCookie != NULL) {
1316     DdeFreeStringHandle(ddeInstance, ddeCookie);
1317     }
1318     if (ddeItem != NULL) {
1319     DdeFreeStringHandle(ddeInstance, ddeItem);
1320     }
1321     if (ddeItemData != NULL) {
1322     DdeFreeDataHandle(ddeItemData);
1323     }
1324     if (ddeData != NULL) {
1325     DdeFreeDataHandle(ddeData);
1326     }
1327     if (hConv != NULL) {
1328     DdeDisconnect(hConv);
1329     }
1330     return result;
1331    
1332     error:
1333     Tcl_SetStringObj(Tcl_GetObjResult(interp),
1334     "invalid data returned from server", -1);
1335    
1336     errorNoResult:
1337     if (ddeCookie != NULL) {
1338     DdeFreeStringHandle(ddeInstance, ddeCookie);
1339     }
1340     if (ddeItem != NULL) {
1341     DdeFreeStringHandle(ddeInstance, ddeItem);
1342     }
1343     if (ddeItemData != NULL) {
1344     DdeFreeDataHandle(ddeItemData);
1345     }
1346     if (ddeData != NULL) {
1347     DdeFreeDataHandle(ddeData);
1348     }
1349     if (hConv != NULL) {
1350     DdeDisconnect(hConv);
1351     }
1352     return TCL_ERROR;
1353     }
1354    
1355    
1356     /* $History: tclwindde.c $
1357     *
1358     * ***************** Version 1 *****************
1359     * User: Dtashley Date: 1/02/01 Time: 12:50a
1360     * Created in $/IjuScripter, IjuConsole/Source/Tcl Base
1361     * Initial check-in.
1362     */
1363    
1364     /* End of TCLWINDDE.C */

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25