[N-World Contents] [Book Contents] [Prev] [Next] [Index]

N·World's ability to handle foreign functions means you can utilize your existing C libraries to enhance your N·world code. In this chapter, you'll learn how to:


Handling Foreign Functions

For the purposes of N·World development, foreign functions are those functions coded in a language other than Lisp, such as Fortran, Pascal, or C. Although ANSI Common Lisp does not provide for defining foreign functions, most Lisp compilers contain some facility for loading (dynamic linking) compiled foreign code into Lisp images.

Unfortunately, the precise implementation of this interface varies between different Lisp compilers. Nichimen Graphics WOW includes Nichimen Foreign Functions (NFF), which is intended to serve as a generic interface between N·World and the underlying Lisp implementation.

There are four major steps to making your foreign functions callable in Lisp:

1. Compiling and linking shared object (.so) files

2. Loading .so files into Lisp

3. Mapping C argument and return value types to Lisp variable types

4. Defining the foreign function using NFF:DEFINE-FOREIGN-FUNCTION.

In addition, NFF includes a facility which allows you to create C data types, as well as create and manipulate C data objects in C or Lisp memory space.

Each of these steps is described in the sections below.


Compiling and Linking Shared Object Files

For SGI IRIX 5.3 and above, all foreign code is loaded as shared-object (.so) files. Foreign code must be compiled and linked as shared object files before they can be loaded into a running Lisp world. Functions contained within the .so files can then be made callable in Lisp by defining them as foreign functions.

To create a shared object (.so) file using the c source file foo.c:

1. Compile your code with the UNIX C compiler (cc).

Assuming the file foo.c is in the directory /usr/foo-dir/

cd /usr/foo-dir

cc -c foo.c

UNIX compiles foo.c and creates an object (.o) file called foo.o

2. Use the UNIX link-editor (ld) command to create a shared-object (.so) file.

Use the -shared option with the ld command:

ld -o foo.so -shared foo.o

The result is the shared object file foo.so.

Compiler Options

Although there is no definitively "best" syntax for compiling code, Nichimen Graphics has enjoyed consistently good performance using the following syntax to compile shared object files:

cc -c -O -KPIC foo.c
ld -o foo.so -shared -all foo.so

(See below for an explanation of the function of each flag).

Including C Libraries

You can include system (C) libraries referenced by the code in the compile and load command, as in this example:

cc -c -O -KPIC foo.c -lm -lgl_s
ld -o foo.so -shared -all foo.so -lm -lgl_s

For the cc command:

In the ld command:

Consult your system documentation for additional details about compiling files on your system.

Loading Shared Object Files

Once you've created a shared-object file, you must load it into the current Lisp world.

To load the shared-object file foo.so:

1. Use the Lisp (load) function.

At the lisp prompt, type:

(load "/usr/foo-dir/foo.so")

C functions within this file can now be made callable in Lisp using the function NFF:DEFINE-FOREIGN-FUNCTION.

Note. The Franz ACL manual states that it is possible to define foreign functions before loading the shared object file which contains them. However, Nichimen Graphics recommends always loading the .so file first before attempting to define any foreign functions.


Defining C Types

In many cases, you need to access C data objects. WOW's NFF interface provides an easy way for you to define C data types in Lisp, and use these types to create C data objects in static (C-like) memory space or in dynamic (Lisp) memory space.

You define C-types with NFF:DEFINE-C-TYPE:

NFF:DEFINE-C-TYPE						(NAME DEFINITION &KEY ALLOCATION
CONSTRUCTOR ACCESSORS)

This function returns a pointer to the new type in either Lisp space or in C space, depending on the value of the allocation keyword. By default,C-types are allocated in foreign space. The arguments for the function are:

Typedefs

You can also use NFF to typedef C-types. A C-type defined with typedef does not have constructors or allocators, and they cannot be allocated. They are intended to serve as placeholders for C-types in code for the purpose of declaring variable or return types. You can use typedefs in C-type definitions. To create a C typedef:

(nff:define-c-typedef name definition)

Type Checking

WOW's NFF interface has compile time type checking, which prevents you from defining a C-type based on an undefined type.

Example

In this example, we'll create a simple C-type called my-c-type, and define it as the C-type unsigned char.

USER(101): (nff:define-c-type MY-C-TYPE :unsigned-char)

MY-C-TYPE

Lisp returns the name of our new type. By default, this type will be allocated in foreign (static) memory space, and accessors and a constructor will be created for it. Execute an apropos on your new type to see the two new functions:

USER(102): (apropos `my-c-type :user)

my-c-type 						[function] (X)

my-c-type 						[function] (&KEY NUMBER IN-FOREIGN-SPACE)

A writer was also defined for this type, of the form

(setf my-c-type)

which stores a Lisp value in an object of type my-c-type.

Table 4.1 NFF C-type keyword arguments.
Return Type Comments
:FLOAT

Floating point types

:SHORT-FLOAT

:SINGLE-FLOAT

:DOUBLE

:LONG-FLOAT

:UNSIGNED-CHAR

Integer types

:UNSIGNED-BYTE

:CHAR

:SIGNED-CHAR

:BYTE

:SIGNED-BYTE

:UNSIGNED-SHORT

:SHORT

:SIGNED-SHORT

:UNSIGNED

:UNSIGNED-INT

:LONG

:SIGNED-LONG

:VOID

:ARRAY (c-type dim &rest dims)

Compound Types

:BIT (dim)

:POINTER (c-type)

:STRUCTURE (&rest slots)

:UNION (&rest slots)

Creating a C Object

Use the constructor function to instantiate an object of the new type:

USER(103): (make-my-c-type)

523960144

The function returns a C pointer to an object of type my-c-type. For convenience, we'll bind an object to a lisp variable:

USER(104): (setf my-value (make-my-c-type))

573721680

Getting the Value of a C Object from Lisp

Use the accessor function to obtain the value contained in the memory location referred to by the pointer:

USER(105): (my-c-type my-value)

0

Note: Values for C data objects are not initialized!!

Setting the Value of a C Object from Lisp

Use the writer to place a value in the object referred to by the pointer bound to my-value.

USER(106): (setf (my-c-type my-value) 255)

255

Data Types and Clipping

An overflow will occur If you pass a value to a C object which is greater than the maximum allowable value for that type. When a value overflows, the modulus of the value is passed. For example:

USER(109): (setf (my-c-type my-value) 256)

256

When we examine the value of the object referenced by this pointer, we see that it has been clipped to fit the bounds of the unsigned-char C-type:

USER(110): (my-c-type my-value)

0

You must use caution when overflowing C objects, since this behavior has not been tested by Nichimen and may not be entirely stable or predictable.

Defining a Type in Lisp Space

When you define a C-type in Lisp space, the resulting objects are allocated dynamically in Lisp memory. This means that they can be moved around by Lisp just like any other Lisp data object. The advantage of this approach is that it avoids many of the memory fragmentation issues associated with static memory allocation techniques like malloc. The disadvantage is that the physical location of the C object is not guaranteed to be the same every time for the C code which makes use of it. The key, therefore, is to pass objects allocated in Lisp memory space into your foreign functions each time you call them, rather than depending on your C code to "remember" where they are between calls.

You can override the default allocation for a type by using the :in-foreign-space argument to the constructor for the type:

USER(113):(setf my-value (make-my-c-type in-foreign-space nil))

#(560185050 301689879)

This time, the pointer returned is a Lisp structure (in essence, an array determining some allocated memoy).

The default value of :in-foreign-space is determined by the value of the allocation argument to the relevant define-c-type form. For example, for types allocated in Lisp space, :in-foreign-space is nil by default. The :in-foreign-space argument allows you to bypass the default allocation specified in the type definition.

Defining More Complex C Structures

You can use the :structure keyword to create more complex C-types. Slot definition for the structure follow the keyword in the definition:

USER(119): (nff:define-c-type MY-STRUCTURE

               (:structure

                (mouse-x     :long)

                (mouse-y     :long)

                (mouse-click :int)

                ))

This structure is equivalent to this C structure:

typedef struct my_structure 

{

		long		mouse-x;

		long		mouse-y;

		int		mouse-click;

} 

The slots themselves accept type definitions from Table 4.1, or you can use any type you've defined yourself. For example, if we defined the type my-c-type, then the following structure definition is perfectly valid:

USER(119): (nff:define-c-type MY-STRUCTURE

               (:structure

                (mouse-x 										my-c-type)

                (mouse-y 										my-c-type)

                (mouse-click 										my-c-type)

                ))

Manipulating Slots in Structures

When you create a structure using the NFF interface, all of the standard accessors and constructors are defined as well. Accessors for individual slots are defined as well. For example, given the structure MY-STRUCTURE we defined in the previous example:

USER(120): (setf my-struct (make-my-structure))

573722000

Where <structure> is the name of the structure and <slot> the name of the slot, the name of the accessor is <structure>-<slot>.Thus, for the mouse-x slot:

USER(122): (my-structure-mouse-x my-struct)

0

Slot writers follow a similar naming convention, i.e. (setf (<slot accessor> <object>):

USER(123): (setf (my-structure-mouse-x my-struct) 12345)

12345

Now, using the accessor function to get the value of the mouse-x slot yields its new value:

USER(125): (my-structure-mouse-x my-struct)

12345

Defining Arrays

You can define C arrays using WOW's NFF interface. Each element of an array has a type. For example, to create a one-dimensional array with five elements, each of which is of type my-structure (which we defined in the previous example):

USER(127): (nff:define-c-type MY-ARRAY

               (:array my-structure 5))

MY-ARRAY

Now, we can create an object of type my-array and bind it to a Lisp variable:

USER(129): (setf my-array-var (make-my-array))

573698832

Lisp returns the memory location for the object. Now, we can use the accessor to obtain the address of an element in the array:

USER(130): (my-array my-array-var 0)

573698832

The elements in this array are themselves structures, so to get the value of a slot in one of the elements of the array we have to specify both the array element and the appropriate slot accessor function:

USER(131): (my-structure-mouse-click (my-array my-array-var
0))

0

And to set the value of a slot, use the writer function:

USER(132): (setf (my-structure-mouse-click (my-array 
my-array-var 0)) 1)

1

C-Type Utilities

WOW's NFF interface includes several useful utilities you can use to manage your foreign types and foreign functions.

Listing C Types

The global variable NFF:*C-TYPES* is a list of all currently defined c-types. You can print this variable (or use a suitable FORMAT form) to print the list of currently defined C-types.

You can also use the NFF:MAP-C-TYPES utility to apply a function to the list of C-types. The following example prints a list of all the NFF C-types which are built-in (not user defined):

USER(40): (nff:map-c-types #'(lambda (c-type)
(when (typep c-type `nff:built-in-c-type)
(print c-type))))

#<BUILT-IN-C-TYPE :UNION> 

#<BUILT-IN-C-TYPE :STRUCTURE> 

#<BUILT-IN-C-TYPE :POINTER> 

#<BUILT-IN-C-TYPE :BIT> 

#<BUILT-IN-C-TYPE :ARRAY> 

#<BUILT-IN-C-TYPE :VOID> 

#<BUILT-IN-C-TYPE :SIGNED-LONG> 

etc....

You could use the (not (typep c-type `nff:built-in-c-type)) predicate to return a list of all user defined C-types.

Finally, you can find a specific C-type using NFF:FIND-C-TYPE:

USER(41):(nff:define-c-type MY-TYPE :char)

MY-TYPE

USER(42):(nff:find-c-type `my-type)

#<C-TYPE MY-TYPE>

Examining C Types

You can use the describe function to examine C-types in more detail.

USER(43): (describe (nff:find-c-type `my-type))

#<C-TYPE MY-TYPE> is the c-type named MY-TYPE.

Its definition is :CHAR.

This can be particularly useful for more complex data structures, like the MY-STRUCTURE structure we defined earlier:

USER(44): (describe (nff:find-c-type `my-structure))

#<C-TYPE MY-STRUCTURE> is the c-type named MY-STRUCTURE.

Its definition is

(:STRUCTURE (MOUSE-X :LONG) (MOUSE-Y :LONG) (MOUSE-CLICK :INT)).

Describe reveals the each slot and its type.


Using FF Types

Lisp does not require explicit typing of variables, but C does. Each argument you pass to a foreign function, or value returned from one, must be typed. FF types are merely easy-to-use names for Lisp types, which unify naming conventions for argument, return-value, and array-types. FF types also provide for compile-time consistency (which is not provided by the underlying ACL foreign-function interface.)

Some, such as :VOID, can only be used to specify return values. In Table 4.2, this information is listed as a set of one or more letters in parentheses after the Category listing in the rightmost column.

So, (ARa) means a type can be used in all three contexts, whereas (AR) means the type can only be used as an argument or return value type.

Table 4.2 WOW NFF Foreign Function (FF) types
FF Type Lisp type Category
:FLOAT

single-float

Floating point types
(ARa)

:SHORT-FLOAT

short-float (!!) (In ACL 4.3 is equivalent to sin
gle-float)

:SINGLE-FLOAT

single-float

:DOUBLE

double-float

:LONG-FLOAT

long-float (!!) Equivalent to double-float for ACL 4.3.

:DOUBLE-FLOAT

double-float

:FIXNUM

fixnum

Integer Types
(ARa)

:INTEGER

integer - as an array type is (signed-byte 32)

:UNSIGNED-INTEGER

integer - as an array type is (unsigned-byte 32)

:CHARACTER

character

:SIGNED-8BIT

as an array type is (signed-byte 8)

Byte Types (Aa)

:UNSIGNED-8BIT

as an array type is (unsigned-byte 8)

:SIGNED-16BIT

as an array type is (signed-byte 16)

:UNSIGNED-16BIT

as an array type is (unsigned-byte 16)

:SIGNED-32BIT

as an array type is (signed-byte 32)

:UNSIGNED-32BIT

as an array type is (unsigned-byte 32)

:STRING

string

String types
(AR)

:SIMPLE-STRING

simple-string

:FOREIGN-STRING

[a pointer to a null terminated string]

:ARRAY
(ff-type & rest dims)

array

Array
(A)

:SIMPLE-ARRAY
(ff-type & rest dims)

simple-array

:SIMPLE-VECTOR
(ff-type)

simple array of rank 1

:BOOLEAN

Lisp Boolean (not NIL / NIL)

Booleans
(AR)

:LISP

A Lisp object (implementation specific)

Lisp
(AR)

:POINTER (c-type)

a pointer to a C-TYPE (defined with NFF:DEFINE-C-TYPE or NFF:DEFINE-C-TYPEDEF

Pointers
(AR)

:VOID

void (no return value)

Void
(R)

Defining New FF Types

Although the WOW NFF interface includes a comprehensive suite of C-types, you may want to define your own FF type. To do so, use NFF:DEFINE-FF-TYPE:

(nff:define-ff-type name definition)

For example:

(nff:define-ff-type MYBOOLEAN :unsigned-char)

FF Type Utilities

The same utilities exist for FF-types as for C-types, only with slightly different names.

nff:find-ff-types

nff:map-ff-types

nff:*FF-TYPES*

Managing Strings

C strings are null terminated, whereas Lisp strings are not. WOW's NFF interface includes two handy utilities for translating between foreign and Lisp strings. To translate a Lisp string to a foreign (null terminated) string:

USER(44):(nff:string-to-foreign-string "A test string")

268766640

The function returns a pointer to the null terminated string. You can pass this function an address if you choose to do so.

To translate a foreign string to a lisp string, just pass the pointer to a null terminated string to NFF:FOREIGN-STRING-TO-STRING:

USER(45): (nff:foreign-string-to-string *)

"A test string"


Defining Foreign Functions

Use NFF:DEFINE-FOREIGN-FUNCTION to define foreign functions:

(nff:define-foreign-function (NAME ARGLIST &KEY LANGUAGE
ENTRY-POINT ARG-CHECKING RETURN-TYPE CALL-DIRECT CALLBACK
PROTOTYPE DOCUMENTATION)

Example:

Assume that you are working with the following C function, read_token:

int read_token(FILE* stream, char* buffer, int size)

{

...

}

Let's assume you've compiled and linked this function in the shared object file read-token.so. To define the foreign function:

(nff:define-foreign-function C-READ-TOKEN-INTERNAL
((c-stream :long)(buffer :char)(size :int))
:entry-point "read_token"

		:return-type :int)

You can now call the C function read_token as the Lisp function C-READ-TOKEN-INTERNAL. For example, the file pointer stream becomes the Lisp variable c-stream, of type integer.


General Issues With Foreign Functions

Watch out for Bignums & C Pointers

There are several important points to remember when specifying types for variables passed from Lisp to C:

NFF is based on ACL's defforeign foreign function interface. If you want to learn more about foreign functions, you can macroexpand the NFF forms, then refer to the ACL documentation to learn more about how the ACL interface works.

When to Recompile Your Lisp Code

If calldirect is used, any callers of the foreign function must be recompiled whenever there is a change in either the original C function definition, or the Lisp nff:define-foreign-function definition. If you specify :callback, you can make changes to your C source code without recompiling calling Lisp functions. Simply create and load an updated .so file which contains the C functions you modify. However, if changes to the C code affect the arguments of a foreign function, you MUST create an updated .so file AND recompile calling Lisp functions.

.SO Load Order

Although the Franz documentation indicates that load order does not matter, Nichimen Graphics has found that random, inexplicable breaks can occur if foreign functions are defined before the relevant .so files are loaded. Load .so files first, then define foreign functions.

.SO Files Cannot Reference External Definitions

Each .so file must be complete in itself; all routines must either be defined in that .so file or must be in a library compiled with the .so file.


Converting N·World 2.1 Foreign Functions

N·World 2.1 relied strictly on the ACL foreign function interface. Table 4.3 shows the mapping between NFF and ACL arglist type arguments, while Table 4.4 shows the concordance between return types:

Table 4.3 WOW NFF and ACL 4.3 argument types
WOW NFF type ACL 4.3 type Category
:SINGLE-FLOAT

SINGLE-FLOAT

Floating Point Types

:DOUBLE-FLOAT

DOUBLE-FLOAT

:SHORT-FLOAT

SHORT-FLOAT

:LONG-FLOAT

LONG-FLOAT

:FLOAT

SINGLE-FLOAT

:DOUBLE

DOUBLE-FLOAT

:FIXNUM

FIXNUM

Integer types

:INTEGER

INTEGER

:CHARACTER

CHARACTER

:STRING

STRING

Character types

:SIMPLE-STRING

SIMPLE-STRING

:ARRAY

ARRAY

Arrays

:SIMPLE-ARRAY

SIMPLE-ARRAY

:SIMPLE-VECTOR

SIMPLE-ARRAY

(:ARRAY TYPE DIMS)

(ARRAY TYPE DIMS)

:BOOLEAN

(FIXNUM (IF <argument> 1 0)))

Boolean types

(:POINTER C-TYPE)

FF:FOREIGN-ADDRESS

Pointers

:FOREIGN-STRING

FF:FOREIGN-ADDRESS

Strings

:LISP

:LISP

Lisp Object

Table 4.4 WOW NFF and ACL 4.3 return types
WOW NFF type ACL 4.3 type Category
:SINGLE-FLOAT

SINGLE-FLOAT

Floating Point Types

:DOUBLE-FLOAT

DOUBLE-FLOAT

:SHORT-FLOAT

SHORT-FLOAT

:LONG-FLOAT

LONG-FLOAT

:FLOAT

SINGLE-FLOAT

:DOUBLE

DOUBLE-FLOAT

:FIXNUM

FIXNUM

Integer types

:INTEGER

INTEGER

:CHARACTER

CHARACTER

:UNSIGNED-INTEGER

:UINTEGER

:STRING

:INTEGER (FF:CHAR*-TO-STRING <result>))

Character types

:SIMPLE-STRING

:INTEGER (FF:CHAR*-TO-STRING <result>))

:ARRAY

ARRAY

Arrays

:SIMPLE-ARRAY

SIMPLE-ARRAY

:SIMPLE-VECTOR

SIMPLE-ARRAY

(:ARRAY TYPE DIMS)

(ARRAY TYPE DIMS)

:BOOLEAN

(INTEGER (NOT (ZEROP <result>)))

Boolean types

(:POINTER C-TYPE)

FF:FOREIGN-ADDRESS

Pointers

:FOREIGN-STRING

FF:FOREIGN-ADDRESS

Strings

:VOID

:VOID

Void (no return value)

:LISP

:LISP

Lisp objects



[N-World Contents] [Book Contents] [Prev] [Next] [Index]

Another fine product from Nichimen documentation!

Copyright © 1996, Nichimen Graphics Corporation. All rights reserved.