Haskell FFI:传递并返回ByteStrings的正确方法

时间:2018-05-23 06:50:18

标签: c haskell garbage-collection ffi bytestring

设置

假设您有一个典型的C API,其中分配了一些结构,然后初始化(分配内存,创建句柄等),然后使用各种函数进行处理(转换,查询等),最后再次释放。即标准的C风格的OO框架,其中struct是'object',函数是'member functions'。

更具体地说,让API适用于字节串。例如,这可能是一个压缩库。

在Haskell中,Data.ByteString.ByteString,在最低级别,只是“GC堆中的固定内存”(用mallocPlainForeignPtrBytes分配),指针是{{1的第一个参数构造函数PS。可以使用Data.ByteString.Internal.toForeignPtr获取ByteString的基础内存。到目前为止一切都很好。

传递ByteString

让Haskell管理C API运行的ByteString很方便。在C中,我们可能有一个struct和init函数,如下所示:

ByteString

typedef struct { uint8_t* buf; size_t buflen; } API_t; void API_init_as_writer(API_t* p, uint8_t* buf, size_t buflen); buf持有Haskell buflen。该对象成为“作家”。 Haskell绑定看起来像这样:

ByteString

使用toForeignPtrwithForeignPtr获取 data API -- empty decl (needs EmptyDataDecls extension) foreign import ccall unsafe "API.h API_init_as_writer" init :: Ptr API -> Ptr Word8 -> Int -> IO ()

返回(传递)Ptr Word8

同样,我们可以让对象(struct)成为'读者'并使用ByteStringByteString创建mallocPlainForeignPtrBytes,如上所述。

垃圾收集阻碍了!

问题在于,在调用API_init_ *函数之后,垃圾收集器不知道正在使用PS(通过保存指向GC堆内存的指针的结构)。在“作家”的情况下:

ByteStrings

以及'读者'的情况:

    writer <- initWriter 1024     -- inits with buflen of 1024 via mallocPlainForeignPtrBytes
    writeSomething writer
    writeSomethingElse writer
    ...
    bs <- getByteString writer    -- construct ByteString with PS
    writeFile "Some.file" bs

所有这些都发生在IO monad中。

显然,这不起作用。在 bs <- ByteString.readFile("Some.file") reader <- initReader bs info1 <- readSomething reader info2 <- readSomethingElse reader ByteString之后,initWriter可能会收集垃圾,而initReaderwriteSomething函数将继续使用垃圾回收内存。

尽管存在任何GC问题,但就像{mon}中的readSomething已经“不安全”一样,这些API ByteString操作也不应该在IO monad中,因为实际上没有任何IO或与ByteString发生的互动。

所以问题是:如何在Haskell中正确构建这个结构?

This所以问题会导致使用RealWorld的暗示,这不是很好。

我的预感是,这需要实现功能。我猜这些OO类型API的顺序特性(alloc,init,操作,操作,...,免费)需要引入除IO monad之外的monad。但我不太清楚如何正确设置它。当我查看数据库绑定时,我只看到IO monad。例如,db_getBerkeleyDB binding函数返回IORef - 不会出现GC问题,因为IO (Maybe ByteString)是查询的结果。我现在有点困惑。我想我正在达到OO和FP之间的摩擦点。或许我完全以错误的方式看待这个。

更新1

正如@Alec指出的那样,使用touchForeignPtr可以缓解(解决?)GC问题。尽管如此,我仍然非常倾向于将其从IO monad中移除,以获得更清晰的界面。

0 个答案:

没有答案