Skip to content

FFI (interop with C)

brian-dawn edited this page Oct 9, 2015 · 2 revisions

(Work in progress)

FFI or interop with C libraries

Pixie has very good support for interop with C libraries, these features are based of a fairly basic set of features, but are composed via pixie.ffi-infer into some rather easy to use utilites.

Loading Symbols

C Libraries can be loaded via pixie.stdlib/ffi-library, a library with the given name will be searched for in every directory found in pixie.stdlib/*load-paths*. The library once found will be returned as a pixie.stdlib.ExternalLib object that can be handed to one of two symbol loading functions.

pixie.stdlib/ffi-fn is used to load a function from an external library, while pixie.stdlib/ffi-voidp is used to load void* symbols.

Although these primitives are fairly basic, getting the arguments to these functions correct can be rather complex. Instead it is recommended that developers use the namespace known as ffi-infer

FFI-INFER

The ffi-infer namespace contains a simple but competent inference engine for creating C interop libraries. The user of this namespace simply specifies symbols to import and where they can be found, and the engine determines the proper way to import them. The inference process can take some time, as it involves compiling a C++ template file that makes heavy use of template meta-programming. But the resulting data can be cached by compiling a given .pxi file to a .pxic file. Thus the macros are only compiled once, and the result of the macros can be re-used on each subsequent invocation of the Pixie program.

FFI-INFER example

Here is a basic example of how to use ffi-infer:

(ns my-sdl-example
  (require pixie.ffi-infer :refer :all))

(with-config {:library "SDL"
              :cxx-flags ["`sdl2-config --cflags --libs`"]
              :includes ["SDL.h"]
              }
  (defconst SDL_INIT_EVERYTHING)
  (defcfn SDL_Init)

  (defconst SDL_WINDOW_SHOWN))

When this example is executed, with-config will create a small C++ program that links against flags found in :cxx-flags. The program will start with a import of SDL.h. The body of the program will then be a collection of template definitions that make use of Boost Type Traits to infer and emit the types of the requested symbols. This program will emit the type data as EDN. That EDN data will be loaded by with-config and used to generate a bunch of type defnintions. The resulting macro-expanded code will look something like this:

(binding [*library* (ffi-library "SDL.dylib")]
  (def SDL_INIT_EVERYTHING 1)
  (def SDL_Init (ffi-fn *library* "SDL_Init" [CUInt32] CUInt64))
  (def SDL_WINDOW_SHOWN 44))

There are several instructions that can be handed to with-config that handle everything from ffi callbacks, to structs, to global variables, but in this example, the value of this interop method can be clearly seen. The user simply specifies how to compile a C program with the given library, and the symbols that need to be imported, along with the generic type (function, global, constant, etc). And ffi-infer does all the heavy lifting.

Pixie itself makes heavy use of ffi-infer and so it is fairly robust and the upside is that the runtime penalty for precompiled files is very low.

FFI-INFER instructions

** TODO **