Version 5 of automatically generating socket based Tcl / C connection code, 1

Updated 2004-03-07 14:43:29

started by Theo Verelst

To make use a pipe or as here a general socket [L1 ] [L2 ] to connect a user interface with a progam or to link programs together is a long used and tried solution since at least X windows. Not that that always worked perfectly, but its pretty ok.

Also web servers/browsers, ftp, etc are examples of existing progams making use of socket IPC. The principle is clear enough: one makes a stream connection, and sends messages across with commands and data [L3 ].

In practice, this doesn't work out all too easy, usually. This page presents an example to do most work completely automatically to make a tcl program connect with a C program, and have the C program execute functions under control of the tcl program (alternatively: C compiled image processing on an interactive Bwise canvas).

Also, an example is easily created, where Tk is used to have a stack of buttons for testing this.

On this page I used a recent windows XP running KDE (on an X simulator), and my own compiled tcl/tk 8.4 (but like on linux) in kde windows. Also, I used the cygwin unix-like environment including gcc compiler for windows. Most or all of the materials presented here should run equally well on linux/unix, and probably other os-es, provided they have a C compiler with unix flavour sockets.

Major issues when making a socket link and programming code around it are:

  • newlines (one or two characters, newline or carriage return)
  • end of line (don't forhet '\0' to terminate C strings)
  • end of message (how do you know the whole message is in)
  • flow control (no dead/live lock, buffer sizes)
  • process issues (creation, referencing, security, joint load)
  • connection control (setup, re-setup, connection with protocol, leakless cleanup, eof issues)
  • error correction/sensitivity (incomplete messages,resyncing)

The approach taken for this test version consists of the following steps:

  1. define a list of message names, which are also used as corresponding C function names
  2. make the socket connection possible both on C and Tcl side
  3. generate automatically the C message handler and the frame of the C functions
  4. generate Tcl/Tk code to make a button for each message
  5. save and compile the generated C file
  6. link it with the socket code
  7. run the resulting C program
  8. connect the tcl program
  9. test the buttons to see if the corresponding C functions get called.

 # NOTE this proc is also defined in BWise

 proc open_text { {n  {}}  } {
   global textname
   if {[winfo exists .tt] == 0} {
      toplevel .tt
      set textname $n
      text .tt.t -width 40 -height 8
      frame .tt.f
      entry .tt.f.e -textvar textname -width 30
      button .tt.f.s -text Save -command {
         global textname;  set f [open $textname w];
         puts -nonewline $f [.tt.t get 0.0 end]; close $f 
      }
      button .tt.f.l -text Load -command {
         global textname; .tt.t del 0.0 end; 
         set f [open $textname r]; 
         while {[eof $f] == 0} {
            .tt.t insert end "[gets $f]\n"
         }; close $f 
      }
      bind .tt.f.e <Double-Button> {
         set textname [tk_getOpenFile]
      }
      pack .tt.t -expand y -fill both
      pack .tt.f -side bottom -expand n -fill x
      pack .tt.f.e -side left -expand y -fill x
      pack .tt.f.s -side right
      pack .tt.f.l -side right
   } {
      set textname $n
   }
   if {$textname != {}} {
      .tt.f.l invoke
   }
 }


 proc soccdecodefunc { } {
   global socfs

   .tt.t insert 0.0 {#include<string.h>}
   .tt.t insert 0.0 "\n#include<stdio.h>\n"
   # What follows is not an error, it is pre-fab C-code
   .tt.t insert end {

 int socdecode(m)
 char m[];
 }
   .tt.t insert end \{\n

   set j 0
   foreach i $socfs {
      .tt.t insert end "   if \(strcmp\(m,\"$i\"\) == 0) \{$i\(\); return\($j\);\}\n"
      incr j;
   }

   .tt.t insert end "\}\n\n"
 } 

 proc socconnect { {port {4100}} } {
   global socsockid

   if ![info exists socsockid] { set socsockid stdout}
   if {$socsockid == "stdout"} {
      catch {set socsockid [socket localhost $port]}
      if {$socsockid == "stdout"} {puts "connect failed\n"; set socsockid stdout; return} ;
      fileevent $socsockid readable {
          if [eof $socsockid] {
             close $socsockid;
             set socsockid stdout
          } {
             # print incoming lines on stdout
             puts "[gets $socsockid]"
          }
      }
   } { puts "soc: connect tried while allready connected, ignored\n" }
 } 


 proc socfuncframe { {n} } {
 # make a simple C function body for message n in the tt.t text widget
   global socfs
   .tt.t insert end "$n\(\)\n{\n   printf(\"called:$n\\n\");\n}\n\n"

   lappend socfs $n
 } 

 proc socgencframe { {messagenames {message1 message2 message3}} } {

 # generate C funtions frame in .tt.t text widget
 # and create window with buttons for each message

   global socfs
   .tt.t del 0.0 end ; 
   set socfs $messagenames
   soccdecodefunc

   catch {unset socfs};
   foreach m $messagenames {socfuncframe $m} ;
   socgenui
 } 

 proc socgenui { } {
   global socfs

   catch {toplevel .socbuts}
   foreach i [winfo children .socbuts] {destroy $i}
   foreach i $socfs {
      pack [button .socbuts.$i -text $i -command "socsend $i"] -side top -fill x
   }
 } 

 proc socsend { {m} } {
   global socsockid

   if [eof $socsockid] { close $socsockid; set socsockid stdout}
   puts $socsockid $m
   flush $socsockid
 } 

 /* serv2.c */
 /* server exa */

 #include "cygwin/socket.h"
 #include "cygwin/in.h"

                       /* this is non-checked for now */
 #define INMAX 8*1024
 #define SERV_TCP_PORT 4100

 int sockfd,newsockfd,clilen;
 struct sockaddr_in cli_addr, serv_addr;
 char inlin[INMAX];
 struct timeval timeout;
 fd_set fdvar;

 int do_read()
 {
   int rr, n;
   n = INMAX;

   FD_ZERO(&fdvar);
   FD_SET(newsockfd, &fdvar);

   timeout.tv_sec = 0;
   timeout.tv_usec = 0;

   if (select(newsockfd+1,&fdvar,(fd_set *) 0, 
      (fd_set *) 0, (struct timeval *) 0) <= 0) return(0);
   if (FD_ISSET(newsockfd,&fdvar)  == 0) 
      { return(0); }
   rr = read(newsockfd,inlin,n);
   if (rr <= 0) {
      rr = 0;
   }
   inlin[rr] = '\0';
   return(rr);
 }

 int do_write(b,n)
 char b[];
 int n;
 {
   int rr;
   rr = write(newsockfd,b,n);
   return(rr);
 }

 serv_main()
 {
   if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) <0 ) {
      printf("Error: can't open socket.\n");
      exit(-1);
   }

   bzero( (char *) &serv_addr, sizeof(serv_addr) );
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   serv_addr.sin_port = htons(SERV_TCP_PORT);

   if (bind(sockfd, (struct sockaddr *) &serv_addr, 
    sizeof(serv_addr)) < 0) {
      printf("Error: cannot bind socket.\n");
      exit(-1);
   }

   printf("waiting for connection ...\n");
   listen(sockfd,5);

      clilen = sizeof(cli_addr);
      newsockfd = accept(sockfd, 
         (struct sockaddr *) &cli_addr, &clilen);
      if (newsockfd < 0) {
         printf("Error opening new socket.\n");
         exit(-1);
      }
   FD_ZERO(&fdvar);
   FD_SET(newsockfd, &fdvar);
   timeout.tv_sec = 0;
   timeout.tv_usec = 0;

   inlin[0] = '\0';
 }

 /* stub.c */ 

 #include<stdio.h>

 extern int serv_main();
 extern int do_read();
 extern int do_write();

 extern char inlin[];

 extern socdecode(char *m);

 main()
 {
   int l;
   serv_main();

   while (1) {
      if ((l = do_read()) >0) {
         printf("C received string:%s",inlin);
         inlin[l-2] = '\0';
         socdecode(inlin);
         do_write(inlin,l-2);
         do_write("\n",1);
      }
   }
 }

 set l {}; for {set i 0} {$i < 10} {incr i} {lappend l "message$i"}
 socgencframe $l
 socconnect


 gcc -c serv2.c
 gcc -c stub.c
 gcc -c test.c

 gcc -o stub.exe stub.o serv2.o test.o

 ./stub

http://82.168.209.239/Wiki/cframecon.jpg

http://82.168.209.239/Wiki/cframebut.jpg

http://82.168.209.239/Wiki/cframett.jpg

http://82.168.209.239/Wiki/cframecon.jpg


Motivations and subsequent work include: