/[dtapublic]/projs/trunk/shared_source/c_tcl_base_7_5_w_mods/tclwindde.c
ViewVC logotype

Contents of /projs/trunk/shared_source/c_tcl_base_7_5_w_mods/tclwindde.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 64 - (show annotations) (download)
Sun Oct 30 04:21:11 2016 UTC (8 years, 1 month ago) by dashley
File MIME type: text/plain
File size: 38781 byte(s)
Adjust line endings to Windows style.
Set properties to expand the "Header" keyword.
Change header and footer.
1 /* $Header$ */
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 */

Properties

Name Value
svn:keywords Header

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25