Thursday, November 27, 2008

A Small Experiment With MzScheme's Foreign Function Interface

One of the projects I've been considering is integrating MzScheme in with another GUI Toolkit besides MrEd. Briefly, I'm interested in doing this because MrEd has some limitations that are apparent when you want to build sophisticated GUIs.

The first step to doing this integration with a new toolkit is understanding how the C foreign function interface works with MzScheme. This is also the first step to writing a MySQL interaface, or working with pretty much any other library out there. So, I figured understanding the foreign function would be valuable no matter what. Here's some sample code I worked up to figure this all out - the integration in this case is with the Tcl interpreter.

#lang scheme
;; Import the FFI libraries
(require scheme/foreign
         scheme/runtime-path
         srfi/26)
(unsafe!)
(provide tcl-eval)

;; Load the ActiveTCL DLL library. You'll have to change this to
;; where the library is installed
(define-runtime-path tcl-dll "c:/tools/tcl/bin/tcl85.dll")
(define tcl-lib (ffi-lib tcl-dll))

;; Lookup C functions by name and signature and bind them to scheme functions.
;; This is the *magic* of Scheme FFI, and cool magic it is.
;; I figured out these signatures by looking at the TCL docs.
(define tcl-lib-create-interp 
         (get-ffi-obj "Tcl_CreateInterp" tcl-lib (_fun -> _pointer)))
(define tcl-lib-eval 
         (get-ffi-obj "Tcl_Eval" tcl-lib (_fun _pointer _string -> _int)))
(define tcl-lib-get-result  
         (get-ffi-obj "Tcl_GetStringResult" tcl-lib (_fun _pointer -> _string)))


;; A scheme wrapper for the tcl library calls. TCL requires that we create
;; a new interpreter, eval a script and then call a function to get the result.
;; We do all this. Again. this information was extracted from the Tcl docs
(define (tcl-eval . stmts)
  (let* ([script (apply string-append (map (cut string-append <> "\n") stmts))]
         [interp (tcl-lib-create-interp)])
    (tcl-lib-eval interp script)
    (tcl-lib-get-result interp)))

And here's an example of how the exported tcl-eval function works:

(tcl-eval "set phrase Hello-"
          "for {set i 1} {$i<=10} {incr i} {"
          "  append phrase , $i"
          "}"
          "append phrase -World"
          "return $phrase")

Overall, I'm quite impressed with how easy it is to pull C functions into the Scheme world. What seemed complicated from reading the docs turned out to be straighforward in code.

Incidentally, as I was about to write up this blog entry, I found collects/ffi/tcl.ss. Which, as you might guess, is a real implementation of what a foreign interface with Tcl should look like. In the end, I found it useful to look at my crude example, and then look at tcl.ss to see how it could be improved.

The next step is to look into binding MzScheme to the Tk side of Tcl/Tk. It seems like an ambitious, but doable project.

2 comments:

  1. You might be interested in PS/Tk, an embedding of Tk in Scheme that is portable to most Scheme implementations, including PLT. Get it at http://sourceforge.net/projects/pstk.

    ReplyDelete
  2. pbewig -

    That's awesome, Thanks for sharing! I can't tell you how much time you might have just saved me.

    -Ben

    ReplyDelete