Skip to content

ktakashi/r6rs-pffi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

R6RS Portable Foreign Function Interface

PFFI is a portable foreign function interface for R6RS Scheme implementations.

Example

Suppose we have the following C file and will be compiled to libfoo.so

int add(int a, int b)
{
  return a + b;
}

Now we want to use the above shared object from Scheme.

#!r6rs
(import (rnrs) (pffi))

(define shared-object (open-shared-object "libfoo.so"))

(define foo (foreign-procedure shared-object int add (int int)))

(foo 1 2) ;; => 3

See examples/ directory for more examples.

APIs

Foreign procedures and variables

This layer of the APIs wraps implementations specific foreign object accessing.

[Procedure] open-shared-object shared-object-name

Returns shared object.

[Macro] foreign-procedure shared-object return-type symbol-name (types ...)

[Macro] foreign-procedure shared-object (conv ...) return-type symbol-name (types ...)

Lookup foreign procedure symbol-name from given shared-object and returns foreign procedure. A foreign procedure is a mere procedure so users can just call as if it's a Scheme procedure.

If the second form is used, then conv must be implementation specific calling conventions. For example __cdecl for Chez Scheme.

[Macro] c-callback return-type (types ...) proc

Creates a callback. Callback is a mechanism that makes foreign procedure call Scheme procedure. The given proc is the procedure called from foreign procedure.

[Procedure] free-c-callback callback

Release allocated callback if needed.

Callback object may not be released automatically so it is user's responsibilty to make sure to release it.

[Macro] define-foreign-variable shared-object type symbol-name [scheme-name]

Lookup foreign variable symbol-name from given shared-object and binds it to scheme-name. If scheme-name is not given, then it is generated from symbol-name with following rules:

  • converting to lower case
  • converting _ to -

type must be specified properly. Currently type can only be numerical.

The bound variable is settable, thus set! syntax can change the value if it's allowed.

Foreign types

Implementations may have own bindings for foreign types. This layer absorbs the difference. Currently following types are supported.

  • char
  • unsigned-char
  • short
  • unsigned-short
  • int
  • unsigned-int
  • long
  • unsigned-long
  • float
  • double
  • int8_t
  • uint8_t
  • int16_t
  • uint16_t
  • int32_t
  • uint32_t
  • int64_t
  • uint64_t
  • pointer
  • void
  • callback

Above types are all variable except callback. Callback is a procedure which is called from foreign world to Scheme world. Thus it may need to have foreign types.

Variadic arguments

C's variadic arguments (i.e. argument specified by ...) can be written by specifying ___. For example, suppose we have sum C function which takes an int as the number of the variadic arguments, and variadic arguments. To specify this, you can write like this

(foreign-procedure lib int sum (int ___))

The ___ must be the last and must not appear more than once.

Pointer operations

[Procedure] pointer? o

Returns #t if given o is a pointer object.

[Procedure] null-pointer? pointer

Returns #t if given pointer value is 0.

[Procedure] bytevector->pointer bv

Converts given bytevector bv to implementation dependent pointer object.

[Procedure] pointer->bytevector p len

Converts given pointer to bytevector whose length is len and elements are derefered values of the pointer p.

[Procedure] integer->pointer i

Converts given integer i to a pointer object. The given integer i is the address of returning pointer.

[Procedure] pointer->integer p

Converts given pointer p to an integer. The returning integer represents the address of the pointer p.

[Procedure] pointer-ref-c-${type} p offset

${type} must be one of the following types:

  • uint8
  • int8
  • uint16
  • int16
  • uint32
  • int32
  • uint64
  • int64
  • unsigned-char
  • char
  • unsigned-short
  • short
  • unsigned-int
  • int
  • unsigned-long
  • long
  • float
  • double
  • pointer

Returns corresponding type value form give pointer p. The offset is byte offset of the given p not aligned value.

[Procedure] pointer-set-c-${type}! p offset value

${type} must be the same as pointer-ref-c-${type}.

Sets given value which is converted to corresponding type to pointer p on offset location. offset is byte offset of the given p.

Foreign structure

[Macro] define-foreign-struct name spec ...

[Macro] define-foreign-struct (name ctr pred) spec ...

Defines a structure. The macro creates constructor, predicate, size-of variable and accessors.

ctr is the constructor which returns newly allocated bytevector whose size is the size of this struct.

pred is the predicate, which simply check if the givn object is a bytevector and it has enough size for this structure. It doesn't distinguish 2 bytevectors created by 2 different ways as long as it has enough size.

Size-of variable is created adding size-of- prefix to name. This variable contains the size of this structure.

spec can be one of the followings:

  • (fields field spec ...)
  • (protocol proc)
  • (parent parent-structure)
  • (alignment alignment)

The same clause can only appear once. If there are more than one the same clause, it raises &syntax.

field spec can be one the followings:

  • (fields (type field))
  • (fields (type field getter))
  • (fields (type field getter setter))

type must be a type listed in Foreign types section except callback.

field is the field name. This is used for generating getter and setter. In other words, it doesn't have to be meaningful name as long as getter and setter is specified.

getter is an accessor to retrieve the structure field value. If this is not specified, then it is created by adding _name_- prefix to field.

setter is an accessor to set the structure field value. If this is not specified, then it is created by adding _name_- prefix and -set! suffix to field.

proc is a procedure which is the same usage as define-record-type's one.

parent-structure must be a foreign structure defined by this macro. There is no actual hierarchy but just putting specified structure in front of this structure so that it seems it has a hierarchy. For example:

alignment must be an integer or integer variable of 1, 2, 4, 8 or 16. This specifies the struct alignment size. This is equivalent of #pragma pack(n).

(define-foreign-struct p
  (fields (int count)))

(define-foreign-struct c
  (fields (pointer elements))
  (parent p))

(make-c 0 (integer->pointer 0))
;; 32 bits -> #vu8(0 0 0 0 0 0 0 0)
;; 64 bits -> #vu8(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)

is the same as the following

(define-foreign-struct p
  (fields (int count)))

(define-foreign-struct c
  (fields (p p)
          (pointer elements)))

(make-c (make-p 0) (integer->pointer 0))
;; 32 bits -> #vu8(0 0 0 0 0 0 0 0)
;; 64 bits -> #vu8(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)

If the first form is used, then ctr and pred are created by adding make- prefix and ? suffix respectively, like define-record-type.

[Macro] define-foreign-union name spec ...

Defines a union structure. The macro creates constructor, predicate, size-of variable and accessors. The auto generating convension is the same as define-foreign-struct unless its specified.

The spec can be one of the followings:

  • (fields field spec ...)
  • (protocol proc)

The fields is the same as define-foreign-struct.

The proc of protocol should look like this:

(lambda (p)
  (lamba (f)
    (p 'f f)))

The p takes 0 or 2 arguments. If the first form is used, then it creates 0 padded bytevector. If the second form is used, then it searches the field setter named f and sets the value f. If it's not found, then it behave as if nothing is passed.

Supporting implementations

  • Sagittarius (0.9.8 or later)
  • Racket (plt-r6rs v8.3)
  • Guile (3.0.8)
  • Chez Scheme (v9.5)

The below implementations are no loger supported due to the inactiveness or officially declared to be archived.

  • Larceny (v0.98)
  • Vicare (0.3d7)
  • Mosh (0.2.7)

Limitation per implementations

Vicare

Vicare doesn't support bytevector to pointer convertion whom converted pointer is shared with source bytevector. So this behaviour is emulated on this library. This emulation doesn't work on NULL pointer. So the following situation doesn't work: Suppose a shared object set a pointer value to NULL, then initialise it on a function. Scheme code first loads the pointer, then call the initialisation function, however the loaded pointer still indicates NULL.

Larceny

On Larceny, GC may move pointers so converting bytevector uses wrapper technique the same as Vicare. Thus the same limitation is applied to it.

Misc (Memo)

Why no Ypsilon

The latest released version of Ypsilon has very limited FFI interface. The biggest problem is c-function is defined as a macro which I think very limited.

Trunk repository version has far more APIs but it's not released nor maintained. Thus it is hard for me to make portable layer for it.

Why no IronScheme

.Net makes a bit things harder. And its FFI support is very limited. (e.g. it doesn't work on Mono)

To support above non supported implementations, your pull request is the fastest way :)