Haskell FFI绑定写入指针的C函数及pipe调用示例求助
Haskell FFI 绑定带输出缓冲区的C函数(以pipe系统调用为例)
核心思路
Haskell虽无可变变量,但在FFI的IO monad中可通过分配可写内存缓冲区让C函数写入数据,之后从这块内存读取结果。关键用Foreign.Marshal模块的alloca系列函数管理内存,自动处理分配与释放,不用手动操作。
示例:绑定pipe系统调用
pipe的C原型是:int pipe(int pipefd[2]);,会把创建的读写文件描述符写入传入的数组缓冲区。
1. 导入必要模块
import Foreign.C.Types (CInt(..)) import Foreign.Ptr (Ptr) import Foreign.Marshal.Array (allocaArray, peekArray)
2. 绑定C函数
直接绑定底层pipe函数:
foreign import ccall "pipe" c_pipe :: Ptr CInt -> IO CInt
3. 封装成Haskell风格函数
用allocaArray分配容纳2个CInt的缓冲区,调用C函数后读取内容:
pipe :: IO (CInt, CInt) pipe = do -- 分配可写内存:能存2个CInt的数组 allocaArray 2 $ \fdPtr -> do res <- c_pipe fdPtr -- 检查系统调用是否成功(返回-1表示失败) if res == -1 then error "pipe系统调用失败" -- 实际项目建议用Either/ExceptT做错误处理 else do -- 从缓冲区读取两个文件描述符 [readFd, writeFd] <- peekArray 2 fdPtr return (readFd, writeFd)
通用缓冲区处理示例
如果绑定写入字节缓冲区的C函数(比如void fill_buffer(char* buf, int len)),可这样实现:
绑定与封装
import Foreign.C.Types (CChar(..), CInt(..)) import Foreign.Ptr (Ptr) import Foreign.Marshal.Alloc (allocaBytes) import Foreign.Storable (peekByteOff) import Data.Word (Word8) -- 绑定C函数 foreign import ccall "fill_buffer" c_fill_buffer :: Ptr CChar -> CInt -> IO () -- Haskell封装函数:指定缓冲区长度,返回读取到的字节列表 fillBuffer :: Int -> IO [Word8] fillBuffer len = do -- 分配指定字节数的可写内存 allocaBytes len $ \buf -> do c_fill_buffer buf (fromIntegral len) -- 逐个读取缓冲区中的字节,转成Haskell的Word8类型 mapM (\offset -> peekByteOff buf offset >>= return . fromIntegral) [0 .. len - 1]
关键要点总结
- 绝不能用常量指针传递给需要写入的C函数,必须用
alloca/allocaArray/allocaBytes在IO中分配可写内存。 - 用
peek(单元素)、peekArray(同类型数组)、peekByteOff(指定偏移量)读取C写入缓冲区的数据。 alloca系列函数遵循RAII原则:内存会在传入的回调函数执行完毕后自动释放,无需手动调用free。
内容的提问来源于stack exchange,提问作者Nikolai Savulkin
相关产品推荐
相关产品推荐

