views:

228

answers:

3

I'm currently writing an app in C++ and found that some of its functionality would be better written in Haskell. I've seen instructions on calling Haskell from C code, but is it possible to do the same with C++?

EDIT: To clarify, what I'm looking for is a way to compile Haskell code into an external library that g++ can link with the object code from C++.

UPDATE: I've put up a working example below for anyone else interested (also so I won't forget).

+2  A: 

Since you can call Haskell from C, there's no reason you can't call it from C++. Calling C++ from Haskell, on the other hand, is much harder and usually requires a C wrapper.

Edit to expand. The instructions are wrong to incomplete. They're a wiki page. Look directly at the GHC manual: http://www.haskell.org/ghc/docs/6.12.2/html/users_guide/ffi-ghc.html

This describes how to export functions, and how to use your own main. Note where it says "some other language, say C." It says this because you can do this from any language (and compiler) that can invoke the vanilla C functions that you're exporting, and that HsFFI.h provides. This is language agnostic, and compiler agnostic. All it requires is the ability to invoke C functions using the standard calling conventions on your system, which a C++ compiler (such as g++) certainly provides.

sclv
According to the instructions, I have to compile the C code with ghc. A solution that would work with C++ is one where the code is eventually compiled with gcc/g++
Tomer Vromen
@Tomer Vromen: see my edit above. What you want is possible out of the box, and documented directly in the GHC manual.
sclv
Clarified a bit as well -- you would compile the C++ code with gcc/g++, and the Haskell code with GHC.
Brooks Moses
Sorry I'm so dense, but I'm just getting a bunch of linker errors. I tried linking with the .o files that ghc produced, and also with all sorts of libraries from GHC's lib path. I always get a message that hs_init is an undefined reference. Is there a working step-by-step example somewhere?
Tomer Vromen
Tomer - no problem! I just realized that this would be an issue, after I posted the comment. Hopefully my full answer will clarify that. (Unfortunately, I'm not aware of any step-by-step examples, but then, I'm only a C++ user at this point.)
Brooks Moses
+3  A: 

As sclv indicates, compiling should be no problem. The difficulty there is likely to be linking the C++ code, and here you will have a little bit of difficulty with getting all the needed runtime libraries linked in. The problem is that Haskell programs need to be linked with the Haskell runtime libraries, and C++ programs need to be linked with the C++ runtime libraries. In the Wiki page you reference, when they do

$ ghc -optc -O test.c A.o A_stub.o -o test

to compile the C program, that actually does two steps: It compiles the C program into an object file, and then links it together. Written out, that would be something like (probably not quite right, as I don't speak GHC):

$ ghc -c -optc-O test.c -o test.o
$ ghc test.o A.o A_stub.o -o test

GHC just acts like GCC (and, IIUC, functionally is GCC) when compiling the C program. When linking it, however, it is different from what happens if you call GCC directly, because it also magically includes the Haskell runtime libraries. G++ works the same way for C++ programs -- when it's used as a linker, it includes the C++ runtime libraries.

So, as I mentioned, you need to compile in a way that links with both runtime libraries. If you run G++ in verbose mode to compile and link a program, like so:

$ g++ test.cpp -o test -v

it will create a long list of output about what it's doing; at the end will be a line of output where it does the linking (with the collect2 subprogram) indicating what libraries it links to. You can compare that to the output for compiling a simple C program to see what's different for C++; on my system, it adds -lstdc++.

Thus, you should be able to compile and link your mixed Haskell/C++ program like so:

$ ghc -c -O A.hs                                # compile Haskell object file.
$ g++ -c -O test.cpp                            # compile C++ object file.
$ ghc A.o A_stub.o test.o -lstdc++ -o test      # link

There, because you've specified -lstdc++, it will include the C++ runtime library (assuming -l is the right GHC syntax; you'll need to check), and because you've linked with ghc, it will include the Haskell runtime library. This should result in a working program.

Alternately, you should be able to do something similar to the -v output investigation with GHC, and figure out what Haskell runtime library (or libraries) it links to for Haskell support, and then add that library when linking your program with C++, just as you already do for pure C++ programs.

Brooks Moses
Thanks for the detailed answer. Following your instructions, I get "only" 1 undefined reference (for std::__ostream_insert) , so this is certainly an improvement. I guess now it's only a matter of coordinating all the necessary libraries, which I'll leave for tomorrow. The -v and -l options do work with GHC. The only thing is that the flag -XForeignFunctionInterface is needed for the first ghc command (and also some include directories with the -I flag)
Tomer Vromen
@Tomer: Alternatively (to -XForeignFunctionInterface on the command line) you can place a language pragma at the top of any haskell sources that use the FFI: {-# LANGUAGE ForeignFunctionInterface #-}
mokus
+3  A: 

To anyone interested, this is the test case that I've finally got working:


M.hs

module Foo where

foreign export ccall foo :: Int -> Int

foo :: Int -> Int
foo = floor . sqrt . fromIntegral

test.cpp

#include <iostream>
#include "M_stub.h"

int main(int argc, char *argv[])
{
    std::cout << "hello\n";
    hs_init(&argc, &argv);
    std::cout << foo(500) << "\n";
    hs_exit();
    return 0;
}

I did the compiles & linking on my Windows machine. The commands to run (in this order) are:

>ghc -XForeignFunctionInterface -c M.hs
>g++ -c test.cpp -I"c:\Program Files\Haskell Platform\2010.2.0.0\lib\include"
>g++ -o test.exe -DDONT_WANT_WIN32_DLL_SUPPORT M.o M_stub.o test.o -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\haskell98-1.0.1.1" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\random-1.0.0.2" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\time-1.1.4" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\process-1.0.1.3" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\directory-1.0.1.1" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\old-time-1.0.0.5" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\old-locale-1.0.0.2" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\filepath-1.1.0.4" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\Win32-2.2.0.2" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\bytestring-0.9.1.7" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\array-0.3.0.1" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\base-4.2.0.2" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\integer-gmp-0.2.0.1" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib\ghc-prim-0.2.0.0" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib" -L"C:\Program Files\Haskell Platform\2010.2.0.0\lib/gcc-lib" -lHSrtsmain -lHShaskell98-1.0.1.1 -lHSrandom-1.0.0.2 -lHStime-1.1.4 -lHSprocess-1.0.1.3 -lHSdirectory-1.0.1.1 -lHSold-time-1.0.0.5 -lHSold-locale-1.0.0.2 -lHSfilepath-1.1.0.4 -lHSWin32-2.2.0.2 -luser32 -lgdi32 -lwinmm -ladvapi32 -lshell32 -lshfolder -lHSbytestring-0.9.1.7 -lHSarray-0.3.0.1 -lHSbase-4.2.0.2 -lwsock32 -luser32 -lshell32 -lHSinteger-gmp-0.2.0.1 -lHSghc-prim-0.2.0.0 -lHSrts -lm -lwsock32 -u _ghczmprim_GHCziTypes_Izh_static_info -u _ghczmprim_GHCziTypes_Czh_static_info -u _ghczmprim_GHCziTypes_Fzh_static_info -u _ghczmprim_GHCziTypes_Dzh_static_info -u _base_GHCziPtr_Ptr_static_info -u _base_GHCziWord_Wzh_static_info -u _base_GHCziInt_I8zh_static_info -u _base_GHCziInt_I16zh_static_info -u _base_GHCziInt_I32zh_static_info -u _base_GHCziInt_I64zh_static_info -u _base_GHCziWord_W8zh_static_info -u _base_GHCziWord_W16zh_static_info -u _base_GHCziWord_W32zh_static_info -u _base_GHCziWord_W64zh_static_info -u _base_GHCziStable_StablePtr_static_info -u _ghczmprim_GHCziTypes_Izh_con_info -u _ghczmprim_GHCziTypes_Czh_con_info -u _ghczmprim_GHCziTypes_Fzh_con_info -u _ghczmprim_GHCziTypes_Dzh_con_info -u _base_GHCziPtr_Ptr_con_info -u _base_GHCziPtr_FunPtr_con_info -u _base_GHCziStable_StablePtr_con_info -u _ghczmprim_GHCziBool_False_closure -u _ghczmprim_GHCziBool_True_closure -u _base_GHCziPack_unpackCString_closure -u _base_GHCziIOziException_stackOverflow_closure -u _base_GHCziIOziException_heapOverflow_closure -u _base_ControlziExceptionziBase_nonTermination_closure -u _base_GHCziIOziException_blockedIndefinitelyOnMVar_closure -u _base_GHCziIOziException_blockedIndefinitelyOnSTM_closure -u _base_ControlziExceptionziBase_nestedAtomically_closure -u _base_GHCziWeak_runFinalizzerBatch_closure -u _base_GHCziTopHandler_runIO_closure -u _base_GHCziTopHandler_runNonIO_closure -u _base_GHCziConc_ensureIOManagerIsRunning_closure -u _base_GHCziConc_runSparks_closure -u _base_GHCziConc_runHandlers_closure -lHSffi

The long list of parameters for the last g++ command is from running

>ghc M.hs -v

and then copying the command where it says "***Linker:" (some of the first parameters need to be removed).


The result:

>test
hello
22
Tomer Vromen