[plt-scheme] FFI questions:

From: Eli Barzilay (eli at barzilay.org)
Date: Wed Nov 12 22:00:38 EST 2008

On Nov 12, Ernie Smith wrote:
> I feel as though I'm missing key pieces of information which would
> permit me finally to grasp the FFI documentation.
> 
> In an effort to discover the missing link I've come up with a few
> questions, I'd be grateful for answers.  More importantly, if in
> reading these questions it dawns on someone that I am missing a
> concept please feel free to slap me into consciousness with the
> necessary point.
> 
> 1- Given that you create a type using this:
> 
>    (make-ctype ctype ...)
> 
>    How do you create an initialized instance of that type?

Jay gave a good answer on this.  The idea is that the primitive ctypes
are ones that can translate Scheme values to C values -- there is no
way for you to directly see these C values in the Scheme world.  When
you create a new ctype with

  (make-ctype _foo s->c c->s)

the resulting type knows how to translate Scheme values to C by
applying the `s->c' function and then doing the conversion that `_foo'
is doing -- which itself can be a user-defined ctype -- and the result
of going through these chain of conversions is always some value at
the C side.  `c->s' is used when going the other way, from a C value
to a Scheme value.

But you can see these values indirectly, by using one of the
facilities that deal with C values in Scheme.  For example:

  > (define _singleton-int (make-ctype _int car list))
  > (define a (malloc _singleton-int))
  ;; note that `malloc' allocates space for the base C type here, an
  ;; _int in this case
  > (ptr-set! a _singleton-int (list 1))
  > (ptr-ref a _singleton-int)
  (1)

The `ptr-set!' and `ptr-ref' functions expect a type to be used, which
allows you to treat the value as a different type, for example:

  > (ptr-ref a _int)
  1
  > (ptr-ref a _byte)
  1  ; <- exposes the fact that my machine is little-endian

There is a `tag' field for pointers which is not used as much as it
should.  In the future I hope to make the above `malloc' save the
ctype on the cpointer value, and have it be used by default in
`ptr-set!', `ptr-ref' and others.  (But this is going to be a
substantial change that will require some careful design.)


>    Something along the lines of:
>    (define my-instance (somehow-make-one-with-this-value 99))
> 
>    I suppose that applying the (somehow ..) would cause the conversion
>    to C encoding from 99 to occur and the instance would contain the C
>    encoded value?

I hope that the above further clarifies the issue.  There are two
accessor functions that can give you the relevant conversion
functions, which you may want to use for debugging; they're not
exposed in the scheme/foreign interface because I didn't see any need
for them so far, and I think that they're not even useful for
debugging -- if you have some substantial conversion code, you'd
probably have a separate function that does it (and that you can
test).  Here's an example of using this:

  > (ctype-scheme->c _singleton-int)
  #<procedure:car>
  > ((ctype-scheme->c _singleton-int) (list 1))
  1

but if you try to do this over the primitive types you get:

  > (ctype-scheme->c _int)
  #f

because there is nothing that you can get here and actually use.
(This is related to the fact that the code that implements these
primitive conversions *must* be C code.)


> 2- Given that you create a struct type using this:
>    (make-cstruct-type ctypes)
> 
>    How do you create an initialized instance of that structure?

There are several ways, like `malloc', or like refering to a pointer
that holds an instance of that cstruc, but if you just want to create
a value, then you get the usual `make-foo' constructor.  For example:

  > (define-cstruct _foo ([x _int] [y _int]))
  > (make-foo 1 2)
  #<cpointer:foo>

The result is a Scheme pointer object that points at the malloced
block of memory holding your struct.  (`make-foo' is implemented using
`malloc'.)

>    Another way of posing this question would be:
>    What are the steps necessary to implement (_list-struct ) starting
>    from (make-cstruct-type )?

You can see the implementation of both in "collects/mzlib/foreign.ss".
The function that actually creates the libffi representation of a
struct is `make-cstruct-type', and both of the above use it.


> 3- Given that you create a pointer type using this:
>    (define-cpointer-type _name ...)
>    How do you instantiate a pointer of that type initialized
>    so that it points to a valid instance of whatever the pointer
>    is intended to point to.

cpointers have that tag field, and `define-cpointer-type' is the way
to create a type (based on _cpointer) that checks that the pointer is
valid using this tag value.


>    (for example how to instantiate a pointer such that it points to
>    a field of the correct type in an existing instance of a structure)

You can get an "offset pointer" with `ptr-add', but that's useful only
in rare situations.


> 4- There appears to be no way of declaring a vector type,

Right -- that's a limitation of libffi.  (If anyone knows of a way to
do it, then feel free to enlighten me...)


>    only a vector instance.
>    So when declaring a structure field which *is* a vector,
>    the declaration needs to be made in the form of a substructure
>    declaration with the correct number of repetitions of the single
>    type.  Then access to that vector in an instance of the containing
>    structure can be accomplished by applying
>    (make-cvector* ...) to what is returned by the
>    id-field-id accessor for that field.
>    Is that a correct and reasonable approach to dealing with this issue?

Yes, it's as good as anything...  It might be more convenient to use
`_list-struct', for such cases -- or perhaps build some abstraction on
top of that which is simpler for the N*type case.  (Actually, now tat
I think about it, perhaps it can be used to get a good fake
representation of c vectors that will work in structs too.)


> 5- Since all C vectors are single type like SRFI-4 vectors, it
>    makes sense to always use the SRFI-4 vector for efficiency
>    whenever the type matches.

If you mean for random Scheme code, then Scheme vectors are still
better, because accessing cvectors will create the Scheme object over
and over again (when you're using boxed values, like floating point
numbers).


>    Is it fair to assume that the (make-cvector ..) implementation
>    does this, or is it necessary to hand code the selection of
>    either (make-cvector ..) or the srfi-4 vector constructor
>    depending on type?

`srfi-4' structs are implemented using the same facility that does
cvectors.

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


Posted on the users mailing list.