用C++代码调用Haskell代码

60

我目前正在使用C++编写应用程序,发现其中一些功能最好使用Haskell编写。我看到了有关从C代码调用Haskell的说明,但是能否在C++中做到相同的呢?

编辑:澄清一下,我想要的是一种将Haskell代码编译为外部库的方法,以便g++可以将C++的目标代码与其链接。

更新:我已经在下面放了个可行的示例,供其他人参考(也为了防止我自己忘记)。


2
你试过了吗?也就是说,当你从C++代码中遵循C语言指令时,它是否有效?除非编写这些指令的人忘记使用extern "C",过度依赖void指针的隐式转换,或者将某个变量命名为class等等,否则我不明白为什么不能。 - asveikau
因为根据指示,我必须使用ghc编译C代码。 - Tomer Vromen
使用g++链接的完整示例在Haskell维基上已经不再适用。新方法似乎依赖于ghc进行所有编译和链接。在ArchLinux上:ghc --make -dynamic -no-hs-main test.cpp Foo.hs -lstdc++ -o test - Roland Puntaier
5个回答

66

对于任何感兴趣的人,这是我最终成功的测试案例:


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;
}

我在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

10
我在Linux上也成功地运行了这个。但是,我还发现你可以通过与 GHC 相连来极大地简化最后一步(因为 C++ 的手动链接步骤要简单得多)。具体来说,你可以链接如下代码:ghc -no-hs-main M.o test.o -lstdc++ - Onyxite
1
另外,对于那些通过apt-get ghc在Linux上安装了Haskell的用户,适当的包含目录应该可用于-I/usr/lib/ghc/include。如果您的C++程序仍然找不到HsFFI.h,请尝试在系统中其他位置搜索HsFFI.h,例如使用findlocate命令。 - RussellStewart
1
同意@Onyxite的提示。作为对Haskell粉丝俱乐部的致敬,我制作了以下关于它的视频:https://asciinema.org/a/jl6B8b327H8knPo9gMWEupWkB - daparic

40

编辑:你还应该看一下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(并且据我了解,从功能上来说它就是GCC)。然而,在链接时,与直接调用GCC不同,因为它还会自动包含Haskell运行时库。对于C++程序,G++的工作方式也是一样的-- 当它作为链接器使用时,它会包含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的答案以获取详细信息,因为他就是这样做的。)


感谢您提供详细的答案。按照您的指示,我只得到了一个未定义引用(std::__ostream_insert),这肯定是一种改进。我想现在只需要协调所有必要的库,这个任务我会留到明天。-v和-l选项确实可以与GHC一起使用。唯一需要注意的是,在第一个ghc命令中需要使用标志-XForeignFunctionInterface(还需要一些包含目录和-I标志)。 - Tomer Vromen
2
@Tomer:作为替代方案(命令行上的-XForeignFunctionInterface),您可以在使用FFI的任何Haskell源文件顶部放置语言pragma:{-# LANGUAGE ForeignFunctionInterface #-}。 - mokus
由于某种原因,A_stub.o在我的电脑上没有被编译,我使用的是Linux系统,除此之外一切都编译正常。 - pyCthon
1
@pyCthon:编译“A.hs”程序时,您是否使用了Tomer提到的“-XForeignFunctionInterface”选项?(我刚刚编辑了答案以添加该选项;之前我忘记了。) - Brooks Moses
你也可以将 {-# LANGUAGE ForeignFunctionInterface #-} 放在 Haskell 代码的顶部。这样,你就不再需要使用 -XForeignFunctionInterface 选项了。 - daparic

11

3

cabal 2.0增加了“foreign-library”功能,似乎解决了链接器问题,并使整个构建过程更加愉快。

我编写了一个简短的示例教程https://github.com/pdlla/haskell-ffi-cabal-foreign-library-examples

译文说明:本文介绍了cabal 2.0新增的“foreign-library”功能,它可以帮助解决链接器问题并改善整个构建过程。同时,作者还提供了一个简短的示例教程,方便读者更好地理解和应用这一功能。


2
由于你可以从C中调用Haskell,所以没有理由不能从C++中调用它。另一方面,从Haskell调用C++要困难得多,通常需要一个C包装器。
编辑以扩展。说明书不完整是错误的。这是一个维基页面。直接查看GHC手册:http://www.haskell.org/ghc/docs/6.12.2/html/users_guide/ffi-ghc.html 本文描述了如何导出函数以及如何使用您自己的主函数。请注意,它说“其他语言,比如C”。这是因为您可以从任何能够调用您正在导出的基本C函数的语言(和编译器)中执行此操作,并且HsFFI.h提供了这些函数。这是与语言和编译器无关的。唯一需要的是在您的系统上使用标准调用约定调用C函数的能力,而C++编译器(例如g ++)确实提供了这种能力。

根据指示,我必须使用ghc编译C代码。一个适用于C++的解决方案是最终使用gcc/g++编译代码。 - Tomer Vromen
@Tomer Vromen:请看我上面的编辑。你想要的可以直接在 GHC 手册中找到,而且有详细的文档说明。 - sclv
稍微澄清一下 -- 你需要使用gcc/g++编译C++代码,使用GHC编译Haskell代码。 - Brooks Moses
抱歉我太蠢了,但我只是得到了一堆链接器错误。我尝试使用ghc生成的.o文件进行链接,还尝试了从GHC的lib路径中选择各种库。我总是收到hs_init是未定义引用的消息。是否有一个可行的逐步示例? - Tomer Vromen
Tomer - 没问题!我发表评论后才意识到这可能是个问题。希望我的完整回答能够澄清这一点。(不幸的是,我目前只是一个C++用户,不知道有任何逐步示例。) - Brooks Moses
https://asciinema.org/a/jl6B8b327H8knPo9gMWEupWkB 是我在 Linux NixOS 中的操作方式。因此,我让 ghc 进行最终构建。 - daparic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接