12

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.

Community
  • 1
  • 1
crockeea
  • 21,651
  • 10
  • 48
  • 101
  • Can you provide a [mcve]? There are too many variables and too little concrete information to diagnose the problem. – n. m. could be an AI Jun 03 '16 at 18:39
  • @n.m. Updated with an [example](https://github.com/crockeea/CPPBindings). – crockeea Jun 04 '16 at 00:41
  • When running with shared libraries, the error "no such file or directory libclib.so" can be corrected by placing libclib.so in LD_LIBRARY_PATH. or specifying its installation directory in ld-options. `-rpath /path/to/libclib-dir` should do the trick. Not sure about integrating C++ code in a Haskell static library. I have never tried this. What's the exact problem with this? Do you have an example of successfully integrating C code, where integrating C++ code in the same manner doesn't work?: What error messages do you get in this case? – n. m. could be an AI Jun 04 '16 at 04:54
  • @n.m. Unless I can specify a *relative* path for the .so file, this is no good. It means that users of the library also have to set an absolute path particular to their system, which is too complicated. When cabal linked the pure-C version of `CLib`, I didn't have to set LD_LIBRARY_PATH or `ld-options` to get the dynamic libraries working; I'm looking for a similarly foolproof solution. I'll get an example with working C/failing C++ integration tomorrow. – crockeea Jun 04 '16 at 05:33
  • It is possible to specify a relative path too, see `$ORIGIN` in `man ld`. I'm sure the C-version of clib was somehow different in other respects because it doesn't matter to the dynamic loader which source language(s) were used to build the library. – n. m. could be an AI Jun 04 '16 at 05:35
  • @n.m. The question is where the path is relative from. It's unclear, which AFAICT is the reason that neither cabal nor stack support a relative "extra-lib-dirs". You can circumvent this either by using Setup.hs or by giving linker options with `optl`, but I couldn't figure out how to get cabal to find the library with either of those solutions. – crockeea Jun 04 '16 at 05:38
  • @n.m. The C version is different in that it is pure C. IIRC, the issue is that GHC tries to compile the header files as pure C files, and throws an error when it sees C++ code in them. – crockeea Jun 04 '16 at 05:39
  • "where the path is relative from" -- from the file being linked. "and throws an error when it sees C++ code in them" -- that's right, don't put C++ code in header files that GHC can see. If you need C++ code in these files, isolate it with `#ifdef __cplusplus` like any other header file that should support both C and C++. If you can give me a pure C working example, I think I will be able to convert it to C++ without too much pain. – n. m. could be an AI Jun 04 '16 at 05:44
  • @n.m. Though I'm not sure why my header file needs to support C and C++ (rather than just C++), the `#ifdef` does make the `cabal configure` step happy. This allows me to use cabal to link with C++ using instructions [here](http://blog.ezyang.com/2010/06/setting-up-cabal-the-ffi-and-c2hs/). Unfortunately, for more complicated examples, I still can't run `cabal repl`. It's possible this is a GHC or cabal bug, but I'm still working on it. A longer explanation and more examples are [at the repo](https://github.com/crockeea/CPPBindings) (see README). – crockeea Jun 04 '16 at 18:53
  • @n.m. Found the problem: the order in which I list the .cpp files in the cabal files matters. I'm not sure if this is a bug or not, what do you think? If you write a short answer about using `__cplusplus`, I'll give you the bounty, and add my own answer with a detailed example. – crockeea Jun 04 '16 at 21:44
  • I've just tried to build your repo, and got an undefined symbol error for a class-static variable, despite the symbol being clearly defined. I think this is a GHCi linker bug. I have replaced the class-static variable with a file-static one, plus two access functions, and now cabal repl works. If you can fix this with reordering C++ files, this looks even more like a linker bug. Their order should not matter. – n. m. could be an AI Jun 04 '16 at 22:03
  • Bug report [here](https://ghc.haskell.org/trac/ghc/ticket/12152) – crockeea Jun 04 '16 at 22:44
  • @n.m. I just tried linking with `stdc++` using `extra-libraries` (pushed in repo), which works when building/static linking, but fails for `cabal repl`/dynamic linking with the same error as above: `panic!... Loading temp shared object failed`. Thoughts on how to work around that? – crockeea Jun 04 '16 at 23:35
  • I've seen some C++-related GHCi linker bugs/inconsistencies before. No real workarounds, just file a bug against GHCi. – n. m. could be an AI Jun 05 '16 at 06:29

2 Answers2

8

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.

crockeea
  • 21,651
  • 10
  • 48
  • 101
4

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243