使用静态链接将C++库与Haskell库相结合

12
设置:我有一个Haskell库HLib,用于提高效率调用C/C++后端CLib。后端非常小且专门用于与HLib一起使用。CLib的接口将仅通过HLib公开;从测试/基准测试/第三方库的角度来看,HLib应该是纯Haskell的。这意味着在cabal文件章节中,例如HLib测试,不应引用-lCLib、libCLib等内容,只需在build-depends上依赖HLib,并且可执行文件不需要查找动态CLib库。我需要能够构建和运行HLib和第三方库中的所有可执行文件,并进行开发时的cabal repl。

原本,CLib 是用纯C编写的。Cabal支持这种情况,我可以通过在 cabal 文件中使用 include-dirsc-sourcesincludes字段以与上述方式精确地将 CLib 集成到 HLib 中。

CLib 已经发展成为一个 C++ 库,我无法轻松地让 cabal 集成它。相反,我采用了自定义构建和 Setup.hs 的 makefile,类似于 this。您可以在 here1,2 中看到此方法的简单示例。

在这个例子中,我无法在HLib中运行cabal repl,因为“不支持加载档案”。这实际上意味着我需要一个动态的C++库,这很容易创建(在CLib的makefile中有一行注释来完成它)。然而,如果我确实创建了动态C++库,则HLib的测试在运行时会由于“没有此文件或目录libclib.so”而失败。这很糟糕(除了崩溃之外),因为测试可执行文件链接到了动态库,这不是我想要的。
具体来说,HLib和SimpleLib的测试都应该通过,并且我应该能够在hlib和simplelib目录中运行cabal repl。

我尝试过其他方法:这个答案这个答案(我无法编译),这个,以及阅读文档(导致“重定位”错误)。

目前我使用的是GHC-7.10.3,但如果在8.0中更容易解决,那也可以。

[1] 简化自lol/challenges

[2]下载并运行./sandbox-init。 这将构建HLib(该库会隐式地构建CLib和依赖于HLib的Haskell库SimpleLib)。


@n.m.已更新,包含一个示例 - crockeea
@n.m. C版本不同的地方在于它是纯C。如果我没记错,问题在于GHC尝试将头文件编译为纯C文件,并在其中看到C++代码时抛出错误。 - crockeea
“where the path is relative from” -- 从被链接的文件中。 “and throws an error when it sees C++ code in them” -- 是的,在头文件中不要放置 GHC 可以看到的 C++ 代码,否则会抛出错误。如果您需要在这些文件中使用 C++ 代码,请像任何其他应支持 C 和 C++ 的头文件一样,使用 #ifdef __cplusplus 进行隔离。如果您能给我一个纯 C 的工作示例,我认为我将能够在不太费力的情况下将其转换为 C++。 - n. m.
虽然我不确定为什么我的头文件需要支持C和C++(而不仅仅是C ++),但#ifdef确实使cabal configure步骤顺利进行。这使我能够使用cabal使用此处的说明链接C ++。不幸的是,对于更复杂的示例,我仍然无法运行cabal repl。这可能是GHC或cabal的错误,但我仍在努力解决。更长的解释和更多示例请参见存储库(请参阅README)。 - crockeea
我刚试图构建您的repo,尽管该符号明确定义了,但是出现了一个类静态变量的未定义符号错误。我认为这是GHCi链接器中的一个bug。我已经用文件静态变量以及两个访问函数替换了类静态变量,现在cabal repl可以工作了。如果您可以通过重新排列C++文件来修复这个问题,那么这更像是一个链接器错误。它们的顺序不应该影响结果。 - n. m.
显示剩余9条评论
2个回答

8
一旦你掌握了一些技巧,将C或C++库与Haskell库一起使用就很容易。我从这篇文章中获取了核心内容article,尽管它似乎过于复杂化了。您可以使用cabal(当前版本为1.25)与Simple构建类型(即无特殊Setup.hs),无需makefile和像c2hs这样的外部工具。
要包含来自纯C库的符号:
  1. 在您的cabal文件中,添加Include-dirs:relative/path/to/headers/Includes:relative/path/to/myheader.h
  2. 添加C-sources:relative/path/to/csources/c1.c,relative/path/to/csources/c2.c,等等
对于C ++,还有一些额外的细节:
  1. 您可以将 .cpp 文件添加到 cabal 文件中的 C-sources 字段中。
  2. 在 Haskell 需要访问的所有函数中,添加 extern "C" 以避免名称混淆。
  3. 将头文件中的所有非纯 C 代码用 #ifdef __cplusplus ... #endif 包围起来(请参见 n.m. 的回答)。
  4. 如果使用标准的 C++ 库,则需要将 extra-libraries: stdc++ 添加到 cabal 文件中,并使用 ghc-options: -pgmlg++ 链接 g++
  5. 如果您想要动态链接(即 cabal repl)正常工作,可能需要稍微调整在 cabal 文件中列出 .c(pp) 文件的顺序。有关更多信息,请参见 this ticket

就是这样!你可以在这里看到一个完整的工作示例,它可以与stackcabal一起使用。


4

GHC无法真正理解C++头文件,它需要纯C代码。C++头文件提供C接口的常见方法是使用#ifdef __cplusplus将C++部分隔离开来,例如:

#ifdef __cplusplus
extern "C" {         // C compilers and various C-based FFIs don't like this
#endif

void foo();

#ifdef __cplusplus
}
#endif

此外,GHCi历史上与链接C++代码存在问题。例如,在某个时候,它无法理解弱符号(通常由编译器与内联函数和模板实例化一起生成)。您可能会遇到这些问题之一。我建议向GHC团队提交错误报告。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接