[Ecls-list] FFI paradigms (was Re: ECL 10.2.1 RC)

Andy Hefner ahefner at gmail.com
Sat Feb 13 23:50:25 UTC 2010


As the discussion has turned to FFI, I'll share how I've been
approaching the problem.

On Sun, Feb 14, 2010 at 4:13 AM, Matthew Mondor
<mm_lists at pulsar-zone.net> wrote:

> With inline FFI this becomes much simpler as the file is #included, C
> struct field accessor functions can also be built with macros
> generating inline code which already knows all that's needed, using
> strings like CFFI allows.  So I've pretty much have abandonned the idea
> of using UFFI for ECL-specific contribs, I'll stick to macros on top of
> the existing c-inline FFI system.  Since the native inline FFI will
> insert ecl_*() convert calls as necessary to convert between C-native
> and the supported FFI types (and the inline-C can do it where required),
> having a wide range of C-native types support becomes less of an issue.

This is approximately how I do it (using inline C to get foreign
constants, access foreign variables and structure members, call
foreign functions), but I don't create corresponding definitions on
the lisp side. If your goal is to compatibility with UFFI / CFFI /
whatever then this is unavoidable, but otherwise I think it's an
unfortunate approach that wastes time writing "bindings" you don't
need, bloats the image with definitions you never use, and adds a
layer of indirection that forces you to stop, think, and often grep
the binding source code whenever you have to map from the original C
documentation to an FFI version.

Instead, I introduce a few helper macros to make using inline C less
verbose. Most importantly, a macro called C, a shorthand for one-liner
c-inline invocations, which takes a string of code, an optional return
type, and type/value pairs for inputs.

Here are some examples from my code:

(c "glEnable(GL_TEXTURE_2D)")
(c "glBindTexture(GL_TEXTURE_2D, #0)" :int (gltexobj-texid obj))

;; An optional return type comes first, since you can always distinguish
;; a type name from the code string, and it reads better.
(c :int "((SDL_Surface *)#0)->w" :pointer-void surface)

I've also introduced a macro CX (C eXpression) which differs only in
that it supplies :side-effects nil. I prefer this for retrieving
constants now:

(cx :int "SDL_MOUSEMOTION")

I've also toyed with a different syntax for C calls, which I use occasionally:

  (call "glColor4ub"
        :unsigned-byte (round r) :unsigned-byte (round g)
        :unsigned-byte (round b) :unsigned-byte (round a))

(call :pointer-void "IMG_Load" :cstring filename)

There's some simple things I could do to improve these helpers. CALL
would be useful more often if it recognized a special :C type of
argument that allowed substituting a C expression into the expansion,
so that the following would be equivalent:

(c "glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, #0)" :unsigned-int id)
(call "glBindProgramARB" :c "GL_FRAGMENT_PROGRAM_ARB" :unsigned-int id)

The C and CX would also benefit from a :bool return type for doing
comparisons in lisp of C values, as presently when this comes up I've
written it out as (not (zerop (C :int ...))) and occasionally screw it
up. Right now they're limited to the conversions provided by ECL.

Aspecial form to introduce local C variables within a function would
benefit this style of use. I've given no thought as to what the syntax
for such a form should be. There is an issue of name clashes with
variables created by the ECL compiler. Here's an example of how you
would use it:

(with-c-vars ("SDL_Event event;")
  (loop as eventp = (c :bool "SDL_WaitEvent(&event)")
        as type = (c :int "event.type")
        when eventp do
        (cond
         ((eql type (cx :int "SDL_QUIT")) (loop-finish))
         ((eql type (cx :int "SDL_KEYDOWN")) ...)
         ...)))

This is how my event loop works now, but with the slight irritation of
having to make the event structure a static variable introduced at
file scope using ffi:clines.

I'm fond enough of this approach that, at present, it feels like
traditional bindings and FFI libraries themselves are offensively
superfluous. If I had to bring this code up on a more conventional CL,
I'd go the route of making my inline C work there (via some invisible
glue and compiling all the C snippets into functions in an external
.so) rather than converting to use CFFI. The hypothetical WITH-C-VARS
is not possible (with reasonable effort, at least) to translate in
such an environment.




More information about the ecl-devel mailing list