Writing Tcl-Based Applications In C, by John Ousterhout
APN Warning: Much of this material is outdated and deprecated as it precedes 8.0
Tcl/Tk Tutorial, Part IV
Outline
AMG: We now have the [socket] command, so it's not necessary to write C code to access sockets. Perhaps if you want access to, say, the raw link-layer, you may need to dip into C, but then again, if you write a sufficiently-generic facility, publish it as a loadable package so that no one will have to reinvent your wheel.
Lars H, 10 June 2005: Structuring is probably not an advantage of C over Tcl anymore, either.
Example: Weather Reports
- Retrieve report, format, print on standard output.
- Open socket to weather server.
- Select station.
- Retrieve first line of report.... - Return list of available stations.
- Given station name, retrieve report. .dlg.bottom.ok
file3 or stdin.dlg.bottom.ok configure -fg red
string compare $x $y
tmp 53 hi 68 lo 37 precip .02 sky part
wthr_stations
wthr_report
midi_play
- Allows packages to coexist without name clashes.AMG: These days you're better off using namespaces than package-prefixed naming conventions.
Tcl_Interp structure encapsulates execution state:
Can have many interpreters in a single application (but usually just one).
Creating and deleting interpreters:
Tcl_Interp *interp; interp = Tcl_CreateInterp(); Tcl_DeleteInterp(interp);
Executing Tcl Scripts
int code; code = Tcl_Eval(interp, "set a 1"); code = Tcl_EvalFile(interp, "init.tcl");
- TCL_OK: normal completion.
- TCL_ERROR: error occurred.Quote from Tcl 8.5 manual [L1 ]: The direct use of interp->result is strongly deprecated (see Tcl_SetResult [L2 ]).
Where Do Scripts Come From?
Creating New Tcl Commands
int EqCmd(ClientData clientData, Tcl_Interp
*interp, int argc, char **argv) {
if (argc != 3) {
interp->result = "wrong # args";
return TCL_ERROR;
}
if (strcmp(argv[1], argv[2]) == 0) {
interp->result = "1";
} else {
interp->result = "0";
}
return TCL_OK;
}AMG: Since 8.0, Tcl has snazzy object-based commands wherein char **argv is replaced with Tcl_Obj *const objv[] for a terrific speed boost. Hot stuff. Check it out.
Creating New Tcl Commands, cont'd
Tcl_CreateCommand(interp, "eq", EqCmd,
(ClientData) NULL, ...);
Tcl_DeleteCommand(interp, "eq"); ClientData
Tcl_CreateCommand(interp, "eq", EqCmd,
clientData, ...);
int EqCmd(ClientData clientData, ...) {...} - Tcl_CreateCommand(... (ClientData) gizmoPtr,
...);
- gizmoPtr = (Gizmo *) clientData;Conventions For Packages
Goal: make it easy to develop and use Tcl extensions.
- Pick short prefix for package, e.g. rdb.
- Use in all global names:
- C procedure: Rdb_Open
- C variable: rdb_NumRecords
- Tcl command: rdb_query
- See Tcl book and Tcl/Tk Engineering Manual for more details.Packages, cont'd
- Named after package: Rdb_Init.
- Creates package's commands.
- Evaluates startup script, if any.
int Rdb_Init(Tcl_Interp *interp) {
Tcl_CreateCommand(...);
Tcl_CreateCommand(...);
...
return Tcl_EvalFile(interp,
"/usr/local/lib/rdb/init.tcl");
}Packages, cont'd
- Compile as shared library, e.g. on Solaris:
cc -K pic -c rdb.c
ld -G -z text rdb.o -o rdb.so
- Dynamically load into tclsh or wish:
load rdb.so Rdb
- Tcl will call Rdb_Init to initialize the package.Managing The Result String
- Permit results of any length.
- Avoid malloc overheads if possible.
- Avoid storage reclamation problems.
- Keep as simple as possible.Result String, cont'd
interp->result = "0";
sprintf(interp->result, "Value is %d", i);
Result String, cont'd
interp->result = malloc(2000);
...
interp->freeProc = free;
Procedures For Managing Result
Tcl_SetResult(interp, string, ...);
Tcl_AppendResult(interp, string,
string, ... string, (char *) NULL);
Tcl_AppendElement(interp, string);
Tcl_ResetResult(interp);Utility Procedures: Parsing
int value, code; code = Tcl_GetInt(interp, argv[1], &value);
Tcl_GetDouble Tcl_ExprDouble Tcl_GetBoolean Tcl_ExprBoolean Tcl_ExprLong Tcl_ExprString
Utility Procedures: Variables
char *value; value = Tcl_GetVar(interp, "a", ...); Tcl_SetVar(interp, "a", "new", ...); Tcl_UnsetVar(interp, "a", ...);
Tcl_TraceVar(interp, "a",
TCL_TRACE_READS|TCL_TRACE_WRITES,
traceProc, clientData); - Can monitor accesses.
- Can override value read or written.Other Utility Procedures
Tcl_SplitList(...) Tcl_Merge(...)
Tcl_InitHashTable(...) Tcl_CreateHashEntry(...) Tcl_FindHashEntry(...) Tcl_DeleteHashEntry(...) Tcl_DeleteHashTable(...)
Tcl_DStringInit(...) Tcl_DStringAppend(...) Tcl_DStringAppendElement(...) Tcl_DStringValue(...) Tcl_DStringFree(...)
Summary