0
votes

I have a Tcl code that is being sourced from a C application using:

Tcl_Eval(tcl_interp, "source nmsp.tcl")

Everything runs fine. However, the namespace scope isn't preserved. For example, the following file:

#!/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\::*]"

when run by itself will produce this output:

Namespace calling ::bob::proc2

But when sourcing from Tcl_Eval will not print anything. In fact, the proc2 procedure can be called by itself fine without any namespace designation.

Anyone knows what may be causing it? I really like the encapsulation that namespaces provide.

3

3 Answers

1
votes

Seems fine to me.

I created the following Tcl extension to perform your 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");
}

Being on windows, I compiled this using:

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

and then created a test_namespace.tcl file with the content you posted above. Running this produces the following:

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

and further introspection shows things are as I would expect from that script:

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

You probably are doing something weird in your C code first if this is really not working for you.

Update

The above example is for extending tcl with a compiled package. Apparently the OP is embedding Tcl into some other application. A trivial example of doing this is provided here which also runs the same command to the same effect as above. In reality when embedding Tcl into an application the code should use the tclAppInit.c file and provide its own Tcl_AppInit function. By running the usual Tcl_Main you get the full capabilities for processing events (needed for fileevents or after commands) and the interactive shell. An example of that follows the trivial version:

/* 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;
}

Running the above:

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

A better embedding scheme that uses tclAppInit to extend a stock Tcl interpreter:

#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"

Building and running this also produces the same output as previous versions:

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
0
votes

As far as I'm aware, the only way your code could not produce the message you expect is if the current namespace at the point it was called was other than the global namespace. Suppose the current namespace was ::foo, the first namespace eval would work in ::foo::bob and the inner one in ::foo::bob::joe. Unqualified procedure definitions put their definitions in the current namespace, of course. To detect if this is really the case, add the output of the namespace current command to your printed message.

If this is the problem, change the outer namespace eval to use a fully-qualified name:

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

If you're writing a Tcl package this is very strongly recommended and it is a good idea even if you're not going all the way to that extent; you can never be quite sure what context a script is going to be sourced in. (The inner namespace eval is OK though; that's running inside a known context, the outer namespace eval.)

0
votes

Sorry guys for trouble. I found the problem. The problem is in my code. I had the following procedure which I completely forgot about:

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
    }
}

The purpose of this procedure, mainly, is to add entry and exit point tracers to all the procedures in my code. For some reason, which I'm still investigating, it also removes the namespace scoping.