Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Statically link C++ library with a Haskell library

Tags:

haskell

ghc

cabal

Setup: I have a Haskell library HLib which makes calls to a C/C++ backend CLib for efficiency. The backend is small and specialized for use with HLib. The interface to CLib will only be exposed through HLib; HLib tests, HLib benchmarks and third party libraries depending on HLib will not make direct FFI calls to CLib. From the test/benchmark/3rd party lib perspective, HLib should be appear purely Haskell. This means in the cabal file sections for, e.g., HLib tests, there should be no references to -lCLib, libCLib, etc, only a build-depends on HLib, and that executables should not need to look for a dynamic CLib library. I need to be able to build and run all executables in HLib and third-party libs, as well as run cabal repl for development.

Originally, CLib was written in pure C. Cabal has support for this case, and I can integrate CLib into HLib in precisely the manner described above by using include-dirs, c-sources, and includes fields in the cabal file.

CLib has evolved into a C++ library, which I couldn't figure out how to get cabal to integrate easily. Instead, I resorted to a makefile with custom build and Setup.hs, like this. You can see a small example of this method here1,2.

In that example, I can't run cabal repl in HLib because "Loading archives not supported". This really means I need a dynamic C++ library, which is simple enough to create (there's a commented line in the CLib makefile to do it). If I do make the dynamic C++ library, however, the test for HLib fails at runtime due to a "no such file or directory libclib.so". This is bad (in addition to the crash) because the test executable linked against the dynamic library, which is not what I want.

Concretely, the tests for HLib and SimpleLib should both pass, and I should be able to run cabal repl in both the hlib and simplelib directories.

Other things I've tried: this answer, this answer (which I can't get to compile), this, and reading the docs (results in "relocation" errors).

I'm using GHC-7.10.3 at the moment, though if this is significantly easier in 8.0, that's fine.

[1] Simplified from lol/challenges.

[2] Download and run ./sandbox-init. This builds HLib (which implicitly builds CLib, and SimpleLib, which is a Haskell library that depends on HLib.

like image 340
crockeea Avatar asked Jun 01 '16 15:06

crockeea


2 Answers

Including a C or C++ library with a Haskell library is trivial once you know a few tricks.

I got the core from this article, though it seems to overcomplicate things. You can use cabal (currently 1.25) with a Simple build type (i.e. no special Setup.hs), no makefile, and no external tools like c2hs.

To include symbols from a pure C library:

  1. In your cabal file, either add Include-dirs: relative/path/to/headers/ or Includes: relative/path/to/myheader.h.
  2. Add C-sources: relative/path/to/csources/c1.c, relative/path/to/csources/c2.c, etc.

There's a couple of extra bits for C++:

  1. You can add .cpp files to the C-sources field in the cabal file.
  2. On all functions in .cpp files that Haskell needs access to, add extern "C" to avoid name mangling.
  3. Surround all non-pure-C code in header files with #ifdef __cplusplus ... #endif (see n.m.'s answer).
  4. If you use the standard C++ library, you'll need to add extra-libraries: stdc++ to your cabal file, and link with g++ using ghc-options: -pgmlg++.
  5. You may have to fiddle a bit with the order that you list the .c(pp) files in the cabal file if you want dynamic linking (i.e. cabal repl) to work. See this ticket for more information.

That's it! You can see a complete working example here which works with both stack and cabal.

like image 81
crockeea Avatar answered Nov 10 '22 19:11

crockeea


GHC cannot really understand C++ header files. It needs pure C code. The common way for a C++ header file to provide C interface is to isolate away C++ parts with #ifdef __cplusplus, e.g.:

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

void foo();

#ifdef __cplusplus
}
#endif

In addition, GHCi is historically known to have problems with linking C++ code. For example, at one point it didn't understand weak symbols (often produced by the compiler in conjunction with inline functions and template instantiations). You might be seeing one of these problems. I'd recommend filing a bug report to the GHC team.

like image 36
n. 1.8e9-where's-my-share m. Avatar answered Nov 10 '22 20:11

n. 1.8e9-where's-my-share m.