Critcl does C++

Early in 2003, Bill Wallace contributed a patch which makes it practical [with which version?] to write

    critcl::config language c++

Re version: see changelog at [L1 ] -jcw


But what's the point? Since Critcl is at its best coding tiny bits of what inventor JCW calls "grease", what's the advantage of C++ over C? A concrete example will probably be instructive ...


KBK Perhaps the point is that many applications, especially on Windows, export APIs that are accessible only from C++? if you're using Critcl to build a Tcl interface to one of them, you'll need something like Bill's patch.

Another use is to glue to existing libraries which have a C++ API -jcw


How does this work? Are there any examples? When i insert the above code my library will not build complains about ns_XX_Init not being found(where XX is the name of the namespace) [email protected]

24apr03 jcw - Ah, thanks for pointing this out. Turns out that you also need to add "critcl::clibraries -lstdc++". Here's an example (output is "123"):

  package require critcl

  critcl::config language c++
  critcl::clibraries -lstdc++

  critcl::ccode {
    class A {
      int value;
    public:
      A() : value (123) {}
      operator int() const { return value; }
    };
  }

  critcl::cproc tryplus {} int {
    A var;
    return var;
  }

  puts [tryplus]

13jun03 wgm - with some additional Tcl glue it's possible to easily build a direct interface to a C++ class. The Tcl glue (using sections of code out of "critcl" and beginners Tcl) is:

 package require critcl
 proc processargs {typesArray names cnames}  {
    upvar $typesArray types
    set body ""
    foreach x $names c $cnames {
        set t $types($x)
        switch -- $t {
            int - long - float - double - char* - Tcl_Obj* {
                append body "            $t $c;\n"
            }
            default {
                append body "            void* $c;\n"
            }
        }
    }
    set n 1
    foreach x $names c $cnames {
        set t $types($x)
        incr n
        switch -- $t {
            int {
                append body "            if (Tcl_GetIntFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
                append body "                return TCL_ERROR;\n"
            }
            long {
                append body "            if (Tcl_GetLongFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
                append body "                return TCL_ERROR;\n"
            }
            float {
                append body "            \{ double tmp;\n"
                append body "                if (Tcl_GetDoubleFromObj(ip, objv\[$n], &tmp) != TCL_OK)\n"
                append body "                   return TCL_ERROR;\n"
                append body "                $c = (float) tmp;\n"
                append body "            \}\n"
            }
            double {
                append body "            if (Tcl_GetDoubleFromObj(ip, objv\[$n], &$c) != TCL_OK)\n"
                append body "                return TCL_ERROR;\n"
            }
            char* {
                append body "            $c = Tcl_GetString(objv\[$n]);\n"
            }
            default {
                append body "            $c = objv\[$n];\n"
            }
        }
    }
    return $body 
 } 
 proc c++command {tclname class constructors methods} {
 #
 # Build the body of the function to define a new tcl command for the C++ class
    set helpline {}
    set classptr ptr_$tclname
    set comproc "    $class* $classptr;\n"
    append comproc "    switch (objc) \{\n"

    foreach adefs $constructors {
       array set types {}
        set names {}
        set cargs {}
        set cnames {}
        
        foreach {t n} $adefs {
            set types($n) $t
            lappend names $n
            lappend cnames _$n
            lappend cargs "$t $n"
        }
        lappend helpline "$tclname pathName [join $names { }]"      
        set nargs [llength $names]
        set ncargs [expr $nargs+2]
       append comproc "        case $ncargs: \{\n"
        
        if {$nargs == 0} {
            append comproc "            $classptr = new $class\();\n"
        } else  {
            append comproc [processargs types $names $cnames]
            append comproc "            $classptr = new $class\([join $cnames {, }]);\n"
         }
         append comproc "            break;\n"
         append comproc "        \}\n"

    }
    append comproc "        default: \{\n"
    append comproc "            Tcl_SetResult(ip, \"wrong # args: should be either [join $helpline { or }]\",TCL_STATIC);\n"
    append comproc "            return TCL_ERROR;\n"
    append comproc "        \}\n"
    append comproc "    \}\n"

    append comproc "    if ( $classptr == NULL ) \{\n"
    append comproc "        Tcl_SetResult(ip, \"Not enough memory to allocate a new $tclname\", TCL_STATIC);\n"
    append comproc "        return TCL_ERROR;\n"
    append comproc "    \}\n"


    append comproc "    Tcl_CreateObjCommand(ip, Tcl_GetString(objv\[1]), cmdproc_$tclname, (ClientData) $classptr, delproc_$tclname);\n"
    append comproc "    return TCL_OK;\n"
 #
 #  Build the body of the c function called when the object is deleted
 #
    set delproc "void delproc_$tclname\(ClientData cd) \{\n"
    append delproc "    if (cd != NULL)\n"
    append delproc "        delete ($class*) cd;\n"
    append delproc "\}\n"

 #
 # Build the body of the function that processes the tcl commands for the class
 #
    set cmdproc "int cmdproc_$tclname\(ClientData cd, Tcl_Interp* ip, int objc, Tcl_Obj *CONST objv\[]) \{\n"
    append cmdproc "    int index;\n"
    append cmdproc "    $class* $classptr = ($class*) cd;\n"

    set rtypes {}
    set tnames {}
    set mnames {}
    set adefs {}
    foreach method $methods {
        foreach {rt n a} $method {
            lappend rtypes $rt
            lappend tnames [lindex [split $n | ] 0]
            set tmp [lindex [split $n | ] 1]
            if { $tmp == ""}  {
                lappend mnames  [lindex [split $n | ] 0]
            } else {
                lappend mnames [lindex [split $n | ] 1]
            }
            lappend adefs $a
        }
    }
    append cmdproc "    const char* cmds\[]=\{\"[join $tnames {","}]\",NULL\};\n"
    append cmdproc "    if (objc<2) \{\n"
    append cmdproc "       Tcl_WrongNumArgs(ip, 1, objv, \"expecting pathName option\");\n"
    append cmdproc "       return TCL_ERROR;\n"
    append cmdproc "    \}\n\n"
    append cmdproc "    if (Tcl_GetIndexFromObj(ip, objv\[1], cmds, \"option\", TCL_EXACT, &index) != TCL_OK)\n"
    append cmdproc "        return TCL_ERROR;\n"
    append cmdproc "    switch (index) \{\n"

    set ndx 0
    foreach rtype $rtypes tname $tnames mname $mnames adef $adefs {
        array set types {}
        set names {}
        set cargs {}
        set cnames {}

        switch -- $rtype {
            ok      { set rtype2 "int" }
            string -
            dstring -
            vstring { set rtype2 "char*" }
            default { set rtype2 $rtype }
        }
      
        foreach {t n} $adef {
            set types($n) $t
            lappend names $n
            lappend cnames _$n
            lappend cargs "$t $n"
        }
        set helpline "$tname [join $names { }]"      
        set nargs [llength $names]
        set ncargs [expr $nargs+2]
       
        append cmdproc "        case $ndx: \{\n"
        append cmdproc "            if (objc==$ncargs) \{\n"
        append cmdproc  [processargs types $names $cnames]
        append cmdproc "                "
        if {$rtype != "void"} {
            append cmdproc "$rtype2 rv = "
        }
        append cmdproc "$classptr->$mname\([join $cnames {, }]);\n"
        append cmdproc "                "
        switch -- $rtype {
           void     { }
           ok { append cmdproc "return rv;" }
           int { append cmdproc "Tcl_SetIntObj(Tcl_GetObjResult(ip), rv);" }
           long { append cmdproc " Tcl_SetLongObj(Tcl_GetObjResult(ip), rv);" }
           float -
           double { append cmdproc "Tcl_SetDoubleObj(Tcl_GetObjResult(ip), rv);" }
           char* { append cmdproc "Tcl_SetResult(ip, rv, TCL_STATIC);" }
           string -
           dstring { append cmdproc "Tcl_SetResult(ip, rv, TCL_DYNAMIC);" }
           vstring { append cmdproc "Tcl_SetResult(ip, rv, TCL_VOLATILE);" }
           default  { append cmdproc "Tcl_SetObjResult(ip, rv); Tcl_DecrRefCount(rv);" }
        }
        append cmdproc "\n"
        append cmdproc "                "
        if {$rtype != "ok"} { append cmdproc "return TCL_OK;\n" }
 
        append cmdproc "            \} else \{\n"
        append cmdproc "               Tcl_WrongNumArgs(ip, 1, objv, \"$helpline\");\n"
        append cmdproc "               return TCL_ERROR;\n"
        append cmdproc "            \}\n"                
        append cmdproc "        \}\n"
        incr ndx
    }
    append cmdproc "    \}\n\}\n"
   

    critcl::ccode $delproc
    critcl::ccode $cmdproc
    critcl::ccommand $tclname {dummy ip objc objv} $comproc
 }

Then a C++ class like the one defined below:

 critcl::config language c++
 critcl::clibraries -lstdc++
 critcl::ccode {
    class Counter {
    public:
        Counter(int startValue=0);
        Counter operator++();
        void set( int newValue);
        void reset();
        int value() const;
    private:
        int count;
        int resetValue;
    };

    Counter::Counter(int startValue) : count(startValue), resetValue(startValue) {}
    Counter Counter::operator++() {
        count++;
    }
    void Counter::set(int newValue) {
        count=newValue;
    }
    void Counter::reset() {
        count=resetValue;
    }
    int Counter::value() const {
        return count;
    }
 }

Can have a Tcl interface generated by using the Tcl glue proc "c++command":

 c++command counter Counter {
    {}
    {int start_value}
 } {
    {void set {int new_value}}
    {void reset {}}
    {void incr|operator++ {}}
    {int value {}}
 }

The arguments to "c++command" are:

  • the name of the Tcl command to create instances of the C++ class.
  • the C++ class name.
  • a list describing the arguments of the C++ constructors to be included in the interface.
  • a list describing the C++ methods to be included in the Tcl interface. Note that "|" can be used map a different Tcl name for the C++ method and "void" is acceptable if you don't need the return result of the method.

The C++ class can then be used in Tcl like this:

 counter p 10
 puts "Initial Counter:  [p value]"
 p incr
 p incr
 p incr
 puts "Counter after 3 increments: [p value]"
 p set 20
 puts "Counter after set to 20: [p value]"
 p reset
 puts "Counter after reset: [p value]"

critcl 2 has been updated to support the above - changes now in the SVN repository, slightly modified to preserve the invoking namespace - stevel - June 12, 2008