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.
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.