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:
testlib_wrap.c
file it produces in my example).-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?
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With