一般信息
我目前正在试验 Haskell 的 C->Haskell (C2HS) 接口生成器。乍一看,这真是太棒了,我extern C
在短短几个小时内就连接了一个相当复杂的 C++ 库(使用一个小的 -wrapper)。(而且我以前从未做过任何 FFI。)
只有一个问题:如何释放 C/C++ 库中分配的内存?我{#pointer ... foreign #}
在C2HS 文档中找到了,这看起来与我所追求的完全一样。由于我的 C-wrapper 将 C++ 库转换为具有引用透明性和功能接口的库,因此 Haskell 存储管理器应该能够为我完成艰苦的工作:-)。不幸的是,我无法让这个工作。为了更好地解释我的问题,我在 GitHub 上建立了一个小型演示项目,它与 C/C++ 库+包装器具有相同的属性,但没有开销。如您所见,该库与pure unsafe
FFI 一起使用是完全安全的。
演示项目
在 GitHub 上,我创建了一个小型演示项目,组织如下:
C 库
C 库非常简单且无用:您可以将整数传递给它,然后可以[0..n]
从库中获取尽可能多的整数(当前)。记住:这个库没用,只是一个演示。该接口也非常简单:该函数LTIData lti_new_data(int n)
将(在传递一个整数之后)返回某种不透明的对象,其中包含 C 库的已分配数据。该库还有两个访问器函数int lti_element_count(LTIData data)
,int lti_get_element(LTIData data, int n)
前者将返回元素的数量,后者将返回元素n
。啊,最后但并非最不重要的一点是,图书馆的用户应该在使用它之后释放不透明LTIData
的void lti_free_data(LTIData data)
.
低级 Haskell 绑定
低级 Haskell 绑定是使用 C2HS 设置的,您可以在
高级 Haskell API
为了好玩,我还设置了一种使用低级 API 绑定的高级 Haskell API和一个使用高级 API 的简单驱动程序。使用驱动程序和例如 valgrind 可以很容易地看到泄漏的内存(对于每个参数p_1, p_2, ..., p_n
,库都会进行\sum_{i = 1..n} 1 + p_i
分配;如下所示很容易观察到):
$ valgrind dist/build/TestHsLTI/TestHsLTI 100 2>&1 | grep -e allocs -e frees
==22647== total heap usage: 184 allocs, 74 frees, 148,119 bytes allocated
$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100 2>&1 | grep -e allocs -e frees
==22651== total heap usage: 292 allocs, 80 frees, 181,799 bytes allocated
$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100 100 2>&1 | grep -e allocs -e frees
==22655== total heap usage: 400 allocs, 86 frees, 215,479 bytes allocated
$ valgrind dist/build/TestHsLTI/TestHsLTI 100 100 100 100 2>&1 | grep -e allocs -e frees
==22659== total heap usage: 508 allocs, 92 frees, 249,159 bytes allocated
演示的当前状态
您应该能够通过简单地键入来克隆、编译和运行项目git clone https://github.com/weissi/c2hs-experiments.git && cd c2hs-experiments && cabal configure && cabal build && dist/build/TestHsLTI/TestHsLTI
那么又是什么问题呢?
问题是该项目只使用而不是使用 C2HS 的Foreign.Ptr
“托管”版本,我无法让它工作。在演示项目中,我还添加了一个文件,尝试使用这些外部指针,但它不起作用:-(。我非常努力地尝试,但没有成功。Foreign.ForeignPtr
{#pointer ... foreign #}
.chs
还有一件事我也不明白:如何使用 C2HS 告诉 GHC 如何释放图书馆的数据。演示项目的库提供了一个函数void lti_free_data(LTIData data)
,应该调用它来释放内存。但是GHC猜不到!?!如果 GHC 使用常规 a free()
,则并非所有内存都会被释放:-(。