Java、SWIG 和 Rust 可以一起使用吗?

Is it possible to use Java, SWIG and Rust together?

我正在尝试调用我编写的 Rust library from Java and I really want to use SWIG to generate the interface layer from a C 头文件(我还希望允许常规 C 客户端调用我的库,因此我认为维护一个接口头文件是有意义的)。

我正在 Windows using Mingw and Rust (GNU ABI).

做这一切

我将详细介绍我所做的以及下面的结果,但基本上我最后得到的是 UnsatisfiedLinkError。有几件事我认为可能是错误的,但我不确定,我不确定如何(或是否)我可以修复它们:

  1. SWIG 将数字放在函数名称中(如果您编辑它在我的示例中生成的 testlib_wrap.c 文件,您可以看到这一点)。
  2. JNIdocumentation says I need to pass the argument -Wl,--add-stdcall-alias when compiling but as I am building with cargo I am not sure how to do that (can I pass it if I build with rustc directly perhaps? I could not see anything in the man page)

简而言之,我的问题是:

如何使用 SWIG 从 Java 调用 Rust?

但我觉得我只是触及了解决方案的表面,所以答案可能解决了上述的一个或两个问题,所以这正是我目前所处的位置...


我首先使用 Cargo:

创建一个新的 Rust 库
cargo new testlib
cd testlib

使用以下内容创建 testlib.h

void tell_me_the_answer(void);

创建一个包含以下内容的 swig 输入文件 (testlib.i):

%module testlib
%{
#include "testlib.h"
%}
%include "testlib.h"

运行 痛饮生成一些 Java 和 C:

mkdir testlib
swig -outdir testlib -java -package testlib testlib.i

创建一个主要的 java class (Program.java),其内容为:

public final class Program {
    static {
        System.loadLibrary("testlib"); 
    }
    public static void main(final String[] args) {
        testlib.testlib.tell_me_the_answer();
    }
}

编译java:

javac Program.java testlib\testlib.java testlib\testlibJNI.java

编辑cargo制作的src\lib.rs文件实现功能:

#[no_mangle]
pub extern "C" fn tell_me_the_answer() {
    println!("The answer is...APPLES!");
}

创建一个新的 build.rs 文件以挂钩通过 gcc-rs 库编译 swig 输出,其中包含:

extern crate gcc;
fn main() {
    gcc::Config::new()
                .file("testlib_wrap.c")
                .include("C:/Program Files/Java/jdk1.8.0_45/include")
                .include("C:/Program Files/Java/jdk1.8.0_45/include/win32")
                .compile("libtestlib.a");
}

编辑 Cargo.toml 文件,使其包含:

[package]
name = "testlib"
version = "0.1.0"
build = "build.rs"
[lib]
name = "testlib"
crate-type = ["dylib"]
[build-dependencies]
gcc = "0.3"

编译 rust 项目:

cargo build

运行 java 申请:

java -Djava.library.path=target\debug Program

得到以下错误:

Exception in thread "main" java.lang.UnsatisfiedLinkError: testlib.testlibJNI.tell_me_the_answer()V
    at testlib.testlibJNI.tell_me_the_answer(Native Method)
    at testlib.testlib.tell_me_the_answer(testlib.java:13)
    at Program.main(Program.java:6)

我查看了 cargo 在 dependency walker 中制作的 DLL,它看起来有点空(就其导出而言)并且单个函数看起来有点奇怪,至少对我来说,由于名称中的 1s 和我认为 --add-stdcall-alias 会删除的 @ 部分,对吗?

我是否已关闭并且依赖 walker 中显示的 DLL 中的名称是我问题的根源?
如果是 Java_testlib_testlibJNI_tell_me_the_answer 会起作用吗?
如果是这样,我该怎么做(我编辑了 SWIG 生成的 _wrap.c 文件以删除 1,但我不确定如何删除 @)?
如果不是,问题是什么?

我有一个解决方案,但它不是很漂亮,所以如果有人可以从货物中无缝地实现这一点,那将是一个更好的解决方案,但批处理文件暂时对我有用。这是我所做的...

我放弃了从 cargo 调用 gcc,从 rust 构建了一个静态库,然后从命令行 运行 gcc 将 rust 生成的 staticlib 与 SWIG 生成的代码混合在一起,并创建了一个动态库,比如所以...

我把Cargo.toml改成了:

[package]
name = "testlib"
version = "0.1.0"
[lib]
name = "testlib"
crate-type = ["staticlib"]

已删除build.rs

构建静态库:

cargo build

编译 SWIG 输出并将其与刚刚生成的货物链接,如下所示:

gcc -shared -o target\debug\testlib.dll "-LC:/Program Files (x86)/Rust stable GNU 1.9/lib/rustlib/i686-pc-windows-gnu/lib" -Ltarget\debug "-IC:/Program Files/Java/jdk1.8.0_45/include" "-IC:/Program Files/Java/jdk1.8.0_45/include/win32" testlib_wrap.c -ltestlib -lws2_32 -luserenv -lgcc_eh -lshell32 -ladvapi32 -Wl,--add-stdcall-alias

现在我有一个 testlib.dll 在 Dependency Walker 中看起来不错,当我 运行 它时我看到:

The answer is...APPLES!

完全正确的答案。