[racket] FFI
Dear Racket users,
I am writing an FFI binding for the C library we develop at work as a
"my first Racket project". I have encountered a problem that I
haven't been able to resolve regarding the functions SCHEME-TO-C and
C-TO-SCHEME which are parameters to DEFINE-CPOINTER-TYPE. The
simplified version is as follows. Consider the following C library:
---START "blah.c"---
#include <stdio.h>
#include <stdlib.h>
typedef long *blah;
/* Make a new blah */
blah mkblah(long x) {
blah z = malloc(sizeof(long));
printf(" In mkblah(x = %ld); z = %p\n", x, z);
*z = x;
return z;
}
/* Return double the argument */
blah doublah(blah x) {
printf(" In doublah(x = %p); *x = %ld\n", x, *x);
return mkblah(*x * 2);
}
---END---
Compile with
$ gcc -shared -fPIC -o blah.so blah.c
Then my FFI bindings are as follows:
---START "blah.rkt"---
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define)
(define *path* "./blah.so")
(define libblah (ffi-lib *path*))
(define-ffi-definer define-blah libblah)
(define (scm-to-blah x)
(printf "SCM --> BLAH with x = ~a" x)
(cond [(blah? x)
(printf " (x is already a blah)~%")
x]
[(fixnum? x)
(printf " (x is a fixnum)~%")
(mkblah x)]
[else (error x "has unexpected type")]))
(define (blah-to-scm x)
(printf "BLAH --> SCM with x = ~a~%" x)
x)
(define-cpointer-type _blah #f scm-to-blah blah-to-scm)
(define-blah mkblah (_fun _long -> _blah))
(define-blah doublah (_fun _blah -> _blah))
---END---
Running this gives:
Welcome to Racket v6.0.
-> ,en "blah.rkt"
"blah.rkt"> (doublah 234)
SCM --> BLAH with x = 234 (x is a fixnum)
In mkblah(x = 234); z = 0x843310
BLAH --> SCM with x = #<cpointer:blah>
In doublah(x = 0x843310); *x = 234
In mkblah(x = 468); z = 0x830020
BLAH --> SCM with x = #<cpointer:blah>
#<cpointer:blah>
This is *almost* what I would expect. We see that 234 is converted
from a Scheme fixnum to a blah via mkblah, then back to a
#<cpointer:blah>. But SCM-TO-BLAH is not called again before DOUBLAH
is called. I would have expected one of the following two outputs:
"blah.rkt"> (doublah 234)
SCM --> BLAH with x = 234 (x is a fixnum)
In mkblah(x = 234); z = 0x843310
BLAH --> SCM with x = #<cpointer:blah>
SCM --> BLAH with x = #<cpointer:blah> (x is already a blah)
In doublah(x = 0x843310); *x = 234
In mkblah(x = 468); z = 0x830020
BLAH --> SCM with x = #<cpointer:blah>
#<cpointer:blah>
(note the extra SCM --> BLAH line before the call to DOUBLAH) or
"blah.rkt"> (doublah 234)
SCM --> BLAH with x = 234 (x is a fixnum)
In mkblah(x = 234); z = 0x843310
In doublah(x = 0x843310); *x = 234
In mkblah(x = 468); z = 0x830020
BLAH --> SCM with x = #<cpointer:blah>
#<cpointer:blah>
where the result of SCM-TO-BLAH is passed directly to DOUBLAH.
Here is why that's a problem. What I really want to do is provide a
printer extension for blah values, and it seems to me that the only
way to do that is to wrap the blah type in a struct and supply the
appropriate method with #:methods gen:custom-write. So I wrap blah in
a blah-hdl struct and adapt the surrounding code like so:
---START "blah2.rkt"---
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define)
(define *path* "./blah.so")
(define libblah (ffi-lib *path*))
(define-ffi-definer define-blah libblah)
(struct blah-hdl (ref))
;; #:methods gen:custom-write
;; [(define write-proc blah-print)])
(define (scm-to-blah x)
(printf "SCM --> BLAH with x = ~a~%" x)
(cond [(blah-hdl? x)
(printf " x is a blah-hdl~%")
(blah-hdl-ref x)]
[(fixnum? x)
(printf " x is a fixnum~%")
(mkblah x)]
[else (error x "has unexpected type")]))
(define (blah-to-scm x)
(printf "BLAH --> SCM with x = ~a~%" x)
(blah-hdl x))
(define-cpointer-type _blah #f scm-to-blah blah-to-scm)
(define-blah mkblah (_fun _long -> _blah))
(define-blah doublah (_fun _blah -> _blah))
---END---
This should wrap and unwrap the #<cpointer:blah> objects in blah-hdl
structs in the passage from Racket to C and back. But:
-> ,en "blah2.rkt"
"blah2.rkt"> (doublah 234)
SCM --> BLAH with x = 234
x is a fixnum
In mkblah(x = 234); z = 0x944140
BLAH --> SCM with x = #<cpointer:blah>
; blah->C: argument is not non-null `blah' pointer
; argument: #<blah-hdl>
; [,bt for context]
"blah2.rkt"> ,bt
; blah->C: argument is not non-null `blah' pointer
; argument: #<blah-hdl>
; context...:
; /usr/share/racket/pkgs/xrepl-lib/xrepl/xrepl.rkt:1346:0
; /usr/share/racket/collects/racket/private/misc.rkt:87:7
I do not understand this error and the error message (minus the
'blah's) doesn't get any hits on Google. It seems to result from the
fact that a blah-hdl is being given directly to doublah somehow. What
am I doing wrong?
Thanks in advance, and sorry for such a long first post!
Kind regards,
Hamish.