JEP 389: Foreign Linker API (Incubator)
Summary
Introduce an API that offers statically-typed, pure-Java access to native code. This API, together with the Foreign-Memory API (JEP 393), will considerably simplify the otherwise error-prone process of binding to a native library.
History
The Foreign-Memory Access API, which provides the foundations for this JEP, was first proposed by JEP 370 and targeted to Java 14 in late 2019 as an incubating API, and then subsequently refreshed by JEP 383 and JEP 393, which were targeted to Java 15 and 16, respectively. Together, the Foreign-Memory Access API and the Foreign Linker API constitute key deliverables of Project Panama.
Goals
-
Ease of use: Replace JNI with a superior pure-Java development model.
-
C support: The initial scope of this effort aims at providing high quality, fully optimized interoperability with C libraries, on x64 and AArch64 platforms.
-
Generality: The Foreign Linker API and implementation should be flexible enough to, over time, accommodate support for other platforms (e.g., 32-bit x86) and foreign functions written in languages other than C (e.g. C++, Fortran).
-
Performance: The Foreign Linker API should provide performance that is comparable to, or better than, JNI.
Non-Goals
It is not a goal to:
- Drop, re-implement, or improve JNI,
- Provide a tool to mechanically generate Java code from native-code header files, or
- Change or improve the way in which Java applications interacting with native libraries are packaged and deployed (e.g., multi-platform JAR files).
Motivation
Java has supported native method calls via the Java Native Interface (JNI) since Java 1.1, but this path has always been hard and brittle. Wrapping a native function with JNI requires developing multiple artifacts: a Java API, a C header file, and a C implementation. Even with tooling help, Java developers must work across multiple toolchains to keep multiple platform-dependent artifacts in sync. This is hard enough with stable APIs, but when trying to track APIs in progress, it is a significant maintenance burden to update all of these artifacts each time the API evolves. Finally, JNI is largely about code, but code always exchanges data, and JNI offers little help in accessing native data. For this reason, developers often resort to workarounds (such as direct buffers or sun.misc.Unsafe
) which make the application code harder to maintain or even less safe .
Over the years, numerous frameworks have emerged to fill the gaps left by JNI, including JNA, JNR and JavaCPP. JNA and JNR generate wrappers dynamically from a user-defined interface declaration; JavaCPP generates wrappers statically driven by annotations on JNI method declarations. While these frameworks are often a marked improvement over the JNI experience, the situation is still less than ideal, especially when compared with languages which offer first-class native interoperation. For instance, Python's ctypes package can dynamically wrap native functions without any glue code. Other languages, such as Rust, provide tools which mechanically derive native wrappers from C/C++ header files.
Ultimately, Java developers should be able to (mostly) just use any native library that is deemed useful for a particular task — and we have seen how the status quo gets in the way of achieving that. This JEP rectifies this imbalance by introducing an efficient and supported API — the Foreign Linker API — which provides foreign-function support without the need for any intervening JNI glue code. It does this by exposing foreign functions as method handles which can be declared and invoked in pure Java code. This greatly simplifies the task of writing, building and distributing Java libraries and applications which depend upon foreign libraries. Moreover, the Foreign Linker API, together with the Foreign-Memory Access API, provides a solid and efficient foundation which third-party native interoperation frameworks — both present and future — can reliably build upon.
Description
In this section we dive deeper into how native interoperation is achieved using the Foreign Linker API. The various abstractions described in this section will be provided as an incubator module named jdk.incubator.foreign
, in a package of the same name, side-by-side with the existing Foreign Memory Access API.
Symbol lookups
The first ingredient of any foreign-function support is a mechanism to look up symbols in native libraries. In traditional Java/JNI scenarios, this is done via the System::loadLibrary
and System::load
methods, which internally map into calls to dlopen
. The Foreign Linker API provides a simple library-lookup abstraction via the LibraryLookup
class (similar to a method-handle lookup), which provides capabilities to look up named symbols in a given native library. We can obtain a library lookup in three different ways:
-
LibraryLookup::ofDefault
— returns the library lookup which can see all the symbols that have been loaded with the VM. -
LibraryLookup::ofPath
— creates a library lookup associated with the library found at the given absolute path. -
LibraryLookup::ofLibrary
— creates a library lookup associated with the library with given name (this might require setting thejava.library.path
variable appropriately).
Once a lookup is obtained, a client can use it to retrieve handles to library symbols, either global variables or functions, using the lookup(String)
method. This method returns a fresh LibraryLookup.Symbol
, which is just a proxy for a memory address and a name.
For instance, the following code looks up the clang_getClangVersion
function provided by the clang
library:
LibraryLookup libclang = LibraryLookup.ofLibrary("clang");
LibraryLookup.Symbol clangVersion = libclang.lookup("clang_getClangVersion");
One crucial distinction between the library loading mechanism of the Foreign Linker API and that of JNI is that loaded JNI libraries are associated with a class loader. Furthermore, to preserve class loader integrity, the same JNI library cannot be loaded into more than one class loader. The foreign-function mechanism described here is more primitive: The Foreign Linker API allows clients to target native libraries directly, without any intervening JNI code. Crucially, Java objects are never passed to and from native code by the Foreign Linker API. Because of this, libraries loaded via LibraryLookup
are not tied to any class loader and can be (re)loaded as many times as needed.
The C linker
The CLinker
interface is the foundation of the API’s foreign function support.
interface CLinker {
MethodHandle downcallHandle(LibraryLookup.Symbol func,
MethodType type,
FunctionDescriptor function);
MemorySegment upcallStub(MethodHandle target,
FunctionDescriptor function);
}