Previous: , Up: Foreign Function Interface   [Contents][Index]


6.22.6 Dynamic FFI

Of course, the land of C is not all nouns and no verbs: there are functions too, and Guile allows you to call them.

Scheme Procedure: pointer->procedure return_type func_ptr arg_types
C Procedure: scm_pointer_to_procedure (return_type, func_ptr, arg_types)

Make a foreign function.

Given the foreign void pointer func_ptr, its argument and return types arg_types and return_type, return a procedure that will pass arguments to the foreign function and return appropriate values.

arg_types should be a list of foreign types. return_type should be a foreign type. See Foreign Types, for more information on foreign types.

Here is a better definition of (math bessel):

(define-module (math bessel)
  #:use-module (system foreign)
  #:export (j0))

(define libm (dynamic-link "libm"))

(define j0
  (pointer->procedure double
                      (dynamic-func "j0" libm)
                      (list double)))

That’s it! No C at all.

Numeric arguments and return values from foreign functions are represented as Scheme values. For example, j0 in the above example takes a Scheme number as its argument, and returns a Scheme number.

Pointers may be passed to and returned from foreign functions as well. In that case the type of the argument or return value should be the symbol *, indicating a pointer. For example, the following code makes memcpy available to Scheme:

(define memcpy
  (let ((this (dynamic-link)))
    (pointer->procedure '*
                        (dynamic-func "memcpy" this)
                        (list '* '* size_t))))

To invoke memcpy, one must pass it foreign pointers:

(use-modules (rnrs bytevectors))

(define src-bits
  (u8-list->bytevector '(0 1 2 3 4 5 6 7)))
(define src
  (bytevector->pointer src-bits))
(define dest
  (bytevector->pointer (make-bytevector 16 0)))

(memcpy dest src (bytevector-length src-bits))

(bytevector->u8-list (pointer->bytevector dest 16))
⇒ (0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0)

One may also pass structs as values, passing structs as foreign pointers. See Foreign Structs, for more information on how to express struct types and struct values.

“Out” arguments are passed as foreign pointers. The memory pointed to by the foreign pointer is mutated in place.

;; struct timeval {
;;      time_t      tv_sec;     /* seconds */
;;      suseconds_t tv_usec;    /* microseconds */
;; };
;; assuming fields are of type "long"

(define gettimeofday
  (let ((f (pointer->procedure
            int
            (dynamic-func "gettimeofday" (dynamic-link))
            (list '* '*)))
        (tv-type (list long long)))
    (lambda ()
      (let* ((timeval (make-c-struct tv-type (list 0 0)))
             (ret (f timeval %null-pointer)))
        (if (zero? ret)
            (apply values (parse-c-struct timeval tv-type))
            (error "gettimeofday returned an error" ret))))))

(gettimeofday)    
⇒ 1270587589
⇒ 499553

As you can see, this interface to foreign functions is at a very low, somewhat dangerous level18.

The FFI can also work in the opposite direction: making Scheme procedures callable from C. This makes it possible to use Scheme procedures as “callbacks” expected by C function.

Scheme Procedure: procedure->pointer return-type proc arg-types
C Function: scm_procedure_to_pointer (return_type, proc, arg_types)

Return a pointer to a C function of type return-type taking arguments of types arg-types (a list) and behaving as a proxy to procedure proc. Thus proc’s arity, supported argument types, and return type should match return-type and arg-types.

As an example, here’s how the C library’s qsort array sorting function can be made accessible to Scheme (see qsort in The GNU C Library Reference Manual):

(define qsort!
  (let ((qsort (pointer->procedure void
                                   (dynamic-func "qsort"
                                                 (dynamic-link))
                                   (list '* size_t size_t '*))))
    (lambda (bv compare)
      ;; Sort bytevector BV in-place according to comparison
      ;; procedure COMPARE.
      (let ((ptr (procedure->pointer int
                                     (lambda (x y)
                                       ;; X and Y are pointers so,
                                       ;; for convenience, dereference
                                       ;; them before calling COMPARE.
                                       (compare (dereference-uint8* x)
                                                (dereference-uint8* y)))
                                     (list '* '*))))
        (qsort (bytevector->pointer bv)
               (bytevector-length bv) 1 ;; we're sorting bytes
               ptr)))))

(define (dereference-uint8* ptr)
  ;; Helper function: dereference the byte pointed to by PTR.
  (let ((b (pointer->bytevector ptr 1)))
    (bytevector-u8-ref b 0)))

(define bv
  ;; An unsorted array of bytes.
  (u8-list->bytevector '(7 1 127 3 5 4 77 2 9 0)))

;; Sort BV.
(qsort! bv (lambda (x y) (- x y)))

;; Let's see what the sorted array looks like:
(bytevector->u8-list bv)
⇒ (0 1 2 3 4 5 7 9 77 127)

And voilà!

Note that procedure->pointer is not supported (and not defined) on a few exotic architectures. Thus, user code may need to check (defined? 'procedure->pointer). Nevertheless, it is available on many architectures, including (as of libffi 3.0.9) x86, ia64, SPARC, PowerPC, ARM, and MIPS, to name a few.


Footnotes

(18)

A contribution to Guile in the form of a high-level FFI would be most welcome.


Previous: , Up: Foreign Function Interface   [Contents][Index]