31.4. The Amiga Foreign Function Call Facility

Platform Dependent: No platform supports this currently

31.4.1. Design issues
31.4.2. Overview
31.4.3. Foreign Libraries
31.4.4. (Foreign) C types
31.4.5. Foreign functions
31.4.6. Memory access
31.4.7. Function Definition Files
31.4.8. Hints
31.4.9. Caveats
31.4.10. Examples

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").

31.4.1. Design issues

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.

31.4.2. Overview

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 (VECTOR (UNSIGNED-BYTE 8)) into memory.

nzero-pointer-p tests for non-NULL pointers in all recognized representations (NULL, UNSIGNED-BYTE and FFI:FOREIGN-POINTER).

31.4.3. Foreign Libraries

declare-library-base ought to be wrapped in an (EVAL-WHEN (compile eval load) ...) form and come before any function is referenced, because the library base symbol must be known.

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*.

31.4.4. (Foreign) C types

The following foreign C types are used in AFFI. They are not regular Common Lisp types or CLOS classes.

AFFI nameLisp equivalentC equivalentComment
NILNILvoidas a result type for functions only
4(UNSIGNED-BYTE 32)unsigned long 
2(UNSIGNED-BYTE 16)unsigned short 
1(UNSIGNED-BYTE 8)unsigned char 
-4(SIGNED-BYTE 32)long 
-2(SIGNED-BYTE 16)short 
-1(SIGNED-BYTE 8)signed char 
0BOOLEANBOOLas a result type for functions only
*opaquevoid* 
:EXTERNALopaquevoid* 
STRINGSTRING or VECTORchar* 
:IOSTRING or VECTORchar* 

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.

31.4.5. Foreign functions

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 (VECTOR (UNSIGNED-BYTE 8)) as input. Thus :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 ::==
 (:offset library-offset)
|(:ARGUMENTS {(argument AFFI-type register)}*)
|(:return-type AFFI-type)
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.

31.4.6. Memory access

(affi:mem-read address type offset) can read 8, 16 and 32 bit signed or unsigned integers (AFFI types -4, -2, -1, 1, 2, 4), a pointer (*), a NULL-terminated string (string) or, if the type argument is of type STRING or (VECTOR (UNSIGNED-BYTE 8)), it can fill this vector. :EXTERNAL is not an acceptable type as no object can be created by using affi:mem-read.

(affi:mem-write address type value [offset]) writes integers (AFFI type -4, -2, -1, 1, 2 and 4) or pointer values (type *), but not vectors to the specified memory address.

(affi:mem-write-vector address vector [offset]) can write memory from the given vector (of type STRING or (VECTOR (UNSIGNED-BYTE 8))).

31.4.7. Function Definition Files

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).

31.4.8. Hints

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.

31.4.9. Caveats

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 INTEGERs and FFI:FOREIGN-POINTERs, no SYMBOLs. The difficulty is just to get the library base value at run-time. Feel free to suggest enhancements to this facility!

31.4.10. Examples

Warning

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.41Last modified: 2006-10-13