How does one interface with a C enum using Haskell and FFI?
Let's say charm.c has an enum key
and a function get_key()
that returns a key
type value.
How can I expose a corresponding Haskell Key
record and function getKey :: IO Key
?
And how can I do this without manually specifying how every singl开发者_开发百科e enum value maps to a Haskell value?
For @KevinReid, here's an example of how to do this with c2hs.
Given the enum key
in the file charm.h
(I have no idea what's in the enum, so I just filled in a few values)
typedef enum
{
PLAIN_KEY = 0,
SPECIAL_KEY = 1,
NO_KEY = 2
}
key;
key get_key();
You can use c2hs's enum hook like this:
{#enum key as Key {underscoreToCase} deriving (Eq, Show)#}
To bind to a function, you can use either call
or fun
. call
is simpler, but doesn't do any marshaling. Here are examples of both. The ffi-wrapped get_key
will return a CInt, so you need to either manually marshal it (if using call
) or specify the marshaller (if using fun
). c2hs doesn't include enum marshallers so I've written my own here:
module Interface where -- file Interface.chs
{#enum key as Key {underscoreToCase} deriving (Eq, Show)#}
getKey = cIntToEnum `fmap` {#call get_key #}
{#fun get_key as getKey2 { } -> `Key' cIntToEnum #}
cIntToEnum :: Enum a => CInt -> a
cIntToEnum = toEnum . cIntConv
C2hs will generate the following Haskell from this (slightly cleaned up):
data Key = PlainKey
| SpecialKey
| NoKey
deriving (Eq,Show)
instance Enum Key where
fromEnum PlainKey = 0
fromEnum SpecialKey = 1
fromEnum NoKey = 2
toEnum 0 = PlainKey
toEnum 1 = SpecialKey
toEnum 2 = NoKey
toEnum unmatched = error ("Key.toEnum: Cannot match " ++ show unmatched)
getKey = cIntToEnum `fmap` get_key
getKey2 :: IO (Key)
getKey2 =
getKey2'_ >>= \res ->
let {res' = cIntToEnum res} in
return (res')
cIntToEnum :: Enum a => CInt -> a
cIntToEnum = toEnum . cIntConv
foreign import ccall safe "foo.chs.h get_key"
get_key :: (IO CInt)
foreign import ccall safe "foo.chs.h get_key"
getKey2'_ :: (IO CInt)
精彩评论