Bezoar 2007-5-22 : yeah just noticed theres no code for the two functions puts_Dingo and mk_Dingo made some changes other places as well. Dunno how it worked like it was. Works now. Thanks for heads up
XO 2006-11-30: Something must be left out accidentally while trimming the fat, I couldn't get this great example up and running. ^o^
Communicating between C and embedded Tcl InterpreterIn 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 JavaScript program that 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 solution 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 usual install paths. If anyone can come up with a better way please feel free to correct the code.[Bezoar ] (Corrected see comment in script).
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 = -g INCLUDE = #SWIG OPTIONS SWIG = /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/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 /usr/lib/libtcl8.4.so -o $(TARGET) myapp : myapp.c myif.o gcc -g $(CCSHARED) -I. $(TCL_INCL) $(TCL_LIB) $(LIBS) myif.o myapp.c /usr/lib/libtcl8.4.so -o myapp clean: rm -f $(WRAPFILE) $(TARGET) *.o myapp
myif.c
#define MYIF_H #include "myif.h" struct Dingo * request; struct Dingo * Reply; struct Dingo * mk_Dingo(const char * name, const char* middlename, const char * lname, int selector, union Data* d ) { struct Dingo * retval = malloc( sizeof(struct Dingo)); memset(retval->name,0,30); strncpy(retval->name,name,29); memset(retval->lname,0,40); strncpy(retval->lname,lname,39); memset(retval->middlename,0,50); strncpy(retval->middlename,middlename,49); retval->selector = selector; memset(&retval->data,0, sizeof(union Data)); memcpy(& retval->data, d, sizeof(union Data)); return retval; } char * puts_Dingo(struct Dingo * ptr) { char buffer[1000]; memset(buffer,0,1000); if ( ptr->selector == 0 ) { sprintf(buffer,"N:%s M:%s L:%s S:%s",ptr->name,ptr->middlename,ptr->lname,ptr->data.strvalue); } else { sprintf(buffer,"N:%s M:%s L:%s S:%d",ptr->name,ptr->middlename,ptr->lname,ptr->data.value); } char * retval = malloc( strlen(buffer)+ 1 ); memset(retval,0, strlen(buffer) + 1 ); strncpy(retval,buffer,strlen(buffer)); return retval; }
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); } Tcl_Init(interp); 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
### THIS section not needed anymore because of addtion of Tcl_Init(interp); in myapp.c #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 swig generated extension 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:Wally\ middlename: [ Dingo_name_get $request ] \ lname: [Dingo_lname_get $request ]" Dingo_name_set $Reply "Wally" Dingo_middlename_set $Reply [ Dingo_name_get $request ] Dingo_lname_set $Reply [Dingo_lname_get $request ] puts stdout "TCLCODE:request: [puts_Dingo $request ]" puts stdout "TCLCODE:Reply : [puts_Dingo $Reply ]" close $fdBuildingCut 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 myappRun
myappOutput
./myapp CCODE:request before p.tcl: N:Chuck M:E. L:Cheese S:1936287828 CCODE:Reply before p.tclN: M: L: S:1936287828 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= _c0c03109_p_Dingo TCLCODE:Setting Reply: name:Wally middlename: Chuck lname: Cheese TCLCODE:request: N:Chuck M:E. L:Cheese S:1936287828 TCLCODE:Reply : N:Wally M:Chuck L:Cheese S:1936287828 CCODE:p.tcl completed successfully CCODE:Reply after p.tcl: N:Wally M:Chuck L:Cheese S:1936287828 CCODE:request after p.tcl: N:Chuck M:E. L:Cheese S:1936287828