Writting chicken scheme binding
Learning how to create Chicken bindings to C libraries.
Reading list
Writting binding for C library
I'm using my own example library library to control whole codebase here and make it as simple as possible. C
sources are in c_src
and include
directories.
Also there is Makefile
to check that everything is ok, so you can also check it to find exact commands to build sources.
Simple bindings
Building sources basics
You can check Makefile
to find out exact commands.
Some gotchas for building:
-I<directory>
-directory
must be written without space- You should include files when compile, linking - you may be done without includes
csc -c <filename.scm>
will produce only<filename.o>
csc <filename.scm> -o
will produce executable<filename>
, by default first filename will be used as pattern
Simplest binding
echo_str
just prints passed string:
void echo_str(const char *str){
printf("echo_str: %s\n", str);
}
So to call it you should do two things:
- Include headers
(foreign-declare "#include \"bindme.h\"")
- Declare foreign-lambda binding in your scheme code
(define echo-str
(foreign-lambda void echo_str c-string))
Types declaration
Use foreign types specifiers page to declare types in different bindings. Also make sure that you've scrolled it down to map of foreign types to C types it's usuall very helpfull when you starting.
Some gotchas:
- If you need some type with qualifiers (like:
const
) you should declare it like this(const TYPE)
:
;; non-const version
(foreign-lambda void echo_str c-string)
;; const version
(foreign-lambda void echo_str (const c-string))
Type conversions
Sometimes you cannot just pass scheme type to C function and you need to convert it type. You have several options how to do this:
- Simplest way is to convert type by writting C code and using foreign-lambda*
(define echo-str2
(foreign-safe-lambda* void (((const c-string) str))
"echo_str2(&str);"))
- You may define some helpers an use foreign-primitive, it would allocate it on stack, so be aware
(define str-to-pointer
(lambda (str)
((foreign-primitive (c-pointer c-string) ((c-string str)) "C_return(&str);") str)))
foreign-lambda
vs foreign-lambda*
You should use foreign-lambda
when you don't need any additional convertational steps to call external code. In foreign-lambda*
you may write additional pieces of C code when you cannot do direct call.
Working with typedefs and structs
Wiki article about how to work with structs.
We're starting with following definition:
typedef struct {
unsigned int count;
const char *str;
} word_count;
void echo_struct(const word_count *wk);
So echo_struct
should print word_count->str
word_count->count
times.
foreigners
and define-foreign-record-type
At first we're using define-foreign-record-type. You can find quite clear usage example in xtypes-egg source code.
You have to add imports:
(import foreign)
(import foreigners)
Function definition with word_count
type as first parameter.
(define echo-struct-c
(foreign-lambda void echo_struct word_count))
And your record type helpers will look like that:
;; define type
(define-foreign-record-type (word_count "word_count")
(constructor: %make-word-count) ;; as I understand it define `malloc(word_count)` function
;; and bind it to `%make-word-count` name
(destructor: %free-word-count) ;; binding `free(word_count *)` function to `%free-word-count`
(unsigned-integer count word_count-count word_count-count-set!)
(c-string str word_count-str word_count-str-set!))
;; this function used to construct foreign-type when calling `echo-struct-c`
(define (make-word-count count str)
(let ((r (%make-word-count)))
(set-finalizer! r %free-word-count)
(word_count-count-set! r count)
(word_count-str-set! r str)
r))
Finally add some scheme function definition:
(define echo-struct
(lambda (count str)
(echo-struct-c (make-word-count count str))))
(echo-struct 4 "hello scheme")
foreign-lambda*
Simple bindings with Binding in previous section require quite complex definition and additional egg. You may write all these things just straightforward with foreign-lambda*
code::
(define echo-struct-stack
(foreign-lambda* void ((unsigned-integer count) (c-string str))
"word_count wk = {count, str};
echo_struct(&wk);"))
(define echo-struct-malloc
(foreign-lambda* void ((unsigned-integer count) (c-string str))
#<<END
word_count *wk = malloc(sizeof(word_count));
wk->count = count;
wk->str = str;
echo_struct(wk);
free(wk);
END
))
Use locations
You may use locations to get pointers in scheme code. Thus you need only fill your structure with some foreign-lambda*
since I cannot find good way to use [INIT]
part to initialize location in let-location
macro.
;; inplace write data to structure
(define write-wk!
(foreign-lambda* void (((c-pointer "word_count") wk) (integer count) (c-string str))
"wk->count = count;
wk->str = str;"))
(define echo-struct-locations
(lambda (count str)
(let-location ((raw-wk (c-pointer "word_count"))) ;; <- C_alloc(sizeof(word_count))
(let ((wk (location raw-wk))) ;; &raw-wk
(write-wk! wk count str)
(echo-struct-c wk)))))