You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 18:45:47