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!\n");
*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!\n");
*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.