静态 link C++ 库和 Haskell 库

Statically link C++ library with a Haskell library

设置:我有一个 Haskell 库 HLib,它调用 C/C++ 后端 CLib 以提高效率。后端很小,专门用于 HLibCLib 的接口将 只有 通过 HLib 公开; HLib 测试、HLib 基准测试和依赖于 HLib 的第三方库不会对 CLib 进行直接 FFI 调用。从test/benchmark/3rd方库的角度来看,HLib应该是纯粹的Haskell。这意味着在 cabal 文件部分中,例如 HLib 测试,不应该有对 -lCLiblibCLib 等的引用,只有 HLib 上的 build-depends ],并且可执行文件不需要寻找动态 CLib 库。我需要能够构建 运行 HLib 和第三方库中的所有可执行文件,以及 运行 cabal repl 用于开发。

最初,CLib 是用纯 C 语言编写的。Cabal 支持这种情况,我可以通过使用 [= cabal 文件中的 33=、c-sourcesincludes 字段。

CLib 已经演变成一个 C++ 库,我不知道如何让 cabal 轻松集成。相反,我求助于带有自定义构建和 Setup.hs 的 makefile,例如 this. You can see a small example of this method here1,2.

在那个例子中,我不能在 HLib 中 运行 cabal repl 因为 "Loading archives not supported"。这实际上意味着我需要一个动态 C++ 库,它很容易创建(CLib makefile 中有一个注释行可以创建)。但是,如果我创建动态 C++ 库,HLib 的测试会在 运行 时因 "no such file or directory libclib.so" 而失败。这很糟糕(除了崩溃之外),因为测试可执行文件链接到动态库,这不是我想要的。

具体来说,HLibSimpleLib 的测试应该都通过了,我应该能够 运行 cabal repl hlibsimplelib 个目录。

我尝试过的其他事情:this answer, this answer (which I can't get to compile), this, and reading the docs(导致 "relocation" 错误)。

我目前正在使用 GHC-7.10.3,不过如果这在 8.0 中明显更容易,那很好。

[1] 简化自 lol/challenges.

[2]下载并运行./sandbox-init。这将构建 HLib(隐式构建 CLibSimpleLib,这是一个依赖于 HLib.[=79 的 Haskell 库=]

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 团队提交错误报告。

一旦掌握了一些技巧,将 C 或 C++ 库与 Haskell 库结合起来就很简单了。

我从这个 article 中得到了核心,尽管它似乎使事情变得过于复杂。您可以使用具有 Simple 构建类型(即没有特殊 Setup.hs)的 cabal(当前为 1.25),没有 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, etc.

C++ 有几个额外的位:

  1. 您可以将 .cpp 文件添加到 cabal 文件中的 C-sources 字段。
  2. 在 Haskell 需要访问的 .cpp 文件中的所有函数上,添加 extern "C" 以避免名称混淆。
  3. #ifdef __cplusplus ... #endif 包围头文件中的所有非纯 C 代码(参见 n.m。的回答)。
  4. 如果您使用标准 C++ 库,则需要将 extra-libraries: stdc++ 添加到您的 cabal 文件中,并使用 ghc-options: -pgmlg++ 添加 link 和 g++
  5. 如果您想要动态 [=65],您可能需要 fiddle 使用 命令 列出 cabal 文件中的 .c(pp) 文件=]ing(即 cabal repl)工作。有关详细信息,请参阅 this ticket

就是这样!您可以看到一个完整的工作示例 here,它适用于 stackcabal