OCaml:连接 C 和 OCaml 的问题
OCaml: Issues linking C and OCaml
我能够包装 C 代码并从 OCaml 解释器访问它,但无法构建二进制文件!我 98% 确定这是链接问题,但找不到探索链接的工具。
走到这一步是件苦差事,(无穷无尽的 Error: The external function is not available
消息)所以我会记录我所做的一切。
一个'system'文件stuff.c
#include <stdio.h>
int fun(int z) // Emulate a "real" subroutine
{
printf("duuude whoa z=%d\n", z);
return 42;
}
以上编译为
cc -fPIC -c stuff.c
ld -shared -o libstuff.so stuff.o
上面的 OCaml 包装器,在 ocmstuff.c
:
#include <caml/mlvalues.h>
CAMLprim value yofun(value z) {
return Val_long(fun(Long_val(z)));
}
以上构建为
cc -fPIC -c ocmstuff.c
ld -shared -o dllostuff.so ocmstuff.o -L . -lstuff -lc -rpath .
是的,确实需要 rpath,否则接下来的步骤会受到影响。 (编辑: 如果你不使用 rpath,你需要使用 LD_LIBRARY_PATH=.
代替。对于最终的 'production' 版本,你将更改 rpath到实际的库路径,或者做 ld.so.conf
欺骗或安装到 'standard' 位置,或者告诉你的用户关于 LD_LIBRARY_PATH
。这就像你对任何其他系统所做的一样。 rpath 解决方案似乎是最稳定可靠的解决方案。)
接下来,一个模块声明,存储在fapi.mli
module Fapi : sig
external ofun : int -> int = "yofun" ;;
end
以上构建为:
ocamlc -a -o fapi.cma -intf fapi.mli -dllib -lostuff
有用吗?是的:
$ rlwrap ocaml fapi.cma
OCaml version 4.11.1
open Fapi ;;
Fapi.ofun 33 ;;
duuude whoa z=33
- : int = 42
#
所以包装器工作正常。现在让我们用它编译。这里是 myprog.ml
:
open Fapi ;;
Fapi.ofun 33 ;;
编译它:
ocamlc -c myprog.ml
ocamlc -o myprog myprog.cmo fapi.cma
最后一条命令喷出:
File "_none_", line 1:
Error: Required module `Fapi' is unavailable
我 98% 确定上述错误是由于一些愚蠢的链接错误引起的,但我无法找到它。我为什么这么想?好吧,这里有一个提供提示的相关问题。
$ rlwrap ocaml
open Fapi ;;
# Fapi.ofun 33 ;;
Error: The external function `yofun' is not available
#
嗯,这很奇怪。它显然必须找到 fapi.cma
,因为这是它知道 yofun
的唯一途径。但不知何故,它不知道它需要为此深入研究 dllostuff.so
。或者 dllostuff.so
可能无法正确 link/load libstuff.so
?或者 libc.so
得到 printf
?我很确定它是最后几个之一,但我就是无法让它工作,也没有调试它的工具。 (nm
和 ldd -r
看起来很健康。是否有一些类似的工具可以用于各种 cma、cmo、cmi、cmx 文件?)
这里有很多内容,但这些行很可疑:
ocamlc -c myprog.ml
ocamlc -o myprog myprog.cmo fapi.cma
OCaml 期望模块按拓扑排序顺序排列,一个模块在引用它的模块之前出现在命令行上。
所以最后一行似乎应该是这样的:
ocamlc -o myprog fapi.cma myprog.cmo
我希望这对您有所帮助,这只是一个快速回复。
如果使用 dune,与 C 的接口会容易得多。底层的细节你不用知道它都为你处理好了。
现在,回到你的例子。这绝对不是 OCaml 用户与 C 交互的方式,但如果你真的想了解它,这里有一些注意事项。
您出现错误的原因是:
- 你指定的模块顺序不正确,应该是拓扑顺序,而不是颠倒的拓扑顺序,即依赖在依赖之前
- 您没有
.ml
文件(-intf
选项的意思非常不同)
最后一个代码段不起作用的原因是您没有加载库。 ocaml
二进制文件显然没有任何 fapi
单元 link 进入其中,因此您必须使用 #load
指令或通过在命令中传递它来显式加载它行。
下面这行也不是必须的,
ld -shared -o dllostuff.so ocstuff.o -L . -lstuff -lc -rpath .
首先,没有必要link将存根文件放入共享库中。它适得其反,并没有真正给你带来任何东西。其次,传递 -rpath .
将使最终可执行文件不可用,除非共享对象与可执行文件存储在同一文件夹中。只需删除它。
为了完成您的练习,以下是它的构建方式 运行。首先,让我们修复存根文件。我们需要 ml
文件,我们还需要删除一个额外的模块定义,
$ cat fapi.{ml,mli}
external ofun : int -> int = "yofun" ;;
external ofun : int -> int = "yofun" ;;
是的,它们是一样的。 mli
文件在这里并不是真的需要,但为了完整起见,我们保留它。
构建纯 C 部分的方式很好,只要您获得可重定位的 .so 文件就可以了。
现在要构建 ocstuff.c
(我们通常称之为存根),您只需要做,
ocamlc -c ocstuff.c
不要把它变成共享库,不要用它做任何其他事情。现在让我们构建 fapi
库,
ocamlc -c fapi.mli
ocamlc -c fapi.ml
现在让我们构建包含 OCaml 和 C 代码的库,
ocamlmklib -o fapi fapi.cmo ocstuff.o -lstuff -L.
现在我们终于可以构建可执行文件了,
ocamlc -c myprog.ml
LD_LIBRARY_PATH=. ocamlc -o myprog fapi.cma myprog.cmo
和运行它,
LD_LIBRARY_PATH=. ./myprog
duuude whoa z=33
请注意,我们必须使用 LD_LIBRARY_PATH
来告诉 system 动态加载程序在哪里寻找外部依赖项 libstuff.so
。当然,您可以使用 rpath
来指定它的位置(通过 -ccopt
将其传递给 ocamlmklib
),但通常假定外部库安装在系统指定的某个位置装载机知道。
再次强调,除非您正在开发自己的构建系统,否则请使用 dune 或 oasis 来构建 OCaml 程序。这些系统将以最佳方式处理所有底层细节。
P.S。还值得一提的是,您不是在构建二进制文件,而是在构建字节码可执行文件。对于二进制文件,您将不得不使用 ocamlopt
编译器。这将是一个完全不同的故事。同样,沙丘是解决方案。
ivg 提供的答案有效。它还提供了足够的提示来改进原始问题以获得正确的行为。对原始配方的更改是:
创建 fapi.mli
和 fapi.ml
两者具有相同的内容:external ofun : int -> int = "yofun" ;;
用ocaml -c
编译以上两个。 mli
必须首先编译:它会产生一个接口文件 cmi
,在 ml
文件可以编译到它的目标文件 cmo
.
之前需要它
名称 dllostuff.so
错误:必须 dllfapi.so
以保持命名一致性。
将 cma
archive/library 构建为 ocamlc -a -o fapi.cma fapi.cmo -dllib -lfapi
就是这样!除了这些,原来的指令工作。 ivg 的回答建议使用
ocamlmklib -o fapi fapi.cmo ostuff.o -L. -lstuff
而不是
ld -shared -o dllfapi.so ostuff.o -L. -lstuff
这些都有效。主要区别在于 ocamlmklib
还创建了一个静态链接库 libfapi.a
。除此之外,它像以前一样创建 dllfapi.so
。 (该版本还包含各种典型的 gcc 符号,用于处理异常、库 ctors、 等。 目前尚不清楚为什么需要这些,因为它们迟早会出现无论如何。)
我能够包装 C 代码并从 OCaml 解释器访问它,但无法构建二进制文件!我 98% 确定这是链接问题,但找不到探索链接的工具。
走到这一步是件苦差事,(无穷无尽的 Error: The external function is not available
消息)所以我会记录我所做的一切。
一个'system'文件stuff.c
#include <stdio.h>
int fun(int z) // Emulate a "real" subroutine
{
printf("duuude whoa z=%d\n", z);
return 42;
}
以上编译为
cc -fPIC -c stuff.c
ld -shared -o libstuff.so stuff.o
上面的 OCaml 包装器,在 ocmstuff.c
:
#include <caml/mlvalues.h>
CAMLprim value yofun(value z) {
return Val_long(fun(Long_val(z)));
}
以上构建为
cc -fPIC -c ocmstuff.c
ld -shared -o dllostuff.so ocmstuff.o -L . -lstuff -lc -rpath .
是的,确实需要 rpath,否则接下来的步骤会受到影响。 (编辑: 如果你不使用 rpath,你需要使用 LD_LIBRARY_PATH=.
代替。对于最终的 'production' 版本,你将更改 rpath到实际的库路径,或者做 ld.so.conf
欺骗或安装到 'standard' 位置,或者告诉你的用户关于 LD_LIBRARY_PATH
。这就像你对任何其他系统所做的一样。 rpath 解决方案似乎是最稳定可靠的解决方案。)
接下来,一个模块声明,存储在fapi.mli
module Fapi : sig
external ofun : int -> int = "yofun" ;;
end
以上构建为:
ocamlc -a -o fapi.cma -intf fapi.mli -dllib -lostuff
有用吗?是的:
$ rlwrap ocaml fapi.cma
OCaml version 4.11.1
open Fapi ;;
Fapi.ofun 33 ;;
duuude whoa z=33
- : int = 42
#
所以包装器工作正常。现在让我们用它编译。这里是 myprog.ml
:
open Fapi ;;
Fapi.ofun 33 ;;
编译它:
ocamlc -c myprog.ml
ocamlc -o myprog myprog.cmo fapi.cma
最后一条命令喷出:
File "_none_", line 1:
Error: Required module `Fapi' is unavailable
我 98% 确定上述错误是由于一些愚蠢的链接错误引起的,但我无法找到它。我为什么这么想?好吧,这里有一个提供提示的相关问题。
$ rlwrap ocaml
open Fapi ;;
# Fapi.ofun 33 ;;
Error: The external function `yofun' is not available
#
嗯,这很奇怪。它显然必须找到 fapi.cma
,因为这是它知道 yofun
的唯一途径。但不知何故,它不知道它需要为此深入研究 dllostuff.so
。或者 dllostuff.so
可能无法正确 link/load libstuff.so
?或者 libc.so
得到 printf
?我很确定它是最后几个之一,但我就是无法让它工作,也没有调试它的工具。 (nm
和 ldd -r
看起来很健康。是否有一些类似的工具可以用于各种 cma、cmo、cmi、cmx 文件?)
这里有很多内容,但这些行很可疑:
ocamlc -c myprog.ml
ocamlc -o myprog myprog.cmo fapi.cma
OCaml 期望模块按拓扑排序顺序排列,一个模块在引用它的模块之前出现在命令行上。
所以最后一行似乎应该是这样的:
ocamlc -o myprog fapi.cma myprog.cmo
我希望这对您有所帮助,这只是一个快速回复。
如果使用 dune,与 C 的接口会容易得多。底层的细节你不用知道它都为你处理好了。
现在,回到你的例子。这绝对不是 OCaml 用户与 C 交互的方式,但如果你真的想了解它,这里有一些注意事项。
您出现错误的原因是:
- 你指定的模块顺序不正确,应该是拓扑顺序,而不是颠倒的拓扑顺序,即依赖在依赖之前
- 您没有
.ml
文件(-intf
选项的意思非常不同)
最后一个代码段不起作用的原因是您没有加载库。 ocaml
二进制文件显然没有任何 fapi
单元 link 进入其中,因此您必须使用 #load
指令或通过在命令中传递它来显式加载它行。
下面这行也不是必须的,
ld -shared -o dllostuff.so ocstuff.o -L . -lstuff -lc -rpath .
首先,没有必要link将存根文件放入共享库中。它适得其反,并没有真正给你带来任何东西。其次,传递 -rpath .
将使最终可执行文件不可用,除非共享对象与可执行文件存储在同一文件夹中。只需删除它。
为了完成您的练习,以下是它的构建方式 运行。首先,让我们修复存根文件。我们需要 ml
文件,我们还需要删除一个额外的模块定义,
$ cat fapi.{ml,mli}
external ofun : int -> int = "yofun" ;;
external ofun : int -> int = "yofun" ;;
是的,它们是一样的。 mli
文件在这里并不是真的需要,但为了完整起见,我们保留它。
构建纯 C 部分的方式很好,只要您获得可重定位的 .so 文件就可以了。
现在要构建 ocstuff.c
(我们通常称之为存根),您只需要做,
ocamlc -c ocstuff.c
不要把它变成共享库,不要用它做任何其他事情。现在让我们构建 fapi
库,
ocamlc -c fapi.mli
ocamlc -c fapi.ml
现在让我们构建包含 OCaml 和 C 代码的库,
ocamlmklib -o fapi fapi.cmo ocstuff.o -lstuff -L.
现在我们终于可以构建可执行文件了,
ocamlc -c myprog.ml
LD_LIBRARY_PATH=. ocamlc -o myprog fapi.cma myprog.cmo
和运行它,
LD_LIBRARY_PATH=. ./myprog
duuude whoa z=33
请注意,我们必须使用 LD_LIBRARY_PATH
来告诉 system 动态加载程序在哪里寻找外部依赖项 libstuff.so
。当然,您可以使用 rpath
来指定它的位置(通过 -ccopt
将其传递给 ocamlmklib
),但通常假定外部库安装在系统指定的某个位置装载机知道。
再次强调,除非您正在开发自己的构建系统,否则请使用 dune 或 oasis 来构建 OCaml 程序。这些系统将以最佳方式处理所有底层细节。
P.S。还值得一提的是,您不是在构建二进制文件,而是在构建字节码可执行文件。对于二进制文件,您将不得不使用 ocamlopt
编译器。这将是一个完全不同的故事。同样,沙丘是解决方案。
ivg 提供的答案有效。它还提供了足够的提示来改进原始问题以获得正确的行为。对原始配方的更改是:
创建
fapi.mli
和fapi.ml
两者具有相同的内容:external ofun : int -> int = "yofun" ;;
用
之前需要它ocaml -c
编译以上两个。mli
必须首先编译:它会产生一个接口文件cmi
,在ml
文件可以编译到它的目标文件cmo
.名称
dllostuff.so
错误:必须dllfapi.so
以保持命名一致性。将
cma
archive/library 构建为ocamlc -a -o fapi.cma fapi.cmo -dllib -lfapi
就是这样!除了这些,原来的指令工作。 ivg 的回答建议使用
ocamlmklib -o fapi fapi.cmo ostuff.o -L. -lstuff
而不是
ld -shared -o dllfapi.so ostuff.o -L. -lstuff
这些都有效。主要区别在于 ocamlmklib
还创建了一个静态链接库 libfapi.a
。除此之外,它像以前一样创建 dllfapi.so
。 (该版本还包含各种典型的 gcc 符号,用于处理异常、库 ctors、 等。 目前尚不清楚为什么需要这些,因为它们迟早会出现无论如何。)