/chicken_binding

Learning how to create Chicken bindings to C libraries

Primary LanguageSchemeMIT LicenseMIT

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:

  1. Include headers
(foreign-declare "#include \"bindme.h\"")
  1. 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")

Simple bindings with foreign-lambda*

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)))))