The Amiga Foreign Function Call Facility ======================================== 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"). Design issues ------------- AFFI was designed to be small in size but powerful enough to use most library functions. Lisp files may be compiled to .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 FINALIZE (see impnotes.txt). Overview -------- These are the AFFI forms: (DECLARE-LIBRARY-BASE keyword-base library-name) (REQUIRE-LIBRARY-FUNCTIONS library-name [(:import {string-names}*)) (OPEN-LIBRARY base-symbol) (CLOSE-LIBRARY base-symbol) (WITH-OPEN-LIBRARY (base-symbol | library-name) {forms}*) (DEFFLIBFUN function-name base-symbol offset mask result-type {argument-type}*) (DECLARE-LIBRARY-FUNCTION function-name library-name {options}*) (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, everything is a function. 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's 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 cheks 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 unsigned-byte vector into memory. NZERO-POINTER-P tests for non-NULL pointers in all recognized representations (NULL, UNSIGNED-BYTE and FOREIGN-POINTER). 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 preceeded 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. [A previous version did so but now AFFI is a module and there are no module exit functions.] Programmers, watch AFFI::*LIBRARIES-ALIST*. (Foreign) C types ----------------- The following foreign C types are used in AFFI. They are *not* regular Common Lisp types or CLOS classes. AFFI name Lisp equiv C equiv NIL NIL void (r) 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 0 (MEMBER NIL T) "BOOL" (r) * opaque void* :EXTERNAL opaque void* STRING STRING or VECTOR char* :IO STRING or VECTOR char* (r) as a result type for functions only. Objects of type STRING are copied and passed NUL-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 NUL-terminated! This is useful for functions like like read() 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 FOREIGN-POINTER are always acceptable where a STRING or :IO type is specified. To meet the design goals, predefined types and objects were used. As such, pointers were represented as integers. Now that there is the FOREIGN-POINTER type, both representations may be used on input. The pointer type should be therefore considered as opaque. Use NZEROP-POINTER-P for NULL tests. 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. On the Amiga, it's an integer whose hexadecimal value is the reverse of the function argument register numbers, where D0 has number 1 and A6 number #xF. A NIL mask is reserved for stack-based calls (unimplemented). 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 FOREIGN-TYPE as function result-type (except from NIL for a NULL pointer) and refuses objects of type STRING or UNSIGNED BYTE-VECTOR as input. Thus :EXTERNAL provides some security on the input and the ability to use FINALIZE for resource-tracking on the output side. (DECLARE-LIBRARY-FUNCTION name library-name {options}*) option ::= (:offset ) | (:arguments {( )}*) | (:return-type ) register ::= :D0 :D1 .. :D7 :A0 ... :A6 declares a named library funtion 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. Memory access ------------- (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 NUL-terminated string (STRING) or, if the type argument is of type STRING or UNSIGNED BYTE-VECTOR, it can fill this vector. :EXTERNAL is not an acceptable type as no object can be created by using MEM-READ. (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. (MEM-WRITE-VECTOR address vector [offset]) can write memory from the given vector (of type STRING or UNSIGNED BYTE-VECTOR). Function Definition Files ------------------------- REQUIRE-LIBRARY-FUNCTION 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 keword (recommended use). Some definition files for standard Amiga libraries are provided. See example 1 below. As 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 4 below. The file READ-FD.LSP 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). Hints ----- By using appropriate EVAL-WHEN forms for DECLARE-LIBRARY-BASE and REQUIRE-LIBRARY-FUNCTIONS and not using FLIBCALL, it is possible to write code that only loads library function definition files at compile-time. See example 1 below. Do not rely on FINALIZE to free resources for you, as CLISP does not call finalizers when it exits, use UNWIND-PROTECT. 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 AFFI1.LSP) were to change, the low-level part (AFFI.D) should remain untouched as all it knows are integers and 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! Examples -------- NB: These examples are soemwhat specific to the Amiga. 1. Using a predefined library function file (use-package "AFFI") ;; 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. 2. Using flibcalll (use-package "AFFI") (eval-when (compile eval load) (declare-library-base :SysBase "exec.library")) ;keyword avoids name conflicts ;; 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) 3. Be fully dynamic, defining library bases ourselves (use-package "AFFI") (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))) 4. Some sample function definitions (defflibfun 'FindTask 'SysBase -294 #xA '* 'string) (declare-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 NUL-terminated-string (mlibcall SetVar name value -1 GVF_LOCAL_ONLY)))