retroj.net

Chicken: Define-Foreign-Enum-Type, Typedef, and Pointers

In the course of writing Chicken Scheme bindings for a certain C library, I came across a tricky problem: how do you write a binding for a function that takes a pointer to a typedef enum type? Here is an example of the situation I mean:

typedef enum { Pain, Suffering, Fear } Garmonbozia; void CreepyDream (Garmonbozia *x) { printf("give me back my garmonbozia!

"); *x = Fear; }

We have a type, Garmonbozia , created by a typedef enum . We have a function, CreepyDream , which takes a pointer to a Garmonbozia as its argument, for use as an out parameter. How shall we go about getting at the value of that out parameter in scheme? We want to use symbolic names for the enum values as well, so we will use define-foreign-enum-type from foreigners to define a foreign type 'garmonbozia', foreign variables for its values, and procedures to convert to and from scheme types.

We are careful to note that the type of Garmonbozia is not enum Garmonbozia because of the typedef — it is just Garmonbozia , and since we want to be able to convert its values to and from scheme types, we need to tell Chicken what it is in terms of primitive C types. For this reason, we tell Chicken that our foreign type is an int type. Saying (enum "Garmonbozia") would be wrong, and saying "Garmonbozia" doesn't given Chicken enough information to convert between C and scheme types.

We used capitalized identifiers in the C code, and we will use lower case for scheme symbols, to aid readability of the examples.

(define-foreign-enum-type (garmonbozia int) (garmonbozia->int int->garmonbozia) (pain Pain) (suffering Suffering) (fear Fear))

Now the question is, how do we create a binding for CreepyDream ? First, let's get some boilerplate in place. Since we need to pass a pointer to the foreign function, that means using a locative on the scheme side. Our function creepy-dream will take no arguments, and it will return whatever garmonbozia value, as a symbol, was assigned to the out parameter by CreepyDream. The main question is, what form should the foreign function itself take? Well, since we have defined a foreign type garmonbozia with define-foreign-enum-type above, our first guess is to try passing a pointer to that type:

(define (creepy-dream) (let-location ((a int)) ((foreign-lambda void CreepyDream (c-pointer garmonbozia)) (location a)) (int->garmonbozia a)))

This turns out to be wrong. When we try to compile, we get a warning:

enumpointer.c: In function ‘stub12’: enumpointer.c:45:1: warning: passing argument 1 of ‘CreepyDream’ from incompatible pointer type [enabled by default] enumpointer.c:22:6: note: expected ‘enum Garmonbozia *’ but argument is of type ‘int *’

Our foreign type garmonbozia was defined as int , and the compiler is telling us that to pass a pointer of this type, we will need an explicit cast. Okay, we can do that with foreign-lambda* and a little bit of C code.

(define (creepy-dream) (let-location ((a int)) ((foreign-lambda* void (((c-pointer int) a)) "CreepyDream((Garmonbozia *) a);") (location a)) (int->garmonbozia a)))

This actually works, but it turns out to be more verbose than needed, because further investigation yielded another variation on our first attempt that solves the problem more simply.

In our first attempt, we tried the foreign type (c-pointer garmonbozia) and got the warning about incompatible pointer type, because garmonbozia was defined as an int type. What we can do instead with the c-pointer form is give a C type as string: (c-pointer "Garmonbozia") . This refers to the C type Garmonbozia , rather than the scheme foreign type garmonbozia .

(define (creepy-dream) (let-location ((a int)) ((foreign-lambda void CreepyDream (c-pointer "Garmonbozia")) (location a)) (int->garmonbozia a)))

Our foreign-lambda now has a correct type signature, and the program compiles without warning, and creepy-dream returns the expected value ('fear) when called. Here is the complete, working code.

(import chicken scheme foreign foreigners) #> typedef enum { Pain, Suffering, Fear } Garmonbozia; void CreepyDream (Garmonbozia *x) { printf("give me back my garmonbozia!

"); *x = Fear; } <# (define-foreign-enum-type (garmonbozia int) (garmonbozia->int int->garmonbozia) (pain Pain) (suffering Suffering) (fear Fear)) (define (creepy-dream) (let-location ((a int)) ((foreign-lambda void CreepyDream (c-pointer "Garmonbozia")) (location a)) (int->garmonbozia a))) (print (creepy-dream))

Expected output:

give me back my garmonbozia! fear

With that I would like to acknowledge and thank zbigniew of #chicken for much assistance with this problem, and to wish for you my readers a reduction of your own garmonbozia in your binding-writing efforts.