将不同类型元素的列表传递给C函数

时间:2013-10-15 12:23:29

标签: haskell tuples ffi

我有一个用C编写的函数我想从Haskell程序调用。功能类型是:

foo :: Int -> Ptr a -> IO ()

它需要一个大小和一个指针,并将整个东西放在内存中。它旨在与混合类型一起使用。你可以把n个浮点数加上m bools等等(在C中)。

在Haskell中表示这种情况的最方便的方法是 - 在我看来 - 例如([a],[b])。但是,我需要整个事情适合Ptr a(它实际上是C中的空白*)。我可以尝试编写像([a],[b]) -> Ptr c这样的函数,但我需要一些帮助。期望的最终功能是:

withArrayLen magicArray foo

1 个答案:

答案 0 :(得分:1)

可以存储在内存中的东西是类Storable类型的实例(在Foreign.Storable中)。所以,考虑到原始的FFI原型

foreign import "foo" c_foo :: CInt -> Ptr a -> IO ()

你可以为同源列表写这样的东西:

homfoo :: Storable a => [a] -> IO ()
homfoo items = withArray items $ \ptr -> c_foo (fromIntegral len) ptr
    where len = length items * sizeOf (head items)

但是你已经说过这个函数适用于混合类型,所以我们需要某种类型约束的异构列表来处理好的Haskell包装器。这是一种方法:

{-# LANGUAGE GADTs #-}

data DynStorable where
    MkStorable :: Storable a => a -> DynStorable

foo :: [DynStorable] -> IO ()
foo items =
    let (requiredSize, offsets) = mapAccumL sizeFold 0 items in
    allocaBytes requiredSize $ \ptr -> do
        zipWithM
            (\offset (MkStorable x) -> pokeByteOff ptr offset x)
            offsets items
        c_foo (fromIntegral requiredSize) ptr
    where
    sizeFold offset (MkStorable x) =
        let unalignment = offset `mod` alignment x
            offset' = if unalignment /= 0
                then offset + alignment x - unalignment
                else offset
        in (offset' + sizeOf x, offset')

main :: IO ()
main = do
   foo [MkStorable (2 :: Int), MkStorable (3.0 :: Double), MkStorable True]

C函数无法区分收到的数据块中的项边界,但如果需要,不可能包含长度前缀或类型代码。