在Haskell中使用C函数的参数

时间:2014-05-26 12:47:16

标签: haskell ffi out-parameters

我的功能可以完成以下任务。

CStructType* foo;
int result = someFunctionThatAllocsFooAsOutput(&foo);

在阅读有关这些内容之后,首先想到的是:

-- Standard binding done elsewhere
data CStructType = CStructType {
  -- Stuff here
} deriving (Eq, Show)
foreign import ccall "some_c_header.h someFunctionThatAllocsFooAsOutput" someFunction :: CStructType Ptr -> IO CInt

-- Call function binding from somewhere
main :: IO ()
main = do
  result <- alloca $ \cstruct -> someFunction cstruct
  doOtherThings cstruct result

但是,我不知道这是有效的,或者如果它是有效的,如果它是最佳做法。

我对Haskell来说相当新,所以如果这篇帖子中有任何重大愚蠢,我会道歉。

1 个答案:

答案 0 :(得分:1)

我认为您的代码不太正确。首先考虑C语言中发生的事情:在栈上分配一个指向CStructType的指针,然后将指针作为参数传递给someFunctionThatAllocsFooAsOutput。该函数负责分配新的CStructType(可能通过malloc),并且生成的新指针存储在您在堆栈上创建的指针中。

要在Haskell中复制它,重要的一点是,不是来回编组,而是需要保留指向由someFunctionThatAllocsFooAsOutput分配的内存的指针,并将其传递给C函数,直到您准备好释放它。所以你可能想要做这样的事情(我没有尝试编译这个,但我认为它很接近):

newtype CStructPtr = CStructPtr (ForeignPtr CStructType)

foreign import ccall "some_c_header.h someFunctionThatAllocsFooAsOutput" someFunction :: Ptr (Ptr CStructType) -> IO CInt

-- we need a hook into the function to free a `CStructType`
foreign import ccall "some_c_header.h &freeFoo" freeFoo :: FunPtr (Ptr CStructType -> IO ())

allocFoo :: IO (CStructPtr, CInt)
allocFoo = alloca $ \ptrptr -> do
    result <- someFunction ptrptr
    cstructptr <- peek ptrptr
    fPtr <- newForeignPtr freeFoo cstructptr
    return (CStructPtr fPtr, result)

-- a useful unwrapper
withCStruct :: CStructPtr -> (Ptr CStructType -> IO a) -> IO a
withCStruct (CStructPtr fPtr) action = withForeignPtr fPtr action

doOtherThings :: CStructPtr -> CInt -> IO ()
doOtherThings ptr result = withCStruct ptr $ \cptr -> otherCFunction cptr result


main = do
    (cstruct,result) <- allocFoo
    doOtherThings cstruct result

有几点需要注意:

  1. 没有必要使用ForeignPtr s,您可以保留原始Ptr CStructType并直接将其传递给函数。这会变得相当不方便,因为您需要手动释放它,这通常涉及顶级bracket
  2. 即使someFunctionThatAllocsFooAsOutput涉及全局状态,指向其他malloc内存的指针或其他特技,此样式也是安全的。如果您对CStructType有更多了解,可能没有必要保留someFunctionThatAllocsFooAsOutput返回的确切内存,您可以更自由地将数据封送到Haskell。但是我没有足够的信息来假设这一点。
  3. 正如其他人所指出的那样,真实世界Haskell的FFI章节非常重要。我不认为它有一个使用输出参数的例子,这是一个遗憾的遗漏。