Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am trying to call into a Rust library from Java and I really want to use SWIG to generate the interface layer from a C header file that I write (I also want to allow regular C clients to call into my library, hence I think it makes sense to maintain one interface header).

I am doing this all on Windows using Mingw and Rust (GNU ABI).

I will go into exactly what I did and the result below but essentially I am getting an UnsatisfiedLinkError at the end. There are a couple of things that I think may be wrong but I am not sure and I am not sure how (or if) I can fix them:

  1. SWIG puts numbers in the function name (you can see this if you edit the testlib_wrap.c file it produces in my example).
  2. The JNI documentation 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)

So in a nutshell, my question is:

How do you call into Rust from Java using SWIG?

But I feel like I am scratching the surface of a solution so the answer may be resolving one or both of the points above so here is exactly where I am so far...


I start by making a new Rust library using Cargo:

cargo new testlib
cd testlib

Create testlib.h with the contents:

void tell_me_the_answer(void);

Create a swig input file (testlib.i) with the following contents:

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

Run swig to generate some Java and C:

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

Create a main java class (Program.java) with the contents:

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

Compile the java:

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

Edit the src\lib.rs file that cargo made to implement the function:

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

Create a new build.rs file to hook in compiling the swig output via the gcc-rs library, which contains:

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");
}

Edit the Cargo.toml file so that it contains:

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

Compile the rust project:

cargo build

Run the java application:

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

Get the following error:

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)

I had a look at the DLL that cargo made me in dependency walker and it looks kind of empty (in terms of it's exports) and the single function looks a bit weird, to me at least, due to the 1s in the name and the @ part which I think the --add-stdcall-alias would remove right?

Dependency Walker Output

Am I close and the name in the DLL as shown in dependency walker is the root of my problem?
If it were Java_testlib_testlibJNI_tell_me_the_answer would it work?
If so, how do I make it so (I edited the _wrap.c file SWIG produced to remove the 1s but I am not sure how I will get rid of the @)?
If not, what's the problem?

like image 591
kmp Avatar asked Jul 08 '16 06:07

kmp


1 Answers

I have a solution, but it is not really beautiful so if anyone can make this seamless from cargo that would be a much nicer solution but a batch file will do for me for the time being. Here is what I did...

I gave up on calling gcc from cargo, built a static lib from rust and then ran gcc from the command line to munge the rust generated staticlib in with the SWIG generated code and create a dynamic library like so...

I changed Cargo.toml to:

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

Deleted build.rs

Built the static lib:

cargo build

Compiled the SWIG output and linked it with what cargo just produced like this:

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

Now I have a testlib.dll that looks fine in Dependency Walker and when I run it I see:

The answer is...APPLES!

Which is totally the right answer.

like image 138
kmp Avatar answered Oct 22 '22 09:10

kmp