我认为您的代码不太正确。首先考虑 C 习惯用法中发生的情况:在堆栈上分配一个指向 CStructType 的指针,然后将指向该指针的指针作为参数传递给someFunctionThatAllocsFooAsOutput
. 该函数负责分配一个新的CStructType
(可能是通过malloc
),并将生成的新指针存储到您在堆栈上创建的指针中。
someFunctionThatAllocsFooAsOutput
要在 Haskell 中复制这一点,重要的一点是,您需要保留一个指向由分配的内存的指针并将其传递给 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
有几点需要注意:
- 不需要使用
ForeignPtr
s,您可以保留一个 rawPtr CStructType
并直接将其传递给函数。这会变得相当不方便,因为您需要手动释放它,这通常涉及顶级bracket
.
- 即使
someFunctionThatAllocsFooAsOutput
涉及全局状态、指向其他 malloc 内存的指针或其他技巧,这种风格也是安全的。如果您对 有更多了解CStructType
,则可能没有必要保留返回的确切内存,someFunctionThatAllocsFooAsOutput
并且您可以更自由地将数据编组到 Haskell。但是这里没有足够的信息让我假设。
正如其他人所指出的FFI
,Real World Haskell 的章节非常相关。我不认为它有一个使用输出参数的例子,这是一个不幸的遗漏。