started by Theo Verelst To make use a [pipe] or as here a general [socket] [http://mini.net/tcl/10283] [http://mini.net/tcl/9844] 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 [http://mini.net/tcl/9102]. 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 { 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} .tt.t insert 0.0 "\n#include\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 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: * real time sound synthesis/analysis C code I have can be runtime tcl/tk controlled [http://members.tripod.com/%7Etheover/softsyn.html] [http://82.168.209.239/fosdem/] [http://82.168.209.239/Articles/pms.html] * idem for 3D graphics [http://mini.net/tcl/10449] * webserver extensions in c or tcl [Web pages with images] * BWise-ification of the various blocks here [http://mini.net/tcl/10291] * distributed applications (protocol based [http://mini.net/tcl/8733] [http://mini.net/tcl/8791]) [http://mini.net/tcl/11013] [distributed linked bwise] [http://mini.net/tcl/10135] ----