Another Foreign Function Interface. All symbols relating to the simple foreign function interface are
exported from the package “AFFI”. To use them,
(
.USE-PACKAGE
"AFFI")
“AFFI” was designed to be small in size but powerful enough to
use most library functions. Lisp files may be compiled to #P".fas"
files without the need to load function definition files at run-time and
without external C or linker support.
memory images can be created, provided that the function libraries are
opened at run-time.
Therefore, “AFFI” supports only primitive C types
(integers 8, 16 and 32 bits wide, signed or unsigned, pointers) and
defines no new types or classes. Foreign functions are not first-class
objects (you can define a LAMBDA
yourself), name spaces are
separate.
The “AFFI” does no tracking of resources. Use EXT:FINALIZE
.
These are the “AFFI” forms:
(declare-library-base
keyword-base
library-name
)
(require-library-functions
library-name
[(:import {string-name
}*)])
(open-library
base-symbol
)
(close-library
base-symbol
)
(with-open-library (
base-symbol
|
library-name
) {form
}*)
(defflibfun
function-name
base-symbol
offset
mask
result-type
{argument-type
}*)
(declare-library-function
function-name
library-name
{option
}*)
(flibcall
function-name
{argument
}*)
(mlibcall
function-name
{argument
}*)
(mem-read
address
result-type
[offset
])
(mem-write
address
type
value
[offset
])
(mem-write-vector
address
vector
[offset
])
(nzero-pointer-p
value
)
Except for with-open-library
,
declare-library-function
and
mlibcall
, all of the above are functions.
A library contains a collection of functions. The library is
referred to by a symbol referred as library-base at the “AFFI”
level. This symbol is created in the package “AFFI”.
The link between this symbol and the OS-level library name is
established by declare-library-base
. To avoid
multiple package conflicts, this and only this function requires the
symbol-name to be in the “KEYWORD” package. The function returns the
library-base.
A library may be opened by open-library
and
closed by close-library
. An opened library must be
closed. with-open-library
is provided to
automatically close the library for you, thus it is much safer to
use.
A function is contained in a library. Every function is referred
to by a symbol. A function is defined through
defflibfun
or
declare-library-function
by giving the function
name, the library-base, an offset into the library, a mask (or NIL
)
for register-based library calls, the result type and all
parameter-types. require-library-functions
loads
the complete set of functions defined in a library file. Symbols are
created in the package “AFFI” and imported into the current
package.
flibcall
and mlibcall
call library functions. mlibcall
is a macro that
does a few checks at macroexpansion time and allows the compiler to
inline the call, not requiring the foreign function to be defined again
at load or execution time. The use of this macro is advertised wherever
possible.
mem-read
reads an arbitrary address (with
offset for structure references) and returns the given type.
mem-write
writes an arbitrary
address. mem-write-vector
copies the content of a
Lisp STRING
or (
into memory.VECTOR
(UNSIGNED-BYTE
8))
nzero-pointer-p
tests for
non-NULL
pointers in all recognized representations
(NULL
, UNSIGNED-BYTE
and FFI:FOREIGN-POINTER
).
declare-library-base
ought to be wrapped in an
(
form and come before any function is referenced, because the library
base symbol must be known.EVAL-WHEN
(compile eval load) ...)
open-library
tries to open the library
referenced by the base symbol. Therefore it must have been preceded
with declare-library-base
. The call returns NIL
on failure. open-library
calls nest. Every
successful call must be matched by close-library
.
with-open-library
does this for you and also allows
you to specify the library by name, provided that its base has been
declared. It is recommended to use this macro and to reference the
library by name.
CLISP will not close libraries for you at program exit.
See Section 30.1, “Customizing CLISP Process
Initialization and Termination” and watch
AFFI::*LIBRARIES-ALIST*
.
The following foreign C types are used in “AFFI”. They are not regular Common Lisp types or CLOS classes.
“AFFI” name | Lisp equivalent | C equivalent | Comment |
---|---|---|---|
NIL | NIL | void | as a result type for functions only |
4 | ( | unsigned long | |
2 | ( | unsigned short | |
1 | ( | unsigned char | |
-4 | ( | long | |
-2 | ( | short | |
-1 | ( | signed char | |
0 | BOOLEAN | BOOL | as a result type for functions only |
* | opaque | void* | |
:EXTERNAL | opaque | void* | |
STRING | STRING or VECTOR | char* | |
:IO | STRING or VECTOR | char* |
Objects of type STRING
are copied and passed
NULL
-terminated on the execution stack. On return, a Lisp string is
allocated and filled from the address returned (unless NULL
).
Functions with :IO
parameters are passed the address of the Lisp
string or unsigned byte vector. These are not NULL
-terminated!
This is useful for functions like
like read-c
which do not need an
array at a constant address longer than the dynamic extent of the call
(it is dangerous to define callback functions with :IO
- or
STRING
- type parameters).
Arguments of type INTEGER
and FFI:FOREIGN-POINTER
are always
acceptable where a STRING
or :IO
type is specified.
See also CUSTOM:*FOREIGN-ENCODING*
.
To meet the design goals, predefined types and objects were
used. As such, pointers were represented as integers. Now that there is
the FFI:FOREIGN-POINTER
type, both representations may be used on input.
The pointer type should be therefore considered as opaque.
Use nzero-pointer-p
for NULL
tests.
Foreign Functions are declared either through
defflibfun
or
declare-library-function
. The former is closer to
the low-level implementation of the interface, the latter is closer to
the other “FFI”.
defflibfun
requires the library base symbol
and register mask to be specified,
declare-library-function
requires the library name
and computes the mask from the declaration of the arguments.
The value of mask is implementation-dependent.
The “AFFI” type 0
is only acceptable as a
function result type and yields either T
or NIL
. The difference
between *
and :EXTERNAL
is the following:
*
uses integers, :EXTERNAL
uses
FFI:FOREIGN-POINTER
as function result-type (except from NIL
for a
NULL
pointer) and refuses objects of type STRING
or
(
as input. Thus VECTOR
(UNSIGNED-BYTE
8)):EXTERNAL
provides some security
on the input and the ability to use EXT:FINALIZE
for resource-tracking on
the output side.
(declare-library-function
name
library-name
{option
}*)
option
::==register
::==:d0
| :d1
| ... | :d7
| :a0
| ... |
:a6
declares a named library function for further reference through
flibcall
and mlibcall
.
mlibcall
should be the preferred way of
calling foreign functions (when they are known at compile-time) as
macroexpansion-time checks may be performed and the call can be sort of
inlined.
(affi:mem-read
can read 8, 16 and 32 bit
signed or unsigned integers (“AFFI” types -4,
-2, -1, 1, 2,
4), a pointer (*), a address
type
offset
)NULL
-terminated
string (string) or, if the type argument is of type
STRING
or (
, it can fill this vector.
VECTOR
(UNSIGNED-BYTE
8)):EXTERNAL
is not an acceptable type as no object can be created by
using affi:mem-read
.
(affi:mem-write
writes integers (“AFFI”
type -4, -2, -1, 1,
2 and 4) or pointer values (type
*), but not vectors to the specified memory address.address
type
value
[offset
])
(affi:mem-write-vector
can write memory from the given vector (of type address
vector
[offset
])STRING
or (
).
VECTOR
(UNSIGNED-BYTE
8))
affi:require-library-functions
will REQUIRE
a
file of name derived from the library name and with type
affi
. It may be used to import all names into the
current package or only a given subset identified by string names, using
the :import
keyword (recommended use).
Some definition files for standard Amiga libraries are provided.
See Example 31.12, “Using a predefined library function file” below.
As affi:require-library-functions
loads a
global file which you, the programmer, may have not defined, you may
consider declaring every function yourself to be certain what the return
and argument types are.
See Example 31.15, “Some sample function definitions” below.
The file read-fd.lisp
defines the function
make-partial-fd-file
with which the provided
.affi
files have been prepared from the original
Amiga FD files (located in the directory FD:
).
They must still be edited as the function cannot know whether a function
accepts a *
, :IO
,
string
or :EXTERNAL
argument and because files
in FD:
only contain a register specification, not
the width of integer arguments (-4
,
-2
, -1
, 1
,
2
, or 4
).
By using appropriate EVAL-WHEN
forms for
affi:declare-library-base
and
affi:require-library-functions
and not using
affi:flibcall
, it is possible to write code that
only loads library function definition files at compile-time.
See Example 31.12, “Using a predefined library function file” below.
Do not rely on EXT:FINALIZE
to free resources for you, as CLISP
does not call finalizers when it exits, use UNWIND-PROTECT
.
You can consider the library bases being symbols in need of being
imported from the package “AFFI” originating from a brain-damage, causing
the usual symbol headaches when using foreign functions calls within macros.
Luckily, even if the high-level interface (or its implementation in
src/affi1.lisp
) were to change,
the low-level part (src/affi.d
)
should remain untouched as all it knows are INTEGER
s and
FFI:FOREIGN-POINTER
s, no SYMBOL
s.
The difficulty is just to get the library base value at run-time.
Feel free to suggest enhancements to this facility!
These examples are somewhat specific to the Amiga.
Example 31.12. Using a predefined library function file
(DEFPACKAGE
"AFFI-TEST" (:use “COMMON-LISP” "AFFI")) (IN-PACKAGE
"AFFI-TEST") ;; SysBase is the conventional name for exec.library ;; It is only enforced by the file loaded by REQUIRE-LIBRARY-FUNCTIONS (eval-when (compile eval load) (declare-library-base :SysBase "exec.library")) ;keyword avoids name conflicts ;; using only MLIBCALL allows not to load definitions at load-time (eval-when (compile eval) (require-library-functions "exec.library" :import '("FindTask"))) (with-open-library ("exec.library") (print (mlibcall FindTask 0)))
This file can be used in interpreted and compiled mode. Compiled, it will have inlined the library function calls.
Example 31.13. Using flibcall
(DEFPACKAGE
"AFFI-TEST" (:use “COMMON-LISP” "AFFI")) (IN-PACKAGE
"AFFI-TEST") (eval-when (compile eval load) ;; keyword avoids name conflicts (declare-library-base :SysBase "exec.library")) ;; The load situation permits the use of flibcall (eval-when (eval compile load) (require-library-functions "exec.library")) (unless (open-library 'SysBase) (error "No library for SysBase")) (flibcall (if t 'FindTask 'Debug) 0) (close-library 'SysBase)
Example 31.14. Be fully dynamic, defining library bases ourselves
(DEFPACKAGE
"AFFI-TEST" (:use “COMMON-LISP” "AFFI")) (IN-PACKAGE
"AFFI-TEST") (eval-when (compile eval load) (defvar mylib (declare-library-base :foobase "foo.library"))) (eval-when (eval compile load) ;eval allows mlibcall, load flibcall (defflibfun 'foo1 mylib -30 '#xA '* 'string) (defflibfun 'foo2 mylib -36 '#x21 0 * 4)) (defun foo (name) (when (open-library mylib) (list (mlibcall foo1 name) (flibcall 'foo2 name 123213)) (close-library mylib)))
Example 31.15. Some sample function definitions
(defflibfun 'FindTask 'SysBase -294 #xA '* 'string)
(eval-library-function FindTask "exec.library"
(:offset -294)
(:return-type *)
(:arguments
(name string :A1)))
(declare-library-function NameFromLock "dos.library"
(:offset -402)
(:return-type 0)
(:arguments
(lock 4 :D1)
(buffer :io :D2)
(len 4 :D3)))
(eval-when (compile eval)
(defconstant GVF_LOCAL_ONLY (ash 1 9))
(defflibfun 'SetVar 'DosBase -900 #x5432 0 'string 'string -4 4))
(defun setvar (name value)
(with-open-library (DosBase)
;; length of -1 means find length of NULL
-terminated-string
(mlibcall SetVar name value -1 GVF_LOCAL_ONLY)))
These notes document CLISP version 2.41 | Last modified: 2006-10-13 |