home *** CD-ROM | disk | FTP | other *** search
- 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 <library-offset>)
- | (:arguments {(<arg-name> <AFFI type> <register>)}*)
- | (:return-type <AFFI-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)))
-
-