c - OCaml:链接 C 和 OCaml 的问题

标签 c ocaml linker-errors

我能够包装 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 ?我很确定它是最后几个之一,但我就是无法让它工作,并且没有调试它的工具。 (nmldd -r 看起来很健康。是否有一些类似的工具可用于各种 cma、cmo、cmi、cmx 文件?)

最佳答案

如果使用dune,与C的接口(interface)会容易得多。您无需了解底层详细信息,它都会为您处理。

现在,回到你的例子。这绝对不是 OCaml 用户与 C ​​交互的方式,但如果您真的想了解它,这里有一些注释。

出现错误的原因是:

  1. 您以错误的顺序指定了模块,它应该是拓扑顺序,而不是逆拓扑顺序,即依赖项位于依赖项之前
  2. 您没有 .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/

相关文章:

ocaml - 用OCaml编写此内容的正确方法是什么?

用于安装 OCaml 库的 Makefile

ocamlyacc 解析错误 : what token?

android - 应用太大?无法执行 dex : Cannot merge new index into a non-jumbo instruction

c++ - 为什么我必须在这里明确声明内联函数?

c - 运行时错误-SIGABRT

c - select() 和 fcntl() 之间的区别

从源打印奇怪的字符串复制字符串

c - 无法解释这个处理指针的 C 示例

visual-studio-2008 - 将 Qt 应用程序链接到 Google Breakpad 时出现 Unresolved 符号错误