foreign-c/README.md

482 lines
22 KiB
Markdown

---
title: Portable Foreign Function Interface for R7RS Documentation
version: 0.6.0
---
# Portable Foreign Function Interface for R7RS
Portable foreign function interface for R7RS. It is portable in the sense that
it supports multiple implementations, as opposed to being portable by
conforming to some specification.
[Project](https://todo.sr.ht/~retropikzel/r7rs-pffi)
[Issue trackers](https://sr.ht/~retropikzel/r7rs-pffi/trackers)
[Maling lists](https://sr.ht/~retropikzel/r7rs-pffi/lists)
[Jenkins](https://jenkins.scheme.org/job/r7rs_pffi/job/r7rs-pffi/)
## Table of contents
<nav>
- [Goals](#goals)
- [Non Goals](#non-goals)
- [Status](#status)
- [Current caveats](#current-caveats)
- [Roadmap](#roadmap)
- [Implementation table](#implementation-table)
- [Beta](#beta)
- [Alpha](#alpha)
- [Not started](#not-started)
- [Other](#other)
- [Documentation](#documentation)
- [Usage](#usage)
- [Chibi](#usage-chibi)
- [Chicken](#usage-chicken)
- [Racket](#usage-racket)
- [Kawa](#usage-kawa)
- [Reference](#reference)
- [Types](#types)
- [Procedures and macros](#procedures-and-macros)
- [pffi-init](#pffi-init)
- [pffi-size-of](#pffi-size-of)
- [pffi-align-of](#pffi-align-of)
- [pffi-define-library](#pffi-define-library)
- [pffi-pointer-null](#pffi-pointer-null)
- [pffi-pointer-null?](#pffi-pointer-null?)
- [pffi-pointer-allocate](#pffi-pointer-allocate)
- [pffi-pointer-address](#pffi-pointer-address)
- [pffi-pointer?](#pffi-pointer?)
- [pffi-pointer-free](#pffi-pointer-free)
- [pffi-pointer-set!](#pffi-pointer-set!)
- [pffi-pointer-get](#pffi-pointer-get)
- [pffi-string->pointer](#pffi-string-to-pointer)
- [pffi-pointer->string](#pffi-pointer-to-string)
- [pffi-struct-make](#pffi-struct-make)
- [pffi-struct-pointer](#pffi-struct-pointer)
- [pffi-struct-offset-get](#pffi-struct-offset-get)
- [pffi-struct-get](#pffi-struct-get)
- [pffi-struct-set!](#pffi-struct-set!)
- [pffi-define](#pffi-define)
- [pffi-define-callback](#pffi-define-callback)
</nav>
<main>
## Goals
<a name="goals"></a>
- Support only R7RS implementations
- Same interface on all implementations
- Some things that are procedures on one implementation are macros on other,
but they must behave the same
- Stability and being boring after 1.0.0 is reached
## Non goals
<a name="non-goals"></a>
- To have every possible FFI feature
- Compiling of used library C code at any point
- That is no stubs, no C code generated by the library and so on
- The pffi library itself may require compilation on installation
## Status
<a name="status"></a>
Currently the interface of the library is in okay shape. It propably will not change much but no
guarantees are being made just yet.
Due to supporting many different Scheme implementations, different parts of this software are in
different stage. As a whole it is still in **alpha** stage. That said the interface should not be
changing anymore and some implementations are in **beta**.
### Current caveats
<a name="current-caveats"></a>
- No way to pass structs by value
- Most implementations are missing callback support
- Always pass arguments to pffi functions/macros as '(1 2 3) and not (list 1 2 3)
- Always pass pffi-define-callback procedure as lambda in place
## Roadmap
For roadmap to 1.0.0 see [issues](https://todo.sr.ht/~retropikzel/r7rs-pffi?search=status%3Aopen%20label%3A%221.0.0%22)
## Implementation table
<a name="implementation-table"></a>
### Released
<a name="released"></a>
### Beta
<a name="beta"></a>
| | pffi-init | pffi-size-of | pffi-define-library | pffi-pointer-null | pffi-pointer-null? | pffi-pointer-allocate | pffi-pointer-address | pffi-pointer? | pffi-pointer-free | pffi-pointer-set! | pffi-pointer-get | pffi-string->pointer | pffi-pointer->string | pffi-struct-make | pffi-struct-pointer | pffi-struct-offset-get | pffi-struct-get | pffi-struct-set! | pffi-define | pffi-define-callback |
|--------------|:---------:|:------------:|:-------------------:|:-----------------:|:------------------:|:---------------------:|:--------------------:|:-------------:|:-----------------:|:-----------------:|:----------------:|:--------------------:|:--------------------:|:----------------:|:-------------------:|:----------------------:|:---------------:|:----------------:|:-----------:|:--------------------:|
| Chibi | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
| Chicken-5 | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
| Gauche | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | |
| Guile | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
| Kawa | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
| Racket | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
| Saggittarius | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
### Alpha
<a name="alpha"></a>
| | pffi-init | pffi-size-of | pffi-define-library | pffi-pointer-null | pffi-pointer-null? | pffi-pointer-allocate | pffi-pointer-address | pffi-pointer? | pffi-pointer-free | pffi-pointer-set! | pffi-pointer-get | pffi-string->pointer | pffi-pointer->string | pffi-struct-make | pffi-struct-pointer | pffi-struct-offset-get | pffi-struct-get | pffi-struct-set! | pffi-define | pffi-define-callback |
|--------------|:---------:|:------------:|:-------------------:|:-----------------:|:------------------:|:---------------------:|:--------------------:|:-------------:|:-----------------:|:-----------------:|:----------------:|:--------------------:|:--------------------:|:----------------:|:-------------------:|:----------------------:|:---------------:|:----------------:|:-----------:|:--------------------:|
| Cyclone | X | X | X | X | X | X | | X | X | X | X | X | X | X | X | X | X | X | X | |
| Gambit | X | X | | | | | X | | | | | | | X | X | X | X | X | | |
| Gerbil | X | | | | | | | | | | | | | X | X | X | X | X | | |
| Larceny | X | | | | | | | | | | | | | X | X | X | X | X | | |
| Mosh | X | X | X | X | X | X | | X | X | X | X | X | X | X | X | X | X | X | X | X |
| Skint | X | | | | | | | | | | | | | X | X | X | X | X | | |
| Stklos | X | X | X | X | X | X | | X | X | | | | | X | X | X | X | X | | |
| tr7 | | | | | | | | | | | | | | X | X | X | X | X | | |
| Ypsilon | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X | X |
### Not started
<a name="not-started"></a>
- [LIPS](https://lips.js.org/)
- Will work on nodejs by using some C FFI library from npm
- Javascript side needs design
- [Biwascheme](https://www.biwascheme.org/)
- Will work on nodejs by using some C FFI library from npm
- Javascript side needs design
- [MIT-Scheme](https://www.gnu.org/software/mit-scheme/)
- Need to study the implementation more
- [Airship](https://gitlab.com/mbabich/airship-scheme)
- Need to study the implementation more
- [Other gambit targets](https://gambitscheme.org/)
- Gambit compiles to different targets other than C too, for example Javascript. It would be cool
and interesting to see if this FFI could also support some of those
- When LIPS and Biwascheme Javascript side is done then Gambit should be done too
- [s48-r7rs](https://codeberg.org/prescheme/s48-r7rs)
- Need to study the implementation more
- [prescheme](https://codeberg.org/prescheme/prescheme)
- Need to study the implementation more
### Other
<a name="other"></a>
- [s7](https://scheme.fail://ccrma.stanford.edu/software/snd/snd/s7.html)
- Propably does not need FFI as it is embeddable only
- [Loko](https://scheme.fail/)
- Desires no C interop, I can respect that
## Documentation
<a name="documentation"></a>
### Usage
<a name="usage"></a>
#### Chibi
<a name="usage-chibi"></a>
Needs libffi-dev, on Debina/Ubuntu/Mint install with:
apt install libffi-dev
Build with:
make chibi
#### Chicken
<a name="usage-chicken"></a>
Needs [r7rs egg](https://wiki.call-cc.org/eggref/5/r7rs), install with:
chicken-install r7rs
#### Racket
<a name="usage-racket"></a>
Needs [racket-r7rs](https://github.com/lexi-lambda/racket-r7rs), install with:
raco pkg install --auto r7rs
#### Kawa
<a name="usage-kawa"></a>
Kawa Needs at least Java version 22
Needs jvm flags:
- \--add-exports java.base/jdk.internal.foreign.abi=ALL-UNNAMED
- \--add-exports java.base/jdk.internal.foreign.layout=ALL-UNNAMED
- \--add-exports java.base/jdk.internal.foreign=ALL-UNNAMED
- \--enable-native-access=ALL-UNNAMED
## Reference
<a name="reference"></a>
### Types
<a name="types"></a>
Types are given as symbols, for example 'int8 or 'pointer.
- int8
- uint8
- int16
- uint16
- int32
- uint32
- int64
- uint64
- char
- unsigned-char
- short
- unsigned-short
- int
- unsigned-int
- long
- unsigned-long
- float
- double
- pointer
- callback
- Callback function
### Procedures and macros
<a name="procedures-and-macros"></a>
Some of these are procedures and some macros, it might also change implementation to implementation.
#### pffi-init <a name="pffi-init"></a>
**pffi-init**
Always call this first, on most implementation it does nothing but some implementations might need
initialisation run.
#### pffi-size-of <a name="pffi-size-of"></a>
**pffi-size-of** object -> number
Returns the size of the pffi-struct, pffi-enum or pffi-type.
#### pffi-align-of <a name="pffi-align-of"></a>
**pffi-align-of** type -> number
Returns the align of the type.
#### pffi-define-library <a name="pffi-define-library"></a>
**pffi-define-library** headers shared-object-name [options] -> object
Load given shared object automatically searching many predefined paths.
Takes as argument a list of C headers, these are for the compiler ones. And an shared-object name,
used by the dynamic FFI's. The name of the shared object should not contain suffix like .so or
.dll. Nor should it contain any prefix like "lib".
Additional options argument can be provided, theys should be a pair with a
keyword. The options are:
- additional-versions
- Search for additional versions of shared object, given shared object "c" and additional
versions "6" "7" on linux the files "libc", "libc.6", "libc.7" are searched for.
- Can be either numbers or strings
- additional-paths
- Give additional paths to search shared objects for
Example:
(cond-expand
(windows (pffi-define-library libc-stdlib
'("stdlib.h")
"ucrtbase"
'((additional-versions ("0" "6"))
(additiona-paths (".")))))
(else (pffi-define-library libc-stdlib
(list "stdlib.h")
"c"
'((additional-versions ("0" "6"))
(additiona-paths ("."))))))
#### Notes
- Do not cond-expand inside the arguments, that might lead to problems on some
implementations.
- Do not store options in variables, that might lead to problems on some
implementations.
- Do pass the headers using quote
- As '(... and not (list...
- Do pass the options using quote
- As '(... and not (list...
#### pffi-pointer-null <a name="pffi-pointer-null"></a>
**pffi-pointer-null** -> pointer
Returns a new NULL pointer.
#### pffi-pointer-null? <a name="pffi-pointer-null?"></a>
**pffi-pointer-null?** pointer -> boolean
Returns #t if given pointer is null pointer, #f otherwise.
#### pffi-pointer-allocate <a name="pffi-pointer-allocate"></a>
**pffi-pointer-allocate** size -> pointer
Returns newly allocated pointer of given size.
#### pffi-pointer-address <a name="pffi-pointer-address"></a>
**pffi-pointer-address** pointer -> number
Returns the address of given pointer as number.
#### pffi-pointer? <a name="pffi-pointer?"></a>
**pffi-pointer?** object -> boolean
Returns #t if given object is pointer, #f otherwise.
#### pffi-pointer-free <a name="pffi-pointer-free"></a>
**pffi-pointer-free** pointer
Frees given pointer.
#### pffi-pointer-set! <a name="pffi-pointer-set!"></a>
**pffi-pointer-set!** pointer type offset value
Sets the value on a pointer on given offset. For example:
(define p (pffi-pointer-allocate 128))
(pffi-pointer-set! p 'int 64 100)
Would set the offset of 64, on pointer p to value 100.
#### pffi-pointer-get <a name="pffi-pointer-get"></a>
**pffi-pointer-get** pointer type offset -> object
Gets the value from a pointer on given offset. For example:
(define p (pffi-pointer-allocate 128))
(pffi-pointer-set! p 'int 64 100)
(pffi-pointer-get p 'int 64)
> 100
#### pffi-string->pointer <a name="pffi-string-to-pointer"></a>
**pffi-string->pointer** string -> pointer
Makes pointer out of a given string.
#### pffi-pointer->string <a name="pffi-pointer-to-string"></a>
**pffi-pointer->string** pointer -> string
Makes string out of a given pointer.
#### pffi-struct-make <a name="pffi-struct-make"></a>
**pffi-struct-make** c-type members . pointer -> pffi-struct
Creates a new pffi-struct and allocates pointer for it. The members argument is a list of member
names and types. For example:
(define color (pffi-struct-make 'color '((int8 . r) (int8 . g) (int8 . b) (int8 .a ))))
(define test (pffi-struct-make "struct test" '((int8 . r) (int8 . g) (int8 . b) (int8 .a ))))
C-type argument can be symbol or a string.
#### pffi-struct-pointer <a name="pffi-struct-pointer"></a>
**pffi-struct-pointer** pffi-struct -> pointer
Returns the pointer that holds the struct content. You need to use this when passing a struct as
a pointer to foreign functions.
(define s (pffi-struct-make 'test '((int . r) (int . g) (int . b))))
(pffi-struct-pointer s)
#### pffi-struct-offset-get <a name="pffi-struct-offset-get"></a>
**pffi-struct-offset-get** member-name -> number
Returns the offset of a struct member with given name.
#### pffi-struct-get <a name="pffi-struct-get"></a>
**pffi-struct-get** pffi-struct member-name -> object
Returns the value of the givens struct member.
#### pffi-struct-set! <a name="pffi-struct-set!"></a>
**pffi-struct-set!** pffi-struct member-name value
Sets the value of the givens struct member. It is up to you to make sure that the type of value is
correct.
#### pffi-define <a name="pffi-define"></a>
**pffi-define** scheme-name shared-object c-name return-type argument-types
Defines a new foreign function to be used from Scheme code. For example:
(cond-expand
(windows (pffi-define-library libc-stdlib '("stdlib.h") "ucrtbase" '("")))
(else (pffi-define-library libc-stdlib '("stdlib.h") "c" '("" "6"))))
(pffi-define c-puts libc-stdlib 'puts 'int '(pointer))
(c-puts "Message brought to you by FFI!")
#### pffi-define-callback <a name="pffi-define-callback"></a>
**pffi-define-callback** scheme-name return-type argument-types procedure
Defines a new Sceme function to be used as callback to C code. For example:
; Load the shared library
(cond-expand
(windows (pffi-define-library libc-stdlib '("stdlib.h") "ucrtbase" '()))
(else (pffi-define-library '("stdlib.h") "c" '("" "6"))))
; Define C function that takes a callback
(pffi-define qsort libc-stdlib 'qsort 'void '(pointer int int callback))
; Define our callback
(pffi-define-callback compare
'int
'(pointer pointer)
(lambda (pointer-a pointer-b)
(let ((a (pffi-pointer-get pointer-a 'int 0))
(b (pffi-pointer-get pointer-b 'int 0)))
(cond ((> a b) 1)
((= a b) 0)
((< a b) -1)))))
; Create new array of ints to be sorted
(define array (pffi-pointer-allocate (* (pffi-size-of 'int) 3)))
(pffi-pointer-set! array 'int (* (pffi-size-of 'int) 0) 3)
(pffi-pointer-set! array 'int (* (pffi-size-of 'int) 1) 2)
(pffi-pointer-set! array 'int (* (pffi-size-of 'int) 2) 1)
(display array)
(newline)
;> (3 2 1)
; Sort the array
(qsort array 3 (pffi-size-of 'int) compare)
(display array)
(newline)
;> (1 2 3)
</main>