【问题标题】:Tcl namespaces in code executing from C从 C 执行的代码中的 Tcl 命名空间
【发布时间】:2012-09-14 21:10:46
【问题描述】:

我有一个从 C 应用程序获取的 Tcl 代码:

Tcl_Eval(tcl_interp, "source nmsp.tcl")

一切运行良好。 但是,不会保留命名空间范围。例如以下文件:

#!/bin/sh

# namespace evaluation
namespace eval bob {
    namespace eval joe {
        proc proc1 {} {}
    }
    proc proc2 {} {
        puts "proc2"
    }
    proc ::proc3 {} {
        puts "proc3"
    }
    proc joe::proc4 {} {
        puts "proc4"
    }
}

puts "Namespace calling [info procs ::bob\::*]"

当自己运行时会产生这个输出:

Namespace calling ::bob::proc2

但是当从 Tcl_Eval 采购时不会打印任何东西。事实上,proc2 过程可以在没有任何命名空间指定的情况下自行调用。

有人知道是什么原因造成的吗?我真的很喜欢命名空间提供的封装。

【问题讨论】:

    标签: tcl


    【解决方案1】:

    我觉得很好。

    我创建了以下 Tcl 扩展来执行您的 Tcl_Eval:

    #include <tcl.h>
    
    static int
    DotestCmd(ClientData clientData, Tcl_Interp *interp,
              int objc, Tcl_Obj *const objv[])
    {
        return Tcl_Eval(interp, "source test_namespace.tcl");
    }
    
    int DLLEXPORT
    Testnamespace_Init(Tcl_Interp *interp)
    {
        if (Tcl_InitStubs(interp, "8.4", 0) == NULL) {
            return TCL_ERROR;
        }
        Tcl_CreateObjCommand(interp, "dotest", DotestCmd, NULL, NULL);
        return Tcl_PkgProvide(interp, "testnamespace", "1.0");
    }
    

    在 Windows 上,我使用以下代码编译:

    cl -nologo -W3 -O2 -MD -DNDEBUG -DUSE_TCL_STUBS -I\opt\tcl\include -c test_namespace.c
    link -dll -release -out:testnamespace.dll test_namespace.obj \opt\tcl\lib\tclstub85.lib
    

    然后使用您在上面发布的内容创建了一个 test_namespace.tcl 文件。运行它会产生以下结果:

    C:\opt\tcl\src>tclsh
    % load testnamespace.dll Testnamespace
    % dotest
    Namespace calling ::bob::proc2
    %
    

    进一步的反省表明事情与我对该脚本的期望一样:

    % namespace children ::
    ::platform ::activestate ::bob ::tcl
    % namespace children ::bob
    ::bob::joe
    %
    

    如果这真的不适合你,你可能首先在你的 C 代码中做了一些奇怪的事情。

    更新

    上面的例子是用一个编译好的包来扩展 tcl。显然,OP 正在将 Tcl 嵌入到其他一些应用程序中。此处提供了执行此操作的一个简单示例,它也运行相同的命令以达到与上述相同的效果。实际上,当将 Tcl 嵌入到应用程序中时,代码应该使用 tclAppInit.c 文件并提供它自己的 Tcl_AppInit 函数。通过运行通常的 Tcl_Main,您可以获得处理事件(fileevents 或 after 命令所需)和交互式 shell 的全部功能。简单版本之后的一个示例:

    /* trivial embedding Tcl example */
    #include <tcl.h>
    #include <locale.h>
    
    int
    main(int argc, char *argv[])
    {
        Tcl_Interp *interp = NULL;
        int r = TCL_ERROR;
    
        setlocale(LC_ALL, "C");
        interp = Tcl_CreateInterp();
        if (interp != NULL) {
            Tcl_FindExecutable(argv[0]);
            r = Tcl_Eval(interp, "source test_namespace.tcl");
            if (TCL_OK == r)
                r = Tcl_Eval(interp, "puts [namespace children ::bob]");
            Tcl_DeleteInterp(interp);
        }
        return r;
    }
    

    运行上述:

    C:\opt\tcl\src>cl -nologo -W3 -O2 -MD -I\opt\tcl\include test_namesp_embed.c -link -subsystem:console -release -libpath:\opt\tcl\lib tcl85.lib
    test_namesp_embed.c
    
    C:\opt\tcl\src>test_namesp_embed.exe test_namespace.tcl
    Namespace calling ::bob::proc2
    ::bob::joe
    

    一个更好的嵌入方案,它使用 tclAppInit 来扩展一个普通的 Tcl 解释器:

    #include <tcl.h>
    #include <locale.h>
    
    #define TCL_LOCAL_APPINIT Custom_AppInit
    int
    Custom_AppInit(Tcl_Interp *interp)
    {
        return Tcl_Eval(interp, "source test_namespace.tcl");
    }
    
    #include "/opt/tcl/src/tcl.git/win/tclAppInit.c"
    

    构建和运行它也会产生与以前版本相同的输出:

    C:\opt\tcl\src>cl -nologo -W3 -O2 -MD -I\opt\tcl\include test_namesp_embed.c -link -subsystem:console -release -libpath:\opt\tcl\lib tcl85.lib
    C:\opt\tcl\src>test_namesp_embed.exe
    Namespace calling ::bob::proc2
    % namespace children ::bob
    ::bob::joe
    % exit
    

    【讨论】:

    • 为什么要检查 Tcl 8.4?
    • 我没有——那里的 8.4 指定了此处所需的 Tcl API 的最低版本。鉴于它是在很久以前发布的,8.4 是我预计必须处理的最旧版本。如果我说“8.2”,我们会放弃一些功能,并且能够使用 8.2 以后的任何东西。
    • 我反其道而行之。您创建了一个 DLL,然后将其加载到 tclsh。我做了什么:我创建了一个加载解释器的 c 应用程序,然后调用 Tcl_Eval(interp, "source test_namespace.tcl");不涉及 tclsh。我想知道这是否会有所作为。
    • @user 没关系。当您从 C 代码中执行 Tcl_Eval 时,它始终使用当前范围;当你没有做任何事情来改变它时,那就是全局的。
    【解决方案2】:

    据我所知,您的代码无法产生您期望的消息的唯一方法是,如果当前命名空间在调用它时不是全局命名空间。假设当前命名空间是::foo,第一个namespace eval 将在::foo::bob 中工作,而内部的在::foo::bob::joe 中工作。当然,不合格的过程定义会将其定义放在当前名称空间中。 要检测是否确实如此,请将namespace current 命令的输出添加到您打印的消息中。

    如果这是问题所在,请将外部 namespace eval 更改为使用完全限定名称:

    namespace eval ::bob {    # <<<<<<< NOTE THIS HERE!
        namespace eval joe {
            proc proc1 {} {}
        }
        proc proc2 {} {
            puts "proc2"
        }
        proc ::proc3 {} {
            puts "proc3"
        }
        proc joe::proc4 {} {
            puts "proc4"
        }
    }
    

    如果您正在编写一个 Tcl 包,这是强烈推荐,即使您没有一路走到那个程度,这也是一个好主意;您永远无法确定脚本将在什么上下文中使用 sourced。(不过,内部 namespace eval 是可以的;它在已知上下文中运行,外部 namespace eval。)

    【讨论】:

      【解决方案3】:

      抱歉,麻烦各位了。我发现了问题。 问题出在我的代码中。我完全忘记了以下程序:

      rename proc _proc
      _proc proc {name args body} {
          global pass_log_trace
      
          set g_log_trace "0"
          if {[info exists pass_log_trace]} {
              set g_log_trace $pass_log_trace
          }
      
          # simple check if we have double declaration of the same procedure
          if {[info procs $name] != ""} {
              puts "\nERROR: redeclaration of procedure: $name"
          }
      
          _proc $name $args $body
      
          if {$g_log_trace != 0} {
              trace add execution $name enter trace_report_enter
              trace add execution $name leave trace_report_leave
          }
      }
      

      这个过程的主要目的是为我的代码中的所有过程添加入口和出口点跟踪器。出于某种原因,我仍在调查,它还删除了命名空间范围。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-17
        • 1970-01-01
        相关资源
        最近更新 更多