[racket] FFI

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Tue May 13 17:17:30 EDT 2014

In `scm-to-blah`, you're trying to use `mkblah` to produce a C-level
blah, because that's what `define-cpointer` needs. But you've declared
`mkblah` to return a Racket-level `_blah` the same as `doublah`. It
doesn't work to `mkblah` and `_blah` in terms of each other.

I think you should think of `mkblah` as a function to generate a
C-level thing, so you can use it to implement `scm-to-blah`:

 (define-blah mkblah (_fun _long -> _pointer))

 (define (scm-to-blah x)
   ....
   (cond ....
         [(fixnum? x)
          (printf "  x is a fixnum~%")
          (define p (mkblah x))
          (cpointer-push-tag! p 'blah)
          p]
         ....))

If you really want `mkblah` to produce a Racket-level `_blah`, then
define a separate "raw" view off mkblah() for the implementation of
`scm-to-blah`:

  (define-blah mkblah/raw (_fun _long -> _pointer)
    #:c-id mkblah)

  (define (scm-to-blah x)
    ....
    (cond [(fixnum? x)
           (printf "  x is a fixnum~%")
           (define p (mkblah/raw x))
           (cpointer-push-tag! p 'blah)
           p]
          ....))

   ....
   (define-blah mkblah (_fun _long -> _blah))

In this latter case, `mkblah` depends on `_blah`, `_blah` depends on
`scm-to-blah`, `scm-to-blah` depends on `mkblah/raw`, and `mkblah/raw`
depends only on primitives.

At Tue, 13 May 2014 22:33:31 +0200, Hamish Ivey-Law wrote:
> 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.
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users

Posted on the users mailing list.