Dynamic FFI
1 Warning:   Read this Section Before Use
2 Dependencies
3 Defining FFIs
define-dynamic-ffi
dynamic-ffi-lib
define-dynamic-ffi/  cached
4 Generating Static FFIs
generate-static-ffi
generate-mapped-static-ffi
5 Inline FFIs
define-inline-ffi
6 Limitations
7 Acknowledgements
8.12

Dynamic FFI🔗ℹ

This module produces automatic ABI-native FFI bindings to C libraries.
Dynamic FFI is a native Racket extension which embeds clang/llvm to parse out declarations from C headers and dynamically build FFI objects with correct type/size information. This library is currently only available for GNU/Linux and macOS, but should be easily portable to other operating systems. If you are experienced with building clang/llvm plugins on other OSes and would like to contribute, please contact the author.
This module uses native extensions to Racket’s C runtime, and is not currently compatible with Racket-on-Chez. Support for Racket-on-Chez is planned for the future.

1 Warning: Read this Section Before Use🔗ℹ

Like Racket’s built-in ffi/unsafe module, this library allows Racket to use C functions and data structures that are not memory-managed by Racket. Racket cannot provide safety guarantees by default for FFI objects created using this library. Users of this library will be required to self-enforce memory management in C and/or manually expose FFI objects to Racket’s garbage collector. Extra care should be taken to ensure that C functions bound via this library or Racket’s built-in FFI do not contain errors like buffer overflows, which could corrupt the runtime, cause undefined behavior, and prevent Racket from providing useful error messages.

Users of this library should be extremely cautious. The library makes it easier to call C functions from Racket, but does not make it safer.

Finally:

Passing unsanitized user-input to any function in this library is horribly dangerous. You should never do it under any circumstance.

2 Dependencies🔗ℹ

Dynamic FFI requires clang and llvm headers and libraries to work. Clang versions 6 and 7 are both known to work with Dynamic FFI. This library does not bundle clang or llvm.

To install a clang toolchain on Fedora:

sudo dnf install "@development tools" racket llvm-devel clang-devel

To install a clang toolchain on Ubuntu:

sudo apt-get install "build-essential" racket llvm-dev libclang-dev clang

To install a clang toolchain on macOS (assuming that Homebrew is installed), run brew install llvm, then follow the instructions emitted by Homebrew to setup paths.

During the raco package install, Dynamic FFI will compile itself and link with the system clang libraries. Dependencies should be installed before the raco installation, or else the library will defer building itself, and will complain about missing dependencies when you try to use it. To finish the installation process, install the required dependencies and run the following command:

raco setup -p -D dynamic-ffi

3 Defining FFIs🔗ℹ

syntax

(define-dynamic-ffi id lib header ...)

 
  lib : 
(or/c string? path?
      (cons/c (or/c string? path?) (listof string?)))
  header : (or/c string? path?)
Parses headers for syntax tree data and builds dynamic FFI bindings. The resulting id will be bound to a case-lambda function which takes the name of the C function as the first argument, and the C function parameters as the rest. If the defined id is called with no arguments, a hash-map of all symbols and FFI objects in the library is returned.

lib can be a relative library created using dynamic-ffi-lib, or a hard-coded path omitting the object file extension.

(require dynamic-ffi/unsafe)
 
(define-dynamic-ffi libc
  (dynamic-ffi-lib "libc" "6")
  "/usr/include/stdio.h")
 
(libc 'printf "hello world\n")

procedure

(dynamic-ffi-lib lib version ...)  
(cons/c (or/c string? path?)
        (listof string?))
  lib : (or/c string? path?)
  version : string?
Takes a library base name and shared object versions, and produces a list argument which can be used in place of a hard-coded system object file path. In the above define-dynamic-ffi example, Racket will search for libc.so.6 in the default system library paths.

syntax

(define-dynamic-ffi/cached id lib header ...)

 
  lib : (or/c string? path?)
  header : (or/c string? path?)
This form behaves the same way as define-dynamic-ffi, except it caches static FFI bindings so that they do not need to be recomputed in future program executions.

4 Generating Static FFIs🔗ℹ

This library has the ability to generate static FFI files that can be used without the dependency on dynamic-ffi.

procedure

(generate-static-ffi ffi-name    
  file    
  lib-path    
  headers ...)  void?
  ffi-name : (or/c string? symbol?)
  file : (or/c string? path?)
  lib-path : (or/c string? path? (cons/c (or/c string? path?) (listof string?)))
  headers : (or/c string? path?)
Generates a static FFI where identifiers corresponding to C function names are prefixed with ffi-name and bound to their associated FFI objects.

procedure

(generate-mapped-static-ffi ffi-name    
  file    
  lib-path    
  headers ...)  void?
  ffi-name : (or/c string? symbol?)
  file : (or/c string? path?)
  lib-path : (or/c string? path? (cons/c (or/c string? path?) (listof string?)))
  headers : (or/c string? path?)
Generates a static FFI whose interface is equivalent to the case-lambda function produced by define-dynamic-ffi.

5 Inline FFIs🔗ℹ

This library allows users to write C functions inline, which will be compiled at runtime and provided as a dynamic FFI.

syntax

(define-inline-ffi name code ... [#:compiler compiler]
                                 [#:compile-flags flags])
 
  code : string?
  compiler : string?
  flags : (or/c string? 'auto)
Define FFI bindings by writing inline C code. This form is designed for use with the at-reader. For security reasons define-dynamic-ffi only accepts C code as string literals.

@define-inline-ffi[mylib]{
  int add(int x, int y) {
    return x + y;
  }
}
(mylib 'add 3 4)

The form can be used without the at-reader as well.

(define-inline-ffi mylib
  "int add(int x, int y) {\n"
  "  return x + y;\n"
  "}")
 
(mylib 'add 3 4)

Extra compile flags can be passed to define-inline-ffi, and the default compiler can be overridden.

(define-inline-ffi libm #:compile-flags "-lm" #:compiler "clang"
  "#include <math.h>\n"
  "double square_root(double x) {\n"
  "  return sqrt(x);\n"
  "}")
(libm 'square_root 16)

6 Limitations🔗ℹ

This library is able to generate dynamic FFI bindings for a fairly large portion of the C language standard. Occasionally we find special declaration types that clang treats differently, which the library may not have support for yet. When such declarations are encountered, dynamic-ffi will emit error messages with details about the missing functionality. Please report any issues you find at the project issue tracker.

As a workaround in almost every case, define-inline-ffi can be used to write a wrapper around functions that dynamic-ffi is unable to parse.

7 Acknowledgements🔗ℹ

Special thanks to Jay McCarthy for suggesting this project idea, and for his advisership during its implementation.