[plt-scheme] Manipulating Scheme vectors with FFI

From: Eli Barzilay (eli at barzilay.org)
Date: Tue Oct 28 17:15:10 EDT 2008

On Oct 28, Benjamin Seppke wrote:
> 
> So my question is: How can I manipulate Scheme vectors inside a c-
> function and see the results of that in Scheme..?

Generally speaking, you don't want to manipulate *Scheme* vectors --
they hold data that your C library will not know how to handle.  The
right solution is to translate a Scheme vector to a C vector and
back.  Here's a few examples:

The C "library" I have is this:

  int foo(double arr[], int length) {
    int i;
    for (i=0; i<length; i++) arr[i] *= 2.0;
    return length;
  }

A simple interface, similar to yours:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun (_vector i _double) _int -> _int)))
  (foo #(1.0 2.0 3) 3)

The problem here is that you never see the result (but the return
value is fine).  This is the semantics of `(_vector i <type>)' -- the
value is an input to the foreign function, and nothing more.  First of
all, I'll make it easier: the `_double*' type is similar to `_double'
except that it will coerce integers too:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun (_vector i _double*) _int -> _int)))
  (foo #(1 2 3) 3)

The next thing to do is to use a "custom type" to specify the second
input, which will result in a simpler interface at the scheme side.
We want to send the length of the vector, but this requires having a
name for the vector so we can refer to it -- [name : type] is used to
get such a name for an argument, and [type = expr] is used to specify
the value (so it's gone from the interface of the resulting function):

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun [v : (_vector i _double*)]
                       [_int = (vector-length v)]
                       -> _int)))
  (foo #(1 2 3))

Now we get to your problem.  The thing is that we want to specify a
`_vector' type in `io' mode, so it will be translated on its way to C
and back -- but that requires specifying the length of the returned
vector.  To get this following how you started the code, you specify
the arguments explicitly:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun (v) ::
                       [p : (_vector io _double* (vector-length v)) = v]
                       [_int = (vector-length v)]
                       -> _int)))
  (foo #(1 2 3))

The problem, as you've noticed, is that you never get to hold the
return value.  You can do that by specifying a second `->' and the
result expression, for example:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun (v) ::
                       [p : (_vector io _double* (vector-length v)) = v]
                       [_int = (vector-length v)]
                       -> _int
                       -> p)))
  (foo #(1 2 3))

and you can also name the return value, and use it in the "real
result" expression:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun (v) ::
                       [p : (_vector io _double* (vector-length v)) = v]
                       [_int = (vector-length v)]
                       -> [r : _int]
                       -> (if (zero? r) (error "boom") p))))
  (foo #(1 2 3))

(I made it throw an error if r=0, since my "library" returns 3...)

But it's probably better to just use a cvector, which corresponds to a
C vector, and since it is mutable, you can just reference the results
with `cvector-ref':

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun [v : _cvector]
                       [_int = (cvector-length v)]
                       -> [r : _int]
                       -> (if (zero? r) (error "boom") v))))
  (define v (list->cvector '(1 2 3) _double*))
  (cvector->list (foo v))

Here I actually used `cvector->list' to see the whole list.  In this
case there is really no need to return the vector, you can just use
the actual value which is now different:

  (define foo
    (get-ffi-obj 'foo "xx.so"
                 (_fun [v : _cvector]
                       [_int = (cvector-length v)]
                       -> [r : _int]
                       -> (when (zero? r) (error "boom")))))
  (define v (list->cvector '(1 2 3) _double*))
  (foo v)
  (cvector->list v)

but the previous setup is probably better.

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                  http://www.barzilay.org/                 Maze is Life!


Posted on the users mailing list.