来自不同语言的代码如何集成到同一平台中?

How does code from different languages get integrated in the same platform?

首先,我不是软件专家,我意识到这个问题可能非常模糊(这只是一种好奇),并准备好阅读我的一些野蛮猜测!

阅读一篇关于 Linux 的开发人员如何在他们的 OS (https://www.zdnet.com/article/linus-torvalds-on-where-rust-will-fit-into-linux/) 中实施 Rust 的文章提出了这个问题。

在他们的 OS 中实施 Rust 甚至意味着什么?他们是否有一些用 C 编写的编译代码调用用 Rust 编写的编译代码?我看不出这是如何有效地完成的,因为你有不同的编译器可能无法优化代码,因为在这个过程中,它正在调用“外部”代码。我想如果你将 Python 或 Java 之类的语言混入其中,情况会变得更糟,这些语言没有预编译。现在您将拥有 JVM 或 PVM 运行 以及编译代码,我想这将是非常不切实际的。我可以想到的一种方法是,如果您将所有这些事情视为单独的进程,并且您只是让来自一种语言的代码启动一个与来自另一种语言的代码相对应的进程,但同样,我认为这不会非常有效...

同样,我意识到我本可以更直接,但我不是在寻找解决问题的具体答案,而是在寻找不同语言如何一起使用的一般见解。感谢理解!

通常在编译源文件时,我们有一组编译器生成的输出选项,例如从 main 函数创建二进制应用程序、静态 link 可用二进制文件或动态link可用库,或者在其他情况下,源到源转换。

内核是用 C 编写的,为了能够像内核一样编译大型代码库,我们经常决定编译每个源文件或一组源文件(正确的术语应该是翻译单元 https://en.wikipedia.org/wiki/Translation_unit_(programming) ) 到静态 linked 库或目标文件中。一旦我们收集了所有目标文件和静态(或共享)linked 库,我们就可以 link 将它们组合在一起并生成最终的 binary/library.

当我们谈论将 Rust 代码集成到内核中时,我们谈论的是在 Rust 中使用来自 C 的静态 linked 库,反之亦然。调用其他编译器或语言生成的代码的过程称为外部函数接口,或 FFI。

FFI 有许多细节和挑战,包括 ABI 或名称重整等等。 ABI,或应用程序二进制接口是您文章中提到的问题之一。与 C 不同,Rust 还没有稳定的 ABI,这意味着不能保证从 Rust 编译的静态库中的符号将来不会有不同的名称或数据布局。这意味着使用 Rust 编译器编译的代码可能与以前的 Rust 编译器输出不兼容,这将需要每次 ABI 更改时更新 C 代码。

C 程序的传统编译方式如下:

  • 每个源文件都是单独编译的! - 放入目标文件,其中包含机器代码和用于将所有目标文件连接在一起的占位符。
  • 所有目标文件连接在一起并填充占位符。

如果您可以使用 Rust 而不是 C 生成其中一些目标文件,C 编译器将无法区分。和不同的C文件没什么区别!


您需要确保跨越语言障碍的函数需要是两种语言的有效函数。例如,您可能无法将结构作为参数传递,或 return 结构,如果这在 Rust 中无效(我实际上并不知道)——您可能只能使用原始值,如 int 和浮点数和指针。如果 Rust 机器代码期望浮点变量位于某些寄存器中,但 C 机器代码将它们放在不同的寄存器中,您可能无法传递浮点数。或者您可能必须向一个或另一个编译器发出特殊提示,说“参数进入 this 寄存器,dummy!”如果 Rust 编译器人员不与 C 编译器人员合作,您可能会 运行 遇到其中一些问题,但运气好的话,它们都可以解决。

由于函数需要两种语言都有效,所以还需要写一个C头文件。你不能 #include 一个 Rust 文件 - 你可以,但它不会工作 - 所以你需要用 C 语法 again 编写函数声明,以便 C 编译器可以理解什么他们是。


对于需要 VM 的语言,它以不同的方式变得复杂。

通常这些语言与 C 如此不同,以至于您不能 link 将机器代码放在一起。相反,您必须使用 VM 的 API 来初始化 VM、加载代码和调用代码。像这样:

    // not real code, just an illustration of how it could work

    // How C starts a JVM and calls a Java method (a Java function)
    void run_jvm() {
        jvm_t *jvm = create_jvm();
        jvm_class_t *main_class = jvm_load_class(jvm, "Main.class");
        jvm_method_t *main_method = jvm_get_method(jvm, main_class, "main", NULL);
        jvm_call(jvm, main_method, NULL, NULL);
        delete_jvm(jvm);
    }

    // how Java calls a C function
    void Java_HelloPrinter_printHello(jvm_t *jvm, jvm_object_t *this, jvm_args_t *args) {
        printf("Hello world! My argument is %d\n", jvm_args_get_int(args, 1));
    }

    // how the method is declared in Java
    public class HelloPrinter {
        public static native void printHello(int i);
        //            ^^^^^^
        // this means it's a C function
    }

在 Python 或 Lua 中,作为更动态的语言,解释器不会为您查找内容。相反,您在 运行 任何 Python 代码之前将函数放入变量中。

    // not real code, just an illustration of how it could work

    // How Python calls a C function
    void printHelloFunction(pvm_t *pvm, pvm_args_t *args) {
        printf("Hello world! My argument is %d\n", pvm_args_get_int(args, 1));
    }

    // How C starts a PVM and calls a Python method
    void run_python() {
        pvm_t *pvm = create_pvm();
        pvm_variable_t *var = pvm_create_global_variable(pvm, "printHello");
        pvm_set_variable_as_c_function(pvm, var, &printHelloFunction);
        pvm_load_module_from_file(pvm, "main.py");
        delete_pvm(pvm);
    }

这些语言与 C 并不处于同等地位,但 Rust 是,因为 Rust 编译为机器代码。如果您可以 运行 Java 或 Python 在 VM 之外编写代码,那么它们可能处于平等地位。曾经有一个名为 gcj 的编译器可以将 Java 编译成机器码,但现在不再维护了。有人可以写一个,虽然它不会 运行 大多数 Java 程序,因为很多程序需要用 VM 做一些事情(比如反射)。