[racket] FFI

From: Hamish Ivey-Law (hamish.ivey.law at gmail.com)
Date: Tue May 13 16:33:31 EDT 2014

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.

Posted on the users mailing list.