Hi All,
I am working on a ffi-helper (FH): a program that will read in a C dot-h file
and generate a Guile dot-scm file which defines a module to provide hooks into
the associated C library.
Currently, I'm working on documentation. I like manuals that start out with
simple demos to show what's going on. That is my approach here. Enjoy ...
FFI Helper for Guile
********************
Matt Wette
December 2017
With NYACC Version 0.00.0
1 Introduction
**************
The acronym FFI stands for "Foreign Function Interface". It refers to
the Guile facility for binding functions and variables from C source
libraries into Guile programs. This distribution provides utilities for
generating a loadable Guile module from a set of C declarations and
associated libraries. The C declarations can, and conventionally do,
come from naming a set of C include files. The nominal method for use
is to write a _ffi-module_ specification in a file which includes a
'define-ffi-module' declaration and then use the command 'guild
compile-ffi' to convert this to Guile Scheme.
$ guild compile-ffi ffi/cairo.ffi
wrote `ffi/cairo.scm'
Note that no C code is generated. The hooks to access C-coded functions
in the Cairo library are provided in 100% Guile Scheme.
The compiler for the FFI Helper (FH) is based on the C parser and
utilities which are included in the NYACC (https://www.nongnu.org/nyacc)
package. Development for the FH is currently being performed in the
'c99dev' branch of the associated git repository. Within the NYACC
distribution, the relevant modules can be found under the directory
'examples/'.
Use of the FFI-helper module depends on the _scheme-bytestructure_
package available from
<https://github.com/TaylanUB/scheme-bytestructures>. Since this package
is currently not under version control we provide a partial copy in the
NYACC distribution.
You are probably hoping to see an example, so let's do that.
2 Demonstration
***************
This is a small FFI Helper example to illustrate its use. We will start
with the Cairo (cairographics.org) package because that is the first one
I started with in developing this package. Say you are an avid Guile
user and want to be able to use cairo in Guile. On most systems this
comes with associated _pkg-config_ support files.
WARNING: The FFI-helper package is under active development and there
is some chance the following example will cease to work in the future.
If you want to follow along and are working in the distribution tree,
you should source the file 'env.sh' in the 'examples' directory.
By practice, I like to put all FH generated modules under a directory
called 'ffi/', so we will do that. We start by generating, in the 'ffi'
directory, a file named 'cairo.ffi' with the following contents:
(define-ffi-module (ffi cairo)
#:pkg-config "cairo"
#:include '("cairo.h" "cairo-pdf.h" "cairo-svg.h"))
Now to generate a Guile module you use 'guild' as follows:
$ guild compile-ffi ffi/cairo.ffi
wrote `ffi/cairo.scm'
Though the file 'cairo/cairo.ffi' is only three lines long, the file
'ffi/cairo.scm' will be over five thousand lines long. It looks like
the following:
(define-module (ffi cairo)
#:use-module (system ffi-help-rt)
#:use-module ((system foreign) #:prefix ffi:)
#:use-module (bytestructures guile)
)
(define link-libs
(list (dynamic-link "libcairo")))
;; int cairo_version(void);
(define ~cairo_version
(delay (fh-link-proc
ffi:int
"cairo_version"
(list)
link-libs)))
(define (cairo_version)
(let () ((force ~cairo_version))))
(export cairo_version)
...
;; typedef struct _cairo_matrix {
;; double xx;
;; double yx;
;; double xy;
;; double yy;
;; double x0;
;; double y0;
;; } cairo_matrix_t;
(define-public cairo_matrix_t-desc
(bs:struct
(list `(xx ,double)
`(yx ,double)
`(xy ,double)
`(yy ,double)
`(x0 ,double)
`(y0 ,double))))
(define-fh-compound-type cairo_matrix_t cairo_matrix_t-desc cairo_matrix_t?
make-cairo_matrix_t)
(export cairo_matrix_t cairo_matrix_t? make-cairo_matrix_t)
... many, many more declarations ...
;; access to enum symbols and #define'd constants:
(define ffi-cairo-symbol-val
(let ((sym-tab
'((CAIRO_SVG_VERSION_1_1 . 0)
(CAIRO_SVG_VERSION_1_2 . 1)
(CAIRO_PDF_VERSION_1_4 . 0)
(CAIRO_PDF_VERSION_1_5 . 1)
(CAIRO_REGION_OVERLAP_IN . 0)
(CAIRO_REGION_OVERLAP_OUT . 1)
(CAIRO_REGION_OVERLAP_PART . 2)
(CAIRO_FILTER_FAST . 0)
(CAIRO_FILTER_GOOD . 1)
(CAIRO_FILTER_BEST . 2)
... more constants ...
(CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID
.
"application/x-cairo.jbig2-global-id"))))
(lambda (k) (or (assq-ref sym-tab k)))))
(export ffi-cairo-symbol-val)
(export cairo-lookup)
... more ...
Note that from the _pkg-config_ spec the FH compiler picks up the
required libraries to bind in. Also, '#define' based constants, as well
as those defined by enums, are provided in a lookup function
'ffi-cairo-symbol-val'. So, for example
guile> (use-modules (ffi cairo))
;;; ffi/cairo.scm:6112:11: warning: possibly unbound variable `cairo_raster_source_acquire_func_t*'
;;; ffi/cairo.scm:6115:11: warning: possibly unbound variable `cairo_raster_source_release_func_t*'
guile> (ffi-cairo-symbol-val 'CAIRO_FORMAT_ARGB32))
$1 = 0
We will discuss the warnings later. These are signals that extra code
needs to be added to the ffi module. But you see how the constants (but
not CPP function macros) can be accessed.
Let's try something more useful: a real program. Create the
following code in a file, say 'cairo-demo.scm', first up a Guile sesion
and 'load' the file.
(use-modules (ffi cairo))
(define srf (cairo_image_surface_create 'CAIRO_FORMAT_ARGB32 200 200))
(define cr (cairo_create srf))
(cairo_move_to cr 10.0 10.0)
(cairo_line_to cr 190.0 10.0)
(cairo_line_to cr 190.0 190.0)
(cairo_line_to cr 10.0 190.0)
(cairo_line_to cr 10.0 10.0)
(cairo_stroke cr)
(cairo_surface_write_to_png srf "cairo-demo.png")
(cairo_destroy cr)
(cairo_surface_destroy srf)
If we set up everything correctly you should have the target 'png' image
of a square. A few items in the above code are notable. First, the
call to 'cairo_image_surface_create' accepted a symbolic form
(''CAIRO_FORMAT_ARGB32' for the format. It would have also accepted the
associated constant '0'. In addition, '(ffi cairo)' function will
accept Scheme strings where the C function wants "pointer to string."
Now try this in your Guile session:
guile> srf
$4 = #<cairo_surface_t* 0x7fda53e01880>
guile> cr
$5 = #<cairo_t* 0x7fda54828800>
Note that the FH keeps track of the C types you use. This can be useful
for debugging but may bloat the namespace. The constants you see are
the pointer values. But it goes further. Let's generate a matrix type:
guile> (define m (make-cairo_matrix_t))
guile> m
$6 = #<cairo_matrix_t 0x10cc26c00>
guile> (pointer-to m)
$7 = #<cairo_matrix_t* 0x10cc26c00>
When it comes to C APIs that expect the user to allocate memory for a
structure and pass the pointer address to the C function, FH provides
the solution:
guile> (cairo_get_matrix cr (pointer-to m))
guile> (use-modules (system ffh-help-rt))
guile> (fh-object-ref m 'xx)
$9 = 1.0