This page describes how to use the CMUCL FFI to allow callbacks from
          foreign code into Lisp. This implementation only works when using the
          generational garbage collector (you should have :gencgc in your *features* list; this is the case on all x86
          platforms). It is also possible to make it work with the
          non-generational garbage collector; ask the cmucl-help mailing list.
          There is some glue code in Lisp to save into cl-tcl.lisp, some C glue
          code to save into tcl-callback.c, and some demo code at the end.
        
          The following code should be saved into a file
          cl-tcl.lisp:
        
;;; Evaluate (alien:load-foreign "/usr/lib/libtcl8.0.so") and
;;; (alien:load-foreign "tcl-demo.o") before compiling or loading
;;; this file.  You will also likely need a version of CMUCL where
;;; the /usr/bin/lisp binary has been produced with the flags
;;; -Xlinker --export-dynamic added to OS_LINK_FLAGS in Config.linux*
;;; Note that the Debian binaries usually don't include these flags.
(cl:defpackage "CL-TCL"
    (:use "CL" "ALIEN" "C-CALL" "SYSTEM")
  (:export
   "+TCL-OK+" "+TCL-ERROR+" "+TCL-RETURN+" "+TCL-BREAK+" "+TCL-CONTINUE+"
   "TCL-INTERPRETER"
   "CREATE-TCL-INTERPRETER" "DESTROY-TCL-INTERPRETER"
   "REGISTER-TCL-COMMAND" "UNREGISTER-TCL-COMMAND"
   "WITH-TCL-INTERPRETER" "EVAL-TCL-EXPR"))
(in-package :CL-TCL)
;;;; User and Implementation-level constants
(defconstant +TCL-OK+ 0
  "TCL return code:  Command completed normally; the interpreter's
result contains the command's result.")
(defconstant +TCL-ERROR+ 1
  "TCL return code:  The command couldn't be completed successfully;
the interpreter's result describes what went wrong.")
(defconstant +TCL-RETURN+ 2
  "TCL return code:  The command requests that the current procedure
return; the interpreter's result contains the procedure's return value.")
(defconstant +TCL-BREAK+ 3
  "TCL return code:  The command requests that the innermost loop
be exited; the interpreter's result is meaningless.")
(defconstant +TCL-CONTINUE+ 4
  "TCL return code:  Go on to the next iteration of the current loop;
the interpreter's result is meaningless.")
;;;; FFI bindings to the Tcl Library and the C-side of the callback stuff
(declaim (inline tcl-createinterp))
(def-alien-routine "Tcl_CreateInterp" system-area-pointer)
(declaim (inline tcl-eval))
(def-alien-routine "Tcl_Eval" int
  (interp system-area-pointer :in)
  (string c-string :in))
(declaim (inline tcl-getstringresult))
(def-alien-routine "Tcl_GetStringResult" c-string
  (interp system-area-pointer :in))
(declaim (inline tcl-deleteinterp))
(def-alien-routine "Tcl_DeleteInterp" void
  (interp system-area-pointer :in))
(declaim (inline set-lisp-callback-handler))
(def-alien-routine "Set_Lisp_Callback_Handler" void
  (handler unsigned-long :in))
(declaim (inline register-tcl-lisp-command))
(def-alien-routine "Register_Tcl_Lisp_Command" void
  (interp system-area-pointer :in)
  (name c-string :in))
(declaim (inline unregister-tcl-lisp-command))
(def-alien-routine "Unregister_Tcl_Lisp_Command" void
  (interp system-area-pointer :in)
  (name c-string :in))
;;;; User-level stuff
(defstruct (tcl-interpreter (:print-function print-tcl-interpreter))
  (valid-p t)
  (sap (int-sap 0) :type system-area-pointer)
  (commands (make-hash-table :test #'equal) :read-only t))
(defun print-tcl-interpreter (inter stream depth)
  (declare (ignore depth))
  (print-unreadable-object (inter stream :type t :identity t)
    (format stream "~:[(INVALID)~;(valid@~8,'0X)~]"
            (tcl-interpreter-valid-p inter)
            (sap-int (tcl-interpreter-sap inter)))))
(defvar *tcl-interpreter-list* nil
  "List of valid TCL interpreters.")
(defun create-tcl-interpreter ()
  "Create a TCL interpreter object that can be passed to the other
functions of CL-TCL."
  (let ((result (make-tcl-interpreter :sap (tcl-createinterp))))
    (push result *tcl-interpreter-list*)
    result))
(defun destroy-tcl-interpreter (inter)
  "Destroy the TCL interpreter object `inter'."
  (assert (and (tcl-interpreter-valid-p inter)
               (member inter *tcl-interpreter-list*)))
  (setf *tcl-interpreter-list*
        (delete (tcl-interpreter-sap inter) *tcl-interpreter-list*
                :key #'tcl-interpreter-sap :test #'sap=))
  (tcl-deleteinterp (tcl-interpreter-sap inter))
  (setf (tcl-interpreter-valid-p inter) nil
        (tcl-interpreter-sap inter) (int-sap 0))
  inter)
(defun register-tcl-command (inter name handler)
  "Registers the lisp function `handler' as the handler for the
command named by the string `name' in the TCL-Interpreter `inter'.
Any previously existing handlers will be superseded.  When the
corresponding command is invoked in the TCL interpreter, the handler
will be called with the TCL-Interpreter object, the command name and
all the arguments to the TCL command.  The handler must return two
values: The return code (one of +TCL-OK+, +TCL-ERROR+, +TCL-RETURN+,
+TCL-BREAK+ or +TCL-CONTINUE+) and the result or error indicator
string."
  (assert (tcl-interpreter-valid-p inter))
  (setf (gethash name (tcl-interpreter-commands inter)) handler)
  (register-tcl-lisp-command (tcl-interpreter-sap inter) name))
(defun unregister-tcl-command (inter name)
  "Unregisters the previously registered command named `name' in the
TCL-Interpreter `inter'.  Returns the old handler function."
  (assert (tcl-interpreter-valid-p inter))
  (let ((handler (gethash name (tcl-interpreter-commands inter) nil)))
    (assert handler)
    (remhash name (tcl-interpreter-commands inter))
    (unregister-tcl-lisp-command (tcl-interpreter-sap inter) name)
    handler))
(defmacro with-tcl-interpreter (inter (&rest commands) &body body)
  "Evaluate the body in an environment were `inter' is bound to a
Tcl interpreter instance and for each (name tcl-proc) in `commands'
a Tcl command for name is defined, which invokes tcl-proc."
  `(let ((,inter (create-tcl-interpreter)))
     (unwind-protect
          (progn
            ,@(loop for (name handler) in commands
                    collect
                    `(register-tcl-command ,inter ,name ,handler))
            ,@body)
       (destroy-tcl-interpreter ,inter))))
(defun eval-tcl-expr (inter expr)
  "Evaluate the expression (string) `expr' in the Tcl-Interpreter
`inter' and return as multiple values the return code and result
string."
  (assert (tcl-interpreter-valid-p inter))
  (let* ((result-code (tcl-eval (tcl-interpreter-sap inter) expr))
         (result-string (tcl-getstringresult (tcl-interpreter-sap inter))))
    (values result-code result-string)))
;;;; Lisp-side of the callback stuff.
(defun %lisp-callback-handler (inter-sap args)
  (handler-case
      (let ((inter (find inter-sap *tcl-interpreter-list*
                         :key #'tcl-interpreter-sap :test #'sap=)))
        (assert (and inter (tcl-interpreter-valid-p inter)))
        (let ((handler (gethash (car args)
                                (tcl-interpreter-commands inter) nil)))
          (assert handler)
          (multiple-value-bind (code result)
              (apply handler inter args)
            (cons code result))))
    (error (c) (cons +TCL-ERROR+ (format nil "Lisp error: ~A" c)))))
(defun %update-lisp-callback-handler ()
  (set-lisp-callback-handler
   (kernel:get-lisp-obj-address #'%lisp-callback-handler)))
(defvar *lisp-callback-scavhook*
  (ext:make-scavenger-hook :value #'%lisp-callback-handler
                           :function #'%update-lisp-callback-handler))
(%update-lisp-callback-handler)
        
          The following C code should be saved into a file
          tcl-callback.c.
        
#include <stdlib.h>
#include <string.h>
#include <tcl.h>
#include "lisp.h"
#include "internals.h"
#include "alloc.h"
#include "arch.h"
lispobj lisp_callback_handler;
static lispobj alloc_str_list(char *list[])
{
  lispobj result, newcons;
  struct cons *ptr;
  if (*list == NULL)
    result = NIL;
  else {
    result = newcons = alloc_cons(alloc_string(*list++), NIL);
    while (*list != NULL) {
      ptr = (struct cons *)PTR(newcons);
      newcons = alloc_cons(alloc_string(*list++), NIL);
      ptr->cdr = newcons;
    }
  }
  return result;
}
int LispTclProc(ClientData data,Tcl_Interp *interp,int argc,char** argv)
{
  lispobj lisp_result;
  struct vector *vec;
  char* result;
  int returnvalue;
  lisp_result=funcall2(lisp_callback_handler,alloc_sap(interp),
                       alloc_str_list(argv));
  returnvalue=fixnum_value(CONS(lisp_result)->car);
  vec=(struct vector*)PTR(CONS(lisp_result)->cdr);
  result=ckalloc((vec->length)+1);
  strcpy(result,(char*)vec->data);
  interp->result=result;
  interp->freeProc=TCL_DYNAMIC;
  return returnvalue;
}
void Set_Lisp_Callback_Handler(lispobj handler)
{
  lisp_callback_handler=handler;
}
void Register_Tcl_Lisp_Command(Tcl_Interp* interp,char* name)
{
  Tcl_CreateCommand(interp,name,LispTclProc,NULL,NULL);
}
void Unregister_Tcl_Lisp_Command(Tcl_Interp* interp,char* name)
{
  Tcl_DeleteCommand(interp,name);
}
        Here is some code you can use to load the demo into CMUCL:
(alien:load-foreign "/usr/lib/libtcl8.0.so")
(alien:load-foreign (make-pathname :name "tcl-callback" :type "o" :version nil
                                   :defaults *load-truename*))
(load (compile-file (make-pathname :name "cl-tcl" :type "cl" :version nil
                                   :defaults *load-truename*)))
        And finally, here is some sample code demonstrating the use of a tcl function which calls back into Lisp:
(cl:defpackage "CL-TCL-DEMO"
    (:use "CL" "CL-TCL")
  (:export "TCL-DEMO"))
(in-package :CL-TCL-DEMO)
(defun tcl-incr (inter cmd first &optional second)
  (declare (ignore inter cmd))
  (values 0
          (format nil "~D"
                  (+ (parse-integer first)
                     (if second (parse-integer second) 1)))))
(defun tcl-demo (n)
  (with-tcl-interpreter inter (("lincr" #'tcl-incr))
    (multiple-value-bind (code result)
        (eval-tcl-expr inter
                       (format nil "set result \"(\"
for {set i 1} {$i <= ~D} {set i [lincr $i 2]} {append result $i \" \"}
append result \")\"
return $result" n))
      (case code
        ((#.+TCL-OK+ #.+TCL-RETURN+) (read-from-string result))
        (t (values code result))))))
        
          This description is adapted from the article
          <87k8jzthta.fsf@orion.dent.isdn.cs.tu-berlin.de>
          posted to the USENET group comp.lang.lisp on 2000-02-21 by Pierre
          Mai.