Recently, I had a necessity to do some calls
kernel32.dll from my Java code.
Just a few system calls on Windows platform,
as simple as it sounds.
Plus I wanted to keep resulting size of binary
as small as possible.
Later requirement has added a fair challenge to that task.
How to call platform code for Java?
JNI - Java Native Interface
JNI is built in JVM and is part of Java standard. Sounds good, there is a catch though. To call native code from Java via JNI, you have to write native code (e.g. using C language). That is it, JNI requires some glue code (aka bindings) between native calls and Java methods.
m... do we have other alternatives?
JNA - Java Native Access
JNA is an alternative to JNI. You can call native code from Java, no glue code. Cool, what is the cost?
JNA jar has size of 1.1 MiB. Extra megabyte just to do couple of simple calls to Windows kernel - not a deal.
Back to JNI
Ok, I need to write some glue code for JNI. What language to choose?
C/C++ - no, just no. C/C++ tool chain, compiler, headers, build tools, is an abomination, especially on Windows. Please, I just need literally half screen of code compiled to dll binary. I do not want 10 GiB worth Visual Studio to pollute my desktop.
Die hard Java guy is speaking :)
Pascal is an ancient language. It was programming language of my youth. MS DOS, Turbo Pascal ... colors were so bright these days.
Twenty years later, I was surprised to find Pascal in pretty good shape. Free Pascal has impressive list of supported platforms. Pascal compiler is lighting fast. Produced binaries have no dependency on libc / msvcrt.
Using Free Pascal I get my kernel32-to-JNI dll with size of 33 KiB. That sounds much, much better.
Can we do better?
Rust is a new kid in a language block. It has a strong ambition to replace C/C++ as system level language. It gives you all powers of C plus memory safety, modernized build system, language level modules (crates).
Sounds promising, let's try Rust for little JNI glue dll.
rustc -C debuginfo=0 --crate-type dylib myjni.rs
result is disappointing 2.5 MiB binary.
dylib is a dll which can be used by other Rust code,
so it is exposing a lot of language specific metadata.
cdylib is a new packaging introduced in Rust 1.10,
which is more suitable for JNI bindings.
rustc -C lto -C debuginfo=0 --crate-type cdylib myjni.rs
has produced 1.6 MiB binary.
-C lto option instructs compiler to do "link time optimization".
For some reason
cdylib was not compiling without
lto option for me.
Ok, direction is right, but we need to move much further. Let's try more compiler options.
rustc -C opt-level=3 -C lto -C debuginfo=0 --crate-type cdylib myjni.rs
has produced 200 KiB binary. Optimization allow compiler to throw away a big portion of standard library which I will never need for my simple JNI binding.
Though, a large portion of standard library is still there.
In Rust you can fully turn off standard library (e.g. to run on bare metal).
Normally you would need at least memory management, but for simple JNI binding you can get away using stack allocation only.
At the moment, using Rust with no_std option
requires nightly build of compiler.
I have also rewrite some portion of kernel32 and JNI
declarations to avoid dependency on
rustc -C opt-level=3 -C panic=abort -C lto -C debuginfo=0 --crate-type cdylib myjni.rs
Binary size is 22.5 KiB.
Cool, we have beaten Free Pascal.
One more tweak, execute
strip -s on resulting dll and final binary size is 16.9 KiB.
Honestly, 16.9 KiB for couple of calls is still overkill. But, I'm not desperate enough to try assembly for JNI binding, at least not today.
Free Pascal IMHO, Free Pascal a good choice if you need simple JNI bindings. As a bonus, Free Pascal on Linux has no dependency on platform's dynamic libraries, so you can build cross-Linux-distro binaries.
Rust. I believe Rust have a great potential. Rust has unique memory safety model yet it let you to get as close to bare metal as C does. Besides other features, Rust has really promising cross compiling capabilities, which gives it a very strong position in embedded / IoT space.
Yet, Rust needs to get more stable.
no_std feature is not available in
latest (1.10) stable.
cdynlib is not supported
by latest stable cargo tool.
Rust tool chain on Windows depends
either on MS Visual Studio or MSys.
Resulting binaries are slightly incompatible
to each other (Oracle JMV is build with
Visual Studio, so using MSys built JNI bindings
leads to process crash in certain cases).