56

我目前正在用 C++ 编写一个应用程序,发现它的某些功能最好用 Haskell 编写。我已经看到了从 C 代码调用 Haskell 的说明,但是是否可以用 C++ 做同样的事情?

编辑:澄清一下,我正在寻找一种将 Haskell 代码编译成外部库的方法,g++ 可以与 C++ 中的目标代码链接。

更新:我在下面为其他感兴趣的人提供了一个工作示例(也是为了不会忘记)。

4

5 回答 5

61

对于任何感兴趣的人,这是我终于开始工作的测试用例:


M.hs

module Foo where

foreign export ccall foo :: Int -> Int

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

测试.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;
}

我在我的 Windows 机器上进行了编译和链接。要运行的命令(按此顺序)是:

>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

最后一个 g++ 命令的参数长列表来自运行

>ghc M.hs -v

然后复制显示“***Linker:”的命令(需要删除一些第一个参数)。


结果:

>test
hello
22
于 2010-10-05T08:21:07.217 回答
36

编辑:您还应该在下面看到 Tomer 的答案。我在这里的回答描述了正在发生的事情的理论,但我可能有一些执行细节不完整,而他的回答是一个完整的工作示例。

正如 sclv 所示,编译应该没有问题。难点可能是链接 C++ 代码,在这里你将有一点困难,要链接所有需要的运行时库。问题是 Haskell 程序需要与 Haskell 运行时库链接,而 C++程序需要与 C++ 运行时库链接。在您引用的 Wiki 页面中,当他们这样做时

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

编译 C 程序,实际上分为两个步骤:将 C 程序编译成目标文件,然后将其链接在一起。写出来,就像(可能不太正确,因为我不会说 GHC):

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

GHC在编译 C 程序时就像 GCC(并且 IIUC 在功能上是GCC)。然而,在链接它时,它与直接调用 GCC 不同,因为它还神奇地包含了 Haskell 运行时库。G++ 对 C++ 程序的工作方式相同——当它用作链接器时,它包括 C++ 运行时库。

因此,正如我所提到的,您需要以与两个运行时库链接的方式进行编译。如果您以详细模式运行 G++ 来编译和链接程序,如下所示:

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

它将创建一长串关于它正在做什么的输出;最后将是一行输出,它在其中进行链接(与collect2子程序),指示它链接到哪些库。您可以将其与编译简单 C 程序的输出进行比较,以了解 C++ 的不同之处;在我的系统上,它添加了-lstdc++.

因此,您应该能够像这样编译和链接您的混合 Haskell/C++ 程序:

$ ghc -c -XForeignFunctionInterface -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

在那里,因为您已指定-lstdc++,它将包含 C++ 运行时库(假设-l是正确的 GHC 语法;您需要检查),并且因为您已链接ghc,它将包含 Haskell 运行时库。这应该会产生一个工作程序。

或者,您应该能够使用 GHC 执行类似于-v输出调查的操作,并找出它链接到的 Haskell 运行时库(或库)以支持 Haskell,然后在将程序与 C++ 链接时添加该库,就像您一样已经为纯 C++ 程序做了。(有关详细信息,请参阅 Tomer 的回答,因为他就是这样做的。)

于 2010-10-05T00:44:59.263 回答
9

这是有关该主题的教程:

https://github.com/jarrett/cpphs

It covers calling Haskell from C++ and calling C from Haskell.

于 2014-09-16T20:34:23.873 回答
2

由于您可以从 C 调用 Haskell,因此没有理由不能从 C++ 调用它。另一方面,从 Haskell 调用 C++ 要困难得多,通常需要 C 包装器。

编辑以展开。说明错误到不完整。它们是一个维基页面。直接看GHC手册:http ://www.haskell.org/ghc/docs/6.12.2/html/users_guide/ffi-ghc.html

这描述了如何导出函数,以及如何使用自己的 main。注意它在哪里说“其他语言,比如 C”。之所以这么说是因为您可以使用任何可以调用您正在导出的普通 C 函数以及 HsFFI.h 提供的语言(和编译器)来执行此操作。这与语言无关,与编译器无关。它所需要的只是使用系统上的标准调用约定调用 C 函数的能力,而 C++ 编译器(例如 g++)当然提供了这一点。

于 2010-10-04T23:27:17.957 回答
1

cabal 2.0 added the "foreign-library" feature which seems to solve the linker issues as well as making the whole build process much more pleasant in general.

I put together a short example tutorial https://github.com/pdlla/haskell-ffi-cabal-foreign-library-examples

于 2019-04-06T03:38:14.383 回答