2011-07-14 56 views
17

让我们假设charm.c具有枚举key和函数get_key(),该函数返回key类型的值。如何使用Haskell和FFI与C enum进行交互?

如何才能公开相应的Haskell Key记录和功能getKey :: IO Key

如何在不手动指定每个枚举值如何映射到Haskell值的情况下执行此操作?

+0

hsc2hs有这个宏,但如果可能的话,我宁愿只使用vanilla Haskell。 – mcandre

+5

你不能在香草Haskell中做到这一点,而无需手工编写。 hsc2hs和c2hs都具有枚举挂钩,绑定-dsl使用CPP宏。这些将为你自动生成Haskell类型和Enum实例,但它不是Haskell的香草。当然你可以用例如c2hs,运行预处理程序,然后运送生成的Haskell文件,但这往往会导致问题。对齐和ptr大小。 –

+9

您需要重量级工具的原因是枚举常量是C中的编译时构造 - 它们不是导出的符号,可通过链接到库来检索 - 因此,发现枚举值需要分析C源文本。 –

回答

6

对于@KevinReid,这里是一个如何使用c2hs来做到这一点的例子。

鉴于文件charm.h在枚举key(我不知道什么是在枚举,所以我只是填写一些值)

typedef enum 
    { 
    PLAIN_KEY = 0, 
    SPECIAL_KEY = 1, 
    NO_KEY = 2 
    } 
key; 

key get_key(); 

您可以使用c2hs的枚举挂钩这样的:

{#enum key as Key {underscoreToCase} deriving (Eq, Show)#} 

要绑定到一个函数,可以使用callfuncall更简单,但不做任何编组。这里是两个例子。 ffi包装的get_key将返回一个CInt,因此您需要手动封送它(如果使用call)或指定编组器(如果使用fun)。 c2hs不包括枚举marshallers所以我写我自己在这里:

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会产生从这个以下哈斯克尔(略清理):

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

似乎像使用enum钩子定义的枚举现在有一个默认编组器:https://github.com/haskell/c2hs/wiki/Implementation%20of%20Haskell%20Binding%20Modules。 – HaskellElephant