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