Version 3 of Swig example showing access to C structures from Tcl

Updated 2006-10-17 00:59:52

Communicating between C and embedded Tcl Interpreter

In a recent project, a customer wanted to customize the behavior of a complex program by somehow incorporating flexible rules that would be invoked upon the input data and the interpreted results passed back to the C program. The initial proposal was to create a CORBA object that would be used to pass the data to a Java program that ran a javascript interpreter. The data would be run through a java script program which implemented the flexible rules and the marshalling and unmarshalling of the data. The results returned using the same CORBA object. Well you don't have to be too sharp to see that this would be a maintenance nightmare and this was just another "use java at all cost" occasions ( don't get me started ...). Since I heard about SWIG I suggested that we embed the rules as a tcl script instead and use swig to do the data translation from C to tcl and back again. What follows in the code snippets below is a simplified version of using swig as mentioned. Although with this swig solutin there really isn't any marshalling/unmarshalling or network involved which makes it significantly faster. Maintenance is also improved because all the code is in one location.

I trimmed most of the fat from the example for space reasons, but one should be able to quickly cut and paste the code into files and make the example ( provided you have swig installed ). The code below has been tested on Solaris and Linux. (Solaris 2.8) , I used the SUNWspro6 compiler and on Linux, (Fedora Core 5/4/3) gcc. I ran into one problem that caused the loading of extensions into interpreted tcl script. Apparently, the global variable tcl_library is not set when you create an interpreter so the init.tcl does not run and none of the auto_xxx variables or utilities are available. At the top of p.tcl you will see how I fix the situation. I call Tcl_FindExecutable but it does not seem to help. This may be unique to my setup as I don't use the usuall install paths. If anyone can come up with a better way please feel free to correct the code.


    Makefile

    INTERFACE = myif.i
    WRAPFILE = $(INTERFACE:.i=_wrap.c)
    WRAPOBJ = $(INTERFACE:.i=_wrap.o)
    TARGET = myif.so # output
    # compiler options
    #CC = /opt/SUNWspro6/bin/cc
    CC = gcc
    CFLAGS = 
    INCLUDE = 
    #SWIG OPTIONS
    SWIG = /opt/usr/bin/swig
    SWIGOPT = -tcl8
    SIWGCC = $(CC)
    # Rules for creating .o files form source.
    COBJS = $(SRCS:.c=.o)
    ALLOBJS = $(COBJS) $(OBJS)
    #Shared Library options ( Shown for Linux ) 
    #CCSHARED = -Kpic
    #BUILD = $(CC) -G 
    CCSHARED = -fpic
    BUILD = $(CC) -shared
    # Tcl installation 
    TCL_INCL = -I/opt/usr/include
    TCL_LIB= -L/opt/usr/lib
    # Tcl installation 
    TCL_INCLUDE= -I/opt/usr/include
    TCL_LIB= -L/opt/usr/lib

    # additional link libraries

    LIBS =  -lm

    .SUFFIXES: .c

    .c.o : 
            gcc   $(CFLAGS) $(INCLUDE) -c $<

    all: $(TARGET)
            echo "All done"

    # convert the SWIG wrapper file into an object file
    $(WRAPOBJ): $(WRAPFILE)
            gcc $(CCSHARED) $(CFLAGS) -c $(WRAPFILE) $(TCL_INCLUDE) $(INCLUDE) 

    # run SWIG
    $(WRAPFILE): $(INTERFACE)
            $(SWIG) $(SWIGOPT) -o $(WRAPFILE) $(INTERFACE)

    # Build the final extension module
    $(TARGET) : $(WRAPOBJ) $(ALLOBJS) myif.o
            $(BUILD) $(CCSHARED) $(WRAPOBJ) $(ALLOBJS) $(TCL_LIB) $(LIBS) myif.o /opt/usr/lib/libtcl8.4.so -o $(TARGET) 

    myapp : myapp.c myapp.h myif.o
            gcc $(CCSHARED) -I. $(TCL_INCL) $(TCL_LIB) $(LIBS) myif.o myapp.c /opt/usr/lib/libtcl8.4.so   -o myapp  

    clean: 
            rm -f $(WRAPFILE) $(TARGET) *.o myapp

myif.h

    #include <math.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/resource.h>


    union Data {
      double value;
      char strvalue[10];
    } ;
    struct Dingo {
      char name[30];
      char lname[40];
      char middlename [50];
      int selector;
      union Data data;
    };
    #ifndef MYIF_H 
    /* these are the C code structures that we are going to modify in the script
    extern struct Dingo * request;
    extern struct Dingo * Reply;
    struct Dingo * mk_Dingo(const char * name, const char * middlename,const char * lname, int selector,union Data * d);
    char *  puts_Dingo(struct Dingo * ptr);
    #endif

myif.i

    %module myif
    %{
    #include <math.h>
    #include <stdlib.h>
    #include "myif.h"
    %}
    %include "myif.h"

myapp.c

    #include <string.h>
    #include <tcl.h>
    #include "myif.h"

    int main(int argc, char** argv)
    {
            // Local variables
            Tcl_Interp * interp;
            union Data d;
            char * strvalue="This is a test";
            double value =0.0;
            char * name="Chuck";
            char * lname="Cheese";
            char * middlename="E.";
            char * retstr=NULL;
            int selector=1;
            memset(d.strvalue,0,10);
            strncpy(d.strvalue,strvalue,9);
            request=mk_Dingo(name,middlename,lname,selector,&d);
            retstr=puts_Dingo(request);
            printf("CCODE:request before p.tcl: %s\n",retstr);
            free(retstr);
            Reply=mk_Dingo("","","",selector,&d);
            retstr=puts_Dingo(Reply);
            printf("CCODE:Reply before p.tcl%s\n",retstr);
            free(retstr);
            Tcl_FindExecutable(argv[0]);
            interp=Tcl_CreateInterp();
            if ( interp == NULL ) {
              printf("Unable to create Interpreter");
              exit(127);
            }
            switch(Tcl_EvalFile(interp,"p.tcl")) {
            case TCL_OK: 
              printf ("CCODE:p.tcl completed successfully\n");
              break;
            case TCL_ERROR:
              printf("CCODE:rocess.tcl returned with an error: TCL_ERROR\n");
              break;
            case TCL_RETURN:
              printf("CCODE:p.tcl returned with an error: TCL_RETURN\n");
              break;
            case TCL_BREAK:
              printf("CCODE:p.tcl returned with an error: TCL_BREAK\n");
              break;
            case TCL_CONTINUE:
              printf("CCODE:p.tcl returned with an error: TCL_CONTINUE\n");
              break;
            default: 
              printf("CCODE:p.tcl returned with unknown error code:?\n");
              break;
            }
            printf("%s\n",Tcl_GetStringResult(interp));
            retstr=puts_Dingo(Reply);
            printf("CCODE:Reply after p.tcl: %s\n",retstr);
            free(retstr);
            retstr=puts_Dingo(request);
            printf("CCODE:request after p.tcl: %s\n",retstr);
            free(retstr);
            Tcl_DeleteInterp(interp);
            exit(0);
    }

p.tcl

    global tcl_library 
    if { ![ info exists tcl_library ] } {
        puts stdout "TCLCODE:setting tcl_library"
        set tcl_library /opt/usr/lib/tcl8.4

    }
    catch { source [file join $tcl_library init.tcl ]  }  
    lappend auto_path [file dirname $tcl_library ]  [pwd]
    load [file normalize [ file join . myif.so ] ]
    # call swig extension methods does not affect C structures but shows how to use swig
    # generated methods in tcl script
    Dingo test; # create a Dingo struct called test 
    test configure -name Charlie -lname Cheezit -middlename G. -selector 1 ; # set some data
    [test cget -data ] configure -strvalue "Rain" 
    # access req structure from C code
    puts stdout "TCLCODE:test name:[test cget -name  ] \
    mname:[test cget -middlename ] \
    lname:[test cget -lname  ] "
    puts stdout "TCLCODE:test selector:[test cget -selector ] \
    strvalue:[[test cget -data ] cget -strvalue] "
    puts stdout "TCLCODE:Accessing C code filled out structure request: \
    N:[Dingo_name_get $request ] \
    M:[Dingo_middlename_get $request ] \
    L:[Dingo_lname_get $request ] " 
    puts stdout "TCLCODE:Global C variable Reply is a swig pointer Reply= $Reply"
    puts stdout "TCLCODE:Setting Reply: name:[id host ] \
    middlename: [ Dingo_name_get $request ] \
    lname: [Dingo_lname_get $request ]" 
    Dingo_name_set $Reply [id host ]
    Dingo_middlename_set $Reply [id user ]
    Dingo_lname_set $Reply [Dingo_lname_get $request ] 
    puts stdout "TCLCODE:request: [puts_Dingo $request ]"
    puts stdout "TCLCODE:Reply : [puts_Dingo $Reply ]"
    close $fd

Building

Cut and paste all code with the filenames given then run make. You should have also installed swig and updated the Makefile paths to match your system.

    make 
    make myapp

Run

    myapp

Output

    CCODE:request before p.tcl: N:Chuck M:E. L:Cheese S:This is a
    CCODE:Reply before p.tclN: M: L: S:This is a
    TCLCODE:setting tcl_library
    TCLCODE:test name:Charlie  mname:G.  lname:Cheezit
    TCLCODE:test selector:1  strvalue:Rain
    TCLCODE:Accessing C code filled out structure request:  N:Chuck  M:E.  L:Cheese
    TCLCODE:Global C variable Reply is a swig pointer Reply= _c060d608_p_Dingo
    TCLCODE:Setting Reply: name:localhost.localdomain  middlename: Chuck  lname: Cheese
    TCLCODE:request: N:Chuck M:E. L:Cheese S:This is a
    TCLCODE:Reply : N:localhost.localdomain M:carl L:Cheese S:This is a
    CCODE:p.tcl completed successfully

    CCODE:Reply after p.tcl: N:localhost.localdomain M:carl L:Cheese S:This is a
    CCODE:request after p.tcl: N:Chuck M:E. L:Cheese S:This is a