Tcl命令trace的实现
trace 的用途
在Tcl里利用trace
命令可以在一个命令执行前和执行后执行预先设置的回掉函数。这使得在不修改程序代码的情况下改变程序代码的逻辑成为可能。
trace 的内部实现
- 文件:
generic/tclTrace.c
trace的注册
回掉函数通过Tcl_TraceCommand
实现注册。可以看到trace的注册是与Tcl命令对应的数据结构绑定在一起的。通过一个链表把当前命令的trace注册串起来。
int Tcl_TraceCommand(
Tcl_Interp *interp,
const char *cmdName, // 命令名
int flags,
Tcl_CommandTraceProc *proc,
ClientData clientData)
{
cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL,
TCL_LEAVE_ERR_MSG);
tracePtr = Tcl_Alloc(sizeof(CommandTrace));
tracePtr->traceProc = proc;
tracePtr->clientData = clientData;
tracePtr->flags = flags &
(TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC);
tracePtr->nextPtr = cmdPtr->tracePtr;
tracePtr->refCount = 1;
cmdPtr->tracePtr = tracePtr;
}
可以看到,后执行的trace命令排在链表的前面。
trace的调用
上面已经提到trace相关的信息是保存在Tcl命令对应的数据结构里的。
Tcl解释器在执行每条命令时,就有机会检查和启动trace回掉函数的执行。
int TclCheckExecutionTraces(...)
{
for (tracePtr = cmdPtr->tracePtr;
(traceCode == TCL_OK) && (tracePtr != NULL);
tracePtr = active.nextTracePtr) {
TraceExecutionProc(...); // 触发调用
}
}
enterstep 和 leavestep 的实现
enterstep
和leavestep
会以enter
和leave
同样的方式注册在命令对应的数据结构上。
在enter
事件发生触发TraceExecutionProc()
函数时注册解释器层面的trace。
int TraceExecutionProc()
{
...
/*
* Third, if there are any step execution traces for this proc, we
* register an interpreter trace to invoke enterstep and/or leavestep
* traces. We also need to save the current stack level and the proc
* string in startLevel and startCmd so that we can delete this
* interpreter trace when it reaches the end of this proc.
*/
if ((flags & TCL_TRACE_ENTER_EXEC) && (tcmdPtr->stepTrace == NULL)
&& (tcmdPtr->flags & (TCL_TRACE_ENTER_DURING_EXEC |
TCL_TRACE_LEAVE_DURING_EXEC))) {
register unsigned len = strlen(command) + 1;
tcmdPtr->startLevel = level;
tcmdPtr->startCmd = Tcl_Alloc(len);
memcpy(tcmdPtr->startCmd, command, len);
tcmdPtr->refCount++;
tcmdPtr->stepTrace = Tcl_CreateObjTrace(interp, 0,
(tcmdPtr->flags & TCL_TRACE_ANY_EXEC) >> 2,
TraceExecutionProc, tcmdPtr, CommandObjTraceDeleted);
}
...
}
Tcl_Trace Tcl_CreateObjTrace(
Tcl_Interp *interp, /* Tcl interpreter */
int level, /* Maximum nesting level */
int flags, /* Flags, see above */
Tcl_CmdObjTraceProc *proc, /* Trace callback */
ClientData clientData, /* Client data for the callback */
Tcl_CmdObjTraceDeleteProc *delProc)
/* Function to call when trace is deleted */
)
{
...
iPtr->tracePtr = tracePtr; // 注册解释器层面的trace回调函数
...
}
回头看trace的触发
- 文件:
generic/tclBasic.c
static int TEOV_RunEnterTraces(...)
{
...
if (iPtr->tracePtr) {
traceCode = TclCheckInterpTraces(interp, command, length,
cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv);
}
if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && (traceCode == TCL_OK)) {
traceCode = TclCheckExecutionTraces(interp, command, length,
cmdPtr, TCL_OK, TCL_TRACE_ENTER_EXEC, objc, objv);
}
...
}