(foreign c)
- About
- Supported implementations
- Installation
- Documentation
- Roadmap
- Implementation support being worked on
- Adding support for new implementation
- Notes
About
(foreign c) is a C foreign function interface (FFI) library for R6RS and R7RS Schemes
Supported implementations
R6RS is supported trough akku.
R7RS is supported trough snow-fort.
- Chez => 10.0.0
- R6RS
- Chibi > 0.11
- R7RS
- Needs libffi
- apt-get install libffi-dev
- Chicken >= 6.0.0
- R7RS
- Ikarus >= 0.0.4-rc1+
- R6RS
- Ironscheme
- R6RS
- Kawa >= 3.11 and Java >= 24
- R7RS
- Needs arguments to enable FFI
- -J--add-exports=java.base/jdk.internal.foreign.abi=ALL-UNNAMED
- -J--add-exports=java.base/jdk.internal.foreign.layout=ALL-UNNAMED
- -J--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED
- -J--enable-native-access=ALL-UNNAMED
- -Dkawa.import.path=/usr/local/share/kawa/lib/*.sld
- So that snow-chibi installed library is found
- Mosh
- R7RS
- Racket >= 8.16 [cs]
- R7RS
- Sagittarius >= 0.9.13
- R6RS
- R7RS
- STklos >= 26.0
- R7RS
- Ypsilon >= 2.08
- R7RS
Installation
R6RS
akku install "(foreign c)"
R7RS
snow-chibi --impls=$SCHEME foreign.c
Documentation
Types
(c-type? type) lol
If type is C type returns #t otherwise #f.
Example:
(c-type? 'i8)
;> #t
If type is C integer type returns #t otherwise #f.
Example:
(c-type-integer? 'i8)
;> #t
If type is C char type returns #t otherwise #f.
Example:
(c-type-char? 'char)
;> #t
If type is C float type returns #t otherwise #f.
Example:
(c-type-float? 'float)
;> #t
If type is C double type returns #t otherwise #f.
Example:
(c-type-double? 'double)
;> #t
If type is C signed type returns #t otherwise #f.
Example:
(c-type-signed? 'i8)
;> #t
(c-type-signed? 'u8)
;> #f
If type is C pointer type returns #t otherwise #f.
Example:
(c-type-pointer? 'pointer)
;> #t
Returns the size of type.
Example:
(c-type-size 'i8)
;> 1
(make-c-bytevector (* (c-type-size 'int)))
Adds type sizes together and returns the result.
Example:
(c-type-size+ 'u8 'u8)
;> 2
(c-type-size+ 'int 'int)
;> 8
Subtracts types sizes and returns the result.
Example:
(c-type-size- 'int 'u8) ;> 3
Multiplies type size with n and returns the result.
Example:
(c-type-size* 'int 2)
;> 8
Divides type size with n and returns the result.
Example:
(c-type-size/ 'int 2)
;> 2
Returns the align of type.
Example:
(c-type-align 'i8)
;> 1
(c-type-align 'int)
;> 4
Adds given types alignments together and returns the result.
Example:
(c-type-align+ 'u8 'u8)
> 2
Subtracts given aligns and returns the result.
Example:
(c-type-align- 'int 'u8)
;> 3
Multiples given align with n and returns the result.
Example:
(c-type-align* 'int 100)
;> 400
Divides given align with n and retursn the result.
Example:
(c-type-align/ 'int 2)
;> 2
Libraries and procedures
(define-c-library scheme-name headers shared-object-name options)
Takes a scheme-name to bind the library to, list of C headers as strings, shared-object-name or #f and options. If shared-object-name is given as #f then platforms C library is used.
The C header strings should not contain "<" or ">", they are added automatically. Pass them using ' and not (list ...).
The shared-object-name should not contain suffix like .so or .dll. Nor should it contain any prefix like "lib".
Options:
- 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 from
Example:
(define-c-library libc '("stdio.h") #f '())
(define-c-library libcurl '("curl/curl.h")
"curl"
'((additional-versions ("" "0" "6"))
(additional-paths ("."))))
(define-c-procedure scheme-name shared-object c-name return-type argument-types)}
Takes a scheme-name to bind the C procedure to, shared-object where the function is looked from, c-name of the function as symbol, return-type and argument-types.
Defines a new foreign function to be used from Scheme code.
Example:
(define-c-library libc '("stdlib.h") #f '())
(define-c-procedure c-puts libc 'puts 'int '(pointer))
(define str-cbv (string->c-bytevector "Message brought to you by (foreign c)!"))
(c-puts str-cb)
(c-bytevector-free str-cbv)
;> Message brought to you by (foreign c)
c-bytevectors
(make-c-bytevector size [byte])
Returns a newly allocated c-bytevector of size bytes.
If the byte argument is missing, the initial contents of the returned c-bytevector are unspecified.
If the fill argument is present, it's value must confine to C u8 values, it specifies the initial value for the bytes of the c-bytevector.
Example:
(make-c-bytevector 128) (make-c-bytevector 128 0)
(c-bytevector byte ...) Returns a newly allocated c-bytevector containing bytes.
Example:
(c-bytevector 0 0 0 0)
Returns #t if obj is c-bytevector, otherwise returns #f.
Frees given c-bytevectors from memory.
Returns a null c-bytevector.
Returns #t if obj is a null c-bytevector, otherwise returns #f.
(c-bytevector-set! cbv type offset value)
Set value of given type on offset on bytevector cbv. Offset is counted as bytes.
(c-bytevector-ref cbv type offset)
Get value of given type on offset on bytevector cbv. Offset is counted as bytes.
Returns a newly allocated c-bytevector of the bytes of bytevector bv.
(c-bytevector->bytevector cbv size)
Returns a newly allocated bytevector of the bytes of c-bytevector cbv. If size is larger than length of c-bytevector the effect is unspecified.
(c-bytevector->copy cbv [start)
Returns a newly allocated bytevector of the bytes of c-bytevector cbv. If size is larger than length of c-bytevector the effect is unspecified.
(c-bytevector->integer cbv offset)
Returns the address of the bytevector cbv as integer. If offset is given it is added to the the returned integer. offset must be an integer.
(integer->c-bytevector address)
Returns the bytevector in the integer address.
Strings
Returns a newly allocated c-bytevector that contains the (string->utf8) bytes of the given string str.
(with-string->c-bytevector str thunk) Calls thunk with newly allocated c-bytevector that contains the (utf8->string) bytes of the given string str and frees it after thunk finishes.
Returns a newly allocated string that contains (utf8->string) bytes of given c-bytevector cbv. If cbv is null empty string is returned.
Null byte (\0) you can use in strings.
Pass pointer by address
(call-with-address-of cbv thunk)
Calls procedure thunk with address c-bytevector of c-bytevector cbv.
Since the support for calling C functions taking pointer address arguments, ones prefixed with & in C, varies, some additional ceremony is needed on the Scheme side.
Example:
Calling from C:
//void func(int\*\* i);
func(&i);
Calling from Scheme:
(define cbv (make-bytevector (c-type-size 'int)))
(call-with-address-of
cbv
(lambda (address-cbv)
(func address-cbv)))
; Use cbv here
The passed c-bytevector, in example named cbv, should only be used after call to call-with-addres-of ends.
Arrays
To access C array index use c-bytevector-ref, c-bytevector-set! and c-type-size. For example say you have array of type 'int and you want to access the 10th index.
(c-bytevector-ref cbv (* (c-type-size 'int) 9)) ; Or shortcut (c-bytevector-ref cbv (c-type-size* 'int 9))
Returns c-bytevector with list lst values written into it.
The list values must be/fit into C type type. If c-bytevector cbv is given, values are written into that. If not, a new c-bytevector is allocated.
Example:
(define (cbv (list->array '(1 2 3 4) 'int)))
(c-bytevector-ref cbv 'int (c-type-size* 'int 0))
;> 1
(c-bytevector-ref cbv 'int (c-type-size* 'int 1))
;> 2
(c-bytevector-ref cbv 'int (c-type-size* 'int 2))
;> 3
(c-bytevector-ref cbv 'int (c-type-size* 'int 3))
;> 4
Returns list with values of c-bytevector cbv read into it.
Example:
(define cbv (list->array '(1 2 3 4) 'int))
(array->list cbv 'int 4)
;> (1 2 3 4)
(vector->array vec type [cbv])
Returns c-bytevector with vector vec values written into it.
The vector values must be/fit into C type type. If c-bytevector cbv is given, values are written into that. If not, a new c-bytevector is allocated.
(array->vector cbv type size [vec])
Returns list with values of c-bytevector cbv read into it. If vector vec is given values are read into that, if not a new vector is returned.
Structs
Calculates offsets of struct members for given struct member names and types. Argument must be a list of items of lists each containing two items. Name of member and it's type. Returns an association list.
Type must be C type (symbol) but name can be quite freely chosen.
Example:
(struct-offsets '((a int) (b int) (c float) (d pointer)))
;> ((a 0) (b 4) (c 8) (d 16))
(struct-offsets '(("a" int) ("b" int) ("c" float) ("d" pointer)))
;> (("a" 0) ("b" 4) ("c" 8) ("d" 16))
(define color-cbv (make-c-bytevector (* (c-type-size 'int) 4)))
(define color-offsets (struct-offsets '((r int) (g int) (b int) (a int))))
(c-bytevector-set! color-cbv 'int (cadr (assoc 'g color-offsets)) 100)
(c-bytevector-ref color-cbv 'int (cadr (assoc 'g color-offsets)))
;> 100
(struct-member-offset name offsets)
Utility function to return the offset of member name from given offsets
(struct-member-type name members)
Utility function to return the type of member name from given members
Calculates the size of struct from given struct members.
(struct->list members cbv [offsets])
Read all values from c-bytevector cbv using members. members is list of member names and types. Optionally offsets can be given, if not they are calculated from members each time procedure is called.
Example:
(define color-cbv (make-c-bytevector (* (c-type-size 'int) 4))
(define color-members '((r int) (g int) (b int) (a int)))
(define color-offsets (struct-offsets color-members))
(c-bytevector-set! color-cbv 'int (cadr (assq 'r color-offsets)) 1)
(c-bytevector-set! color-cbv 'int (cadr (assq 'g color-offsets)) 2)
(c-bytevector-set! color-cbv 'int (cadr (assq 'b color-offsets)) 3)
(c-bytevector-set! color-cbv 'int (cadr (assq 'a color-offsets)) 4)
(struct->list color-members color-cbv)
;> '((r 1) (g 2) (b 3) (a 4))
(struct->list color-members color-cbv color-offsets)
;> '((r 1) (g 2) (b 3) (a 4))
(list->struct members lst [offsets])
Write all values from list lst using members. members is list of member names and types. Optionally offsets can be given, if not they are calculated from members each time procedure is called.
Example:
(define color-members '((r int) (g int) (b int) (a int)))
(define color-offsets (struct-offsets color-members))
(define color-cbv (list->struct color-members '(4 3 2 1)))
(struct->list color-members color-cbv)
;> '((r 4) (g 3) (b 2) (a 1))
(struct->list color-members color-cbv color-offsets)
;> '((r 4) (g 3) (b 2) (a 1))
Callbacks
(define-c-callback scheme-name return-type argument-types procedure)
Defines a new Sceme function to be used as callback to C code.
Takes scheme-name to bind the Scheme procedure to, return-type, argument-types and procedure, as in place lambda!.
(pointer->c-bytevector pointer)
This procedure can be used inside the callback to make c-bytevector out of C pointer pointer.
Environment variables
To add more paths to where foreign c looks for libraries set FOREIGN_C_LOAD_PATH to paths separated by ; on windows, and : on other operating systems.
Roadmap
- 1.0.0
- [x] Loading C libraries
- [x] Calling C functions
- [x] c-bytevector (C pointer) handling
- [x] C pointer to string and back conversions
- [x] Passing pointers by address
- [ ] Very basic struct handling
- [ ] Very basic array handling
- [ ] c-bytevector copying
- [ ] Good documentation
- 1.N.0
- Optional functionality, some implementations will support some wont
- Callbacks
- Optional functionality, some implementations will support some wont
Implementation support being worked on
- Capyscheme
- R6RS
- R7RS
- Cyclone
- R7RS
- Gambit
- R7RS
- Gauche
- R7RS
- Gerbil
- R7RS
- Guile
- R6RS
- R7RS
- Mit-Scheme
- R7RS
- Mosh
- R6RS
- Racket
- R6RS
- Unable to run tests as it cant find SRFI-64
- R6RS
- Larceny
- R6RS
- R7RS
- Ypsilon
- R6RS
- Problems with akku-r7rs
- R6RS
Adding support for new implementation
- Make the library files foreign/c/YOURSCHEME-primitives.scm
- Include it in cond expand in foreign/c.sld
Implementation specific code
SCHEME-primitives.scm must implement:
- shared-object-load
- define-c-procedure
- c-u8-ref
- c-u8-set!
- c-pointer-ref
- c-pointer-set!
- c-null
- c-null?
YOURSCHEME-primitives.scm optionally can implement:
- define-c-callback
If there is no support for callbacks then this stub must be there:
(define-syntax define-c-callback
(syntax-rules ()
((_ scheme-name return-type argument-types procedure)
(define scheme-name
(error "define-c-callback not yet supported on YOURSCHEME")))))
Run R7RS tests with:
make SCHEME=YOURSCHEME all install test
and R6RS tests with:
make SCHEME=YOURSCHEME test-r6rs
Notes
- Do not cond-expand inside the arguments, that might lead to problems on some implementations.
- Do not store member names or types in variables, that might lead to problems on some implementations.
- Pass the members using quote
- As '(...) and not (list ...)