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 的实现

enterstepleavestep会以enterleave同样的方式注册在命令对应的数据结构上。

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);
    }
    ...
}