我能够包装 C 代码并从 OCaml 解释器访问它,但无法构建二进制文件!我 98% 确定这是一些链接问题,但找不到探索链接的工具。
达到这一点是一件苦差事,(无数的错误:外部函数不可用
消息),所以我将记录我所做的一切。
一个“系统”文件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=.
。对于最终的“生产”版本,您需要更改 rpath到实际的库路径,或者进行 ld.so.conf
欺骗或安装到“标准”位置,或者告诉您的用户有关 LD_LIBRARY_PATH
的信息。这就像您所做的那样d 对任何其他系统都这样做。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
无法正确链接/加载 libstuff.so
?或者也许 libc.so
来获取 printf
?我很确定它是最后几个之一,但我就是无法让它工作,并且没有调试它的工具。 (nm
和 ldd -r
看起来很健康。是否有一些类似的工具可用于各种 cma、cmo、cmi、cmx 文件?)
最佳答案
如果使用dune,与C的接口(interface)会容易得多。您无需了解底层详细信息,它都会为您处理。
现在,回到你的例子。这绝对不是 OCaml 用户与 C 交互的方式,但如果您真的想了解它,这里有一些注释。
出现错误的原因是:
- 您以错误的顺序指定了模块,它应该是拓扑顺序,而不是逆拓扑顺序,即依赖项位于依赖项之前
- 您没有
.ml
文件(-intf
选项含义非常不同)
最后一个代码片段不起作用的原因是您没有加载库。 ocaml
二进制文件显然没有链接任何 fapi
单元,因此您必须使用 #load
指令或通过在命令行中传递它。
下面的行也不是必需的,
ld -shared -o dllostuff.so ocstuff.o -L . -lstuff -lc -rpath .
首先,不需要将 stub 文件链接到共享库中。它会适得其反,并不会真正给你带来任何东西。其次,传递 -rpath .
将使最终可执行文件不可用,除非共享对象与可执行文件存储在同一文件夹中。只需删除它即可。
为了完成您的练习,以下是它的构建和运行方式。首先,让我们修复 stub 文件。我们需要 ml
文件,还需要删除额外的模块定义,
$ cat fapi.{ml,mli}
external ofun : int -> int = "yofun" ;;
external ofun : int -> int = "yofun" ;;
是的,它们是相同的。这里实际上并不需要 mli
文件,但为了完整起见,我们保留它。
构建纯 C 部分的方式没问题,只要获得可重定位的 .so 文件就可以了。
现在构建 ocstuff.c
(我们通常称之为 stub ),您只需要这样做,
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
来告诉系统动态加载器在哪里查找外部依赖项libstuff.so
。当然,您可以使用 rpath
指定其位置(通过 -ccopt
将其传递给 ocamlmklib
),但通常假设外部库安装在系统加载程序知道的某个位置。
再次强调,除非您正在开发自己的构建系统,否则请使用沙丘或绿洲来构建 OCaml 程序。这些系统将以最佳方式处理所有低级细节。
附注还值得一提的是,您构建的不是二进制文件,而是字节码可执行文件。对于二进制文件,您必须使用 ocamlopt
编译器。这将是一个完全不同的故事。再说一遍,沙丘是解决方案。
关于c - OCaml:链接 C 和 OCaml 的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69936809/