大致跟随this tutorial,我设法让这个玩具项目起作用。它从C ++程序调用Haskell函数。
{-# LANGUAGE ForeignFunctionInterface #-}
module Foo where
foreign export ccall foo :: Int -> Int -> IO Int
foo :: Int -> Int -> IO Int
foo n m = return . sum $ f n ++ f m
f :: Int -> [Int]
f 0 = []
f n = n : f (n-1)
bar.c++
#include "HsFFI.h"
#include FOO // Haskell module (path defined in build script)
#include <iostream>
int main(int argc, char *argv[]) {
hs_init(&argc, &argv);
std::cout << foo(37, 19) << "\n";
hs_exit();
return 0;
}
call-haskell-from-cxx.cabal
name: call-haskell-from-cxx
version: 0.1.0.0
build-type: Simple
cabal-version: >=1.10
executable foo.so
main-is: Foo.hs
build-depends: base >=4.10 && <4.11
ghc-options: -shared -fPIC -dynamic
extra-libraries: HSrts-ghc8.2.1
default-language: Haskell2010
#!/bin/bash
hs_lib="foo.so"
hs_obj="dist/build/$hs_lib/$hs_lib"
ghc_version="8.2.1" # May need to be tweaked,
ghc_libdir="/usr/local/lib/ghc-$ghc_version" # depending on system setup.
set -x
cabal build
g++ -I "$ghc_libdir/include" -D"FOO=\"${hs_obj}-tmp/Foo_stub.h\"" -c bar.c++ -o test.o
g++ test.o "$hs_obj" \
-L "$ghc_libdir/rts" "-lHSrts-ghc$ghc_version" \
-o test
env LD_LIBRARY_PATH="dist/build/$hs_lib:$ghc_libdir/rts:$LD_LIBRARY_PATH" \
./test
构建脚本
893
这有效(Ubuntu 16.04,GCC 5.4.0),打印std::cout << foo(37, 19) << "\n";
- 但它不是很健壮,即,如果我删除 Haskell函数的实际调用,即/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziTopHandler_flushStdHandles_closure'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziStable_StablePtr_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziPtr_FunPtr_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziWord_W8zh_con_info'
/usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so: undefined reference to `base_GHCziIOziException_cannotCompactPinned_closure'
...
行,然后在链接步骤失败,并显示错误消息
foo
显然,包含Haskell项目需要额外的库文件。我如何明确地依赖所有必要的东西,以避免这种脆弱?
包含ldd
调用时的构建脚本输出,最终可执行文件为++ cabal build
Preprocessing executable 'foo.so' for call-haskell-from-C-0.1.0.0..
Building executable 'foo.so' for call-haskell-from-C-0.1.0.0..
Linking a.out ...
Linking dist/build/foo.so/foo.so ...
++ g++ -I /usr/local/lib/ghc-8.2.1/include '-DFOO="dist/build/foo.so/foo.so-tmp/Foo_stub.h"' -c bar.c++ -o test.o
++ g++ test.o dist/build/foo.so/foo.so -L /usr/local/lib/ghc-8.2.1/rts -lHSrts-ghc8.2.1 -o test
++ env LD_LIBRARY_PATH=dist/build/foo.so:/usr/local/lib/ghc-8.2.1/rts: sh -c 'ldd ./test; ./test'
linux-vdso.so.1 => (0x00007fff23105000)
foo.so => dist/build/foo.so/foo.so (0x00007fdfc5360000)
libHSrts-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/rts/libHSrts-ghc8.2.1.so (0x00007fdfc52f8000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdfc4dbe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdfc49f4000)
libHSbase-4.10.0.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/base-4.10.0.0/libHSbase-4.10.0.0-ghc8.2.1.so (0x00007fdfc4020000)
libHSinteger-gmp-1.0.1.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/integer-gmp-1.0.1.0/libHSinteger-gmp-1.0.1.0-ghc8.2.1.so (0x00007fdfc528b000)
libHSghc-prim-0.5.1.0-ghc8.2.1.so => /usr/local/lib/ghc-8.2.1/ghc-prim-0.5.1.0/libHSghc-prim-0.5.1.0-ghc8.2.1.so (0x00007fdfc3b80000)
libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fdfc3900000)
libffi.so.6 => /usr/local/lib/ghc-8.2.1/rts/libffi.so.6 (0x00007fdfc36f3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdfc33ea000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fdfc31e2000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdfc2fde000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdfc2dc1000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdfc5140000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdfc2bab000)
:
let uid = Auth.auth().currentUser.uid
let posts = Database.database().reference().child("user_posts")
let query = posts.queryOrdered(byChild: "").queryEqual(toValue: uid)
query.observe(.childAdded) {(snapshot: DataSnapshot) in
...
答案 0 :(得分:2)
通常,ghc
使用-Wl,--no-as-needed
选项链接可执行文件,您也应该使用它。 (您可以检查ghc
如何链接可执行文件,例如使用cabal build --ghc-options=-v3
。)
您可以找到更多详细信息here。我的下一个理解是:foo.so
要求libHSbase-4.10.0.0-ghc8.2.1.so
在运行时根据需要加载,即当我们需要从中加载符号时(请选中readelf -a dist/build/foo.so/foo.so | grep NEEDED
)。因此,如果您不致电foo
,则不会加载base.so
。但是ghc需要加载所有库(我不知道为什么)。 --no-as-needed
选项将强制加载所有库。
请注意,--no-as-needed
选项取决于位置,因此请将其放在共享库之前。
答案 1 :(得分:2)
此答案说明了链接期间发生的情况,使用-Wl,--no-as-needed
的解决方案为何有效以及应该采取什么措施才能拥有更强大的方法。
简而言之:-lHSrts-ghcXXX.so
取决于libHSbaseXXX.so
,libHSinteger-gmpXXX.so
和libHSghc-primXXX.so
,它们必须在链接期间提供给链接器。
此处提出的解决方案取决于大量的人工工作,并且伸缩性不是很好。但是,我对cabal
知之甚少,无法告诉您如何自动执行此操作,但是我希望您可以迈出最后一步。
或者也许使用-Wl,--no-as-needed
解决方案就可以了,因为您知道幕后发生的事情。
让我们首先以某种简化的方式逐步完成版本的链接过程,而无需调用foo
(here是Eli Bendersky的一篇很棒的文章,即使它涉及静态链接):
链接器维护一个符号表,并且必须找到所有符号的定义/机器代码。让我们简化并假设,在表的开头它只有符号main
,并且该符号的定义是未知的。
在对象文件main
中找到了符号test.o
的定义。但是,功能main
使用功能hs_init
和hs_exit
。因此,我们找到了main
的定义,但是除非我们知道hs_init
和hs_exit
的定义,否则它不会起作用。所以现在我们必须寻找它们的定义。
在下一步中,链接器查看foo.so
,但是foo.so
并没有定义我们感兴趣的任何符号(未使用foo
!),并且链接器只是跳过foo.so
而永不回头。
链接器查看-lHSrts-ghcXXX.so
。在那里找到hs_init
和hs_exit
的定义。因此,使用了共享库的全部内容,但是需要定义诸如base_GHCziTopHandler_flushStdHandles_closure
之类的符号。这意味着链接器开始寻找这些符号的定义。
但是在命令行上没有更多的库,因此链接器一无所有,并且链接失败/失败,因为缺少某些符号的定义。
使用foo
的情况有什么不同? 2.步骤之后,不仅需要hs_init
和hs_exit
,还需要foo
,这在foo.so
中可以找到。因此必须包含foo.so
。
由于foo.so
库的构建方式,因此包含以下信息:
>>> readelf -d dist/build/foo.so/foo.so | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libHSrts-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3.so]
0x0000000000000001 (NEEDED) Shared library: [libgmp.so.10]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
>>> readelf -d dist/build/foo.so/foo.so | grep RPATH
0x000000000000000f (RPATH) Library rpath: [
/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM:
/usr/lib/ghc/rts:
/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3:
/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS]
根据此信息,链接器知道需要哪些共享库(NEEDED
-标志)以及可以在系统上找到它们的位置(RPATH
)。找到/打开/处理这些库(即标记为需要),因此存在所有必需的定义。
您可以通过添加
来跟踪整个过程g++ ...
-Wl,--trace-symbol=base_GHCziTopHandler_flushStdHandles_closure \
-Wl,--verbose \
-o test
链接步骤。
如果我们强制按照{Yuras的建议,通过foo.so
将-Wl,--no-as-needed
包含到生成的可执行文件中,则会发生同样的事情。
此分析的结果是什么?
我们应该在命令行上(-lHSrts-ghcXXX.so
之后)提供所需的库,而不要依赖于通过其他共享库每次添加它们的可能性。显然,有些神秘的名称仅对我的安装有效:
g++ ...
-L/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM -lHSbase-4.8.2.0-HQfYBxpPvuw8OunzQu6JGM-ghc7.10.3 \
-L/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS -lHSinteger-gmp-1.0.0.0-2aU3IZNMF9a7mQ0OzsZ0dS-ghc7.10.3 \
-L/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 -lHSghc-prim-0.4.0.0-8TmvWUcS1U1IKHT0levwg3-ghc7.10.3 \
...
-o test
现在它可以生成,但是不会在运行时加载(毕竟,仅在rpath
中设置了正确的foo.so
,但没有使用foo.so
)。要解决此问题,我们可以扩展LD_LIBRARY_PATH
或添加-rpath
链接命令行:
g++ ...
-L... -lHSbase-... -Wl,-rpath,/usr/lib/ghc/base_HQfYBxpPvuw8OunzQu6JGM \
-L... -lHSinteger-gmp-... -Wl,-rpath,/usr/lib/ghc/integ_2aU3IZNMF9a7mQ0OzsZ0dS \
-L... -lHSghc-prim-... -Wl,-rpath,/usr/lib/ghc/ghcpr_8TmvWUcS1U1IKHT0levwg3 \
...
-o test
必须有一个实用程序来自动获取路径和库名(在构建foo.so
时,cabal似乎可以做到),但是我不知道该怎么做,因为我没有使用过haskell / cabal的经验