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 ?我很确定它是最后几个之一,但我就是无法让它工作,也没有调试它的工具。 (nmldd -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 ​​交互的方式,但如果你真的想了解它,这里有一些注意事项。

您出现错误的原因是:

  1. 你指定的模块顺序不正确,应该是拓扑顺序,而不是颠倒的拓扑顺序,即依赖在依赖之前
  2. 您没有 .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.mlifapi.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、 等。 目前尚不清楚为什么需要这些,因为它们迟早会出现无论如何。)