[Openmcl-devel] Foreign Function Questions

Gary Byers gb at clozure.com
Fri Nov 24 23:15:26 PST 2006

On Fri, 24 Nov 2006, Brent Fulgham wrote:

> I have a couple of questions regarding the FFI and interface
> databases.  Perhaps this is covered in the documentation, but an
> initial review did not turn up what I was looking for.
> 1.  Is there a way to get the correct "decorated" names of the FFI
> functions from the database?  While it's pretty easy (in many cases)
> to guess from the original C header, I can't always figure out how
> things like arrays and other pointer types are expected to be passed.

When dealing with foreign function call arguments and results, OpenMCL's 
FFI basically deals with:
  - signed and unsigned integers of width 8/16/32/64
  - single and double floats
  - aggregate objects (structures/unions) that're passed by value
    on some platforms
  - pointers.

If you ignore the issue of vector (AltiVec/XMM) types, that's
pretty much what C deals with.  Note that the difference between
an array type and a pointer type in C is often pretty subtle,
and it's often possible to use arrays an pointers interchangably.

I don't remember whether the interface database stores syntactically
richer type information; by the time #_ has looked up a foreign
function definition, everything's boiled down to one of those
basic types (:signed-halfword, :address, etc.)

> 2.  I would like to define some materials for the OpenGL FFI
> interface.  In C, this might look something like:
>      glMaterialfv(GL_BACK, GL_DIFFUSE, {0.396, 0.74151, 0.69102, 1.0});
>      But if I attempt to call it using something like:
>       (#_glMaterialfv #$GL_FRONT #$_GL_AMBIENT #(0.396D0 0.74151D0
> 0.69102D0 1.0D0))
> I get an error:  "> Error: value #<SIMPLE-VECTOR 4> is not of the
> expected type MACPTR."
> What's the proper way to declare this in the FFI description?

Suppose that we wanted to call the C library function #_open, which
will return a file descriptor (small non-negative integer) or -1 (error
indicator) given a file namestring and an integer which specifies some
mode flags.  If we did:

? (#_open "/etc/passwd" #$O_RDONLY) ; open an existing file
                                                     ; for reading
we'd get a similar error (a complaint that the string "/etc/passwd"
isn't a MACPTR).

In C, a string (for all intents and purposes; it could be argued that
C doesn't have a "string" data type, just a few sets of conventions)
is just the address of a sequence of 8-bit bytes with a #\nul byte
at the end.

In Lisp in general (and in OpenMCL in particular), a string is ...
something else entirely.  In particular, it has some bits (somewhere)
that say that it's a string, and it probably has some bits (somewhere)
that say how many elements it contains, as well as some bits (or the
absence of some bits) that say whether or not it's a SIMPLE-STRING,
and whether or not it has a fill pointer and other things ... To
the extent that it has an "address", that address is generally
determined by the GC and can change at any instruction boundary.
(There are exceptions to this general rule.)

What we sort of want to do in order to be able to call #_open is

a) allocate a block of non-relocatable memory somewhere.  Since
    we'll only need that block of memory until we're done calling
    #_open, a stack would be a good place to allocate it.  The
    block of memory needs to be of length N+1, where N is the
    length of the lisp string.

b) copy the character codes of the characters in the lisp string
    to successive 8-bit bytes in the memory block.

c) slap a trailing #\Nul byte on the end of the memory block

d) pass the address of that memory block as the first argument to

This is a common enough idiom that there's a macro that does most
of it for is (WITH-CSTRS).  If we used WITH-CSTRS when attempting
to call #_open, as in:

(with-cstrs ((c-name "/etc/passwd"))
   (#_open c-name #$O_RDONLY))

the FFI shouldn't complain and we should get a non-negative
file descriptor back from the call to #_open.

Back to your example: I'm not entirely sure that the best lisp
interface to #_glMaterialfv would involve passing a lisp array
around, and if it did there might be some question as to whether
an array of element-type SINGLE-FLOAT should be used or whether
the interface we'd want should be more general.  Assuming that
we decided to lisp SINGLE-FLOAT vector around, we'd wind up
with a similar set of issues as existed in the string case:

In C, an array of SINGLE-FLOATs is just the address of a block
of memory that contains some bits that can be interpreted as
SINGLE-FLOATs (or what C would probably just call a "float").

In Lisp (especially in OpenMCL), a (VECTOR SINGLE-FLOAT) is something
else entirely; as in the case of a Lisp STRING, it has a lot of 
auxiliary information about it encoded in bits that are part of
the object, and (as in the string case) we can't meaningfully talk
about the "address" of a (VECTOR SINGLE-FLOAT) in general without
running into GC issues.

We'd have to go through essentially the same steps (stack-allocate
some foreign memory, copy the values in the lisp SINGLE-FLOAT array
into the foreign memory block, then pass the address of that block
to #_glMaterialfv).  Doing that would be simpler and less error-prone
if there was a WITH-FOREIGN-SINGLE-FLOAT-VECTOR (analogous to 
WITH-CSTRs), but ... there isn't.  If we were going to do a lot of
this and had thought about if or how a lisp vector was the best
way to represent the material matrix and had thought about whether
a specialize vector type should be used or whether we wanted to
be more general about it, we might find it useful to write
WITH-FOREIGN-SINGLE-FLOAT-VECTOR.  (We'd also find it easier to
write if OpenMCL offered some syntactic sugar for dealing with
foreign arrays; without that, the code looks uglier and is probably
more error-prone as well.)

A less-than-general approach that'd do the right thing in this
particular case would be something like:

(rlet ((c-float-array (:array :float 4)))
   ;; We happen to know that a single-float value is 32-bits/4 bytes
   ;; wide, so we can multiply each array index by 4 to get a byte
   ;; index.  The FFI could do this for us.
  (dotimes (i 4)
    (setf (%get-single-float c-float-array (* i 4))
          (aref lisp-float-array i)))
  (#_glMaterialfv #$GL_FRONT #$_GL_AMBIENT c-float-array))

> Thanks,
> -Brent
> _______________________________________________
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
> http://clozure.com/mailman/listinfo/openmcl-devel

More information about the Openmcl-devel mailing list