6.19.8 More Foreign Functions

It is possible to pass pointers to foreign functions, and to return them 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:

(use-modules (system foreign))
(define memcpy
  (foreign-library-function #f "memcpy"
                            #:return-type '*
                            #:arg-types (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 (foreign-library-function #f "gettimeofday"
                                     #:return-type int
                                     #:arg-types (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 level21.

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 (foreign-library-function
                #f "qsort" #:arg-types (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

(21)

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