158. Introducing Foreign Linker API
The main goal of Foreign Linker API is to provide a robust and easy-to-use API (no need to write C/C++ code) for sustaining interoperability between the Java code and C/C++ foreign functions of native shared libraries (in the future, other programming languages will be supported via this API).The journey of calling foreign code starts with java.lang.foreign.SymbolLookup functional interface. This interface represents the entry point and it consists of looking up the address of a given symbol in a loaded native shared library. There are three ways of doing this as follows:Linker.defaultLookup() – as its name suggests, defaultLookup() represents a default lookup that scans and locates all the symbols of the commonly used native shared libraries depending on the current operating system.
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = linker.defaultLookup();
SymbolLookup.loaderLookup() – represents a loader lookup that scans and locates all the symbols in all the native shared libraries loaded in the current class loader (via System.loadLibrary() and System.load() based on java.library.path).
System.loadLibrary(“fooLib”); // fooLib.dll is loaded here
SymbolLookup lookup = SymbolLookup.loaderLookup();
SymbolLookup.libraryLookup(String name, Arena arena) – represents a library lookup capable to scan and load in the arena scope a native shared library with the given name. It also creates a symbol lookup for all symbols in that native shared library. Alternatively, we can specify a Path via SymbolLookup.libraryLookup(Path path, Arena arena).
try (Arena arena = Arena.openConfined()) {
SymbolLookup lookup = SymbolLookup.libraryLookup(
libName/libPath, arena.scope());
}
If this step is successfully accomplished then we can choose the symbol(s) corresponding to the foreign function(s) that we want to call. Finding a foreign function can be done by its name via the SymbolLookup.find(String name) method. If the pointed method exits among the located symbols then find() returns a zero-length memory segment wrapped in an Optional (Optional<MemorySegment>). The base address of this segment points to the foreign function’s entry point.
MemorySegment fooFunc = mathLookup.find(“fooFunc”).get();
So far, we have located the native shared library and found one of its methods (fooFunc). Next, we have to link Java code to this foreign function. This is accomplished via the Linker API which is based on two concepts:
downcall – call the native code from Java code
upcall – call the Java code from the native code
These two concepts are materialized by the Linker interface. Downcall is mapped in two methods having the following signatures:
MethodHandle downcallHandle(
FunctionDescriptor function, Linker.Option… options)
default MethodHandle downcallHandle(MemorySegment symbol,
FunctionDescriptor function, Linker.Option… options)
Typically, the default method is used via the MemorySegment obtained earlier via the find() method, a function descriptor that describes the signature of the foreign function, and an optional set of linker options. The returned MethodHandle is used later to invoke the foreign function via invoke(), invokeExact(), and so on. Via invoke() or invokeExact() we pass arguments to the foreign function and access the returned result of running the foreign function (if any).Upcall is mapped by the following method:
MemorySegment upcallStub(MethodHandle target,
FunctionDescriptor function, SegmentScope scope)
Typically, the target argument refers to a Java method, the function argument describes the Java method signature and the scope argument represents the associated with the returned MemorySegment. This MemorySegment is passed later as an argument of the Java code that invokes (invoke()/invokeExact()) a downcall method handle. As a consequence, this MemorySegment acts as a function pointer.If we glue these knowledge together then we can write a classical example of calling the getpid() method (on Windows 10, _getpid() – https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getpid) as follows (consider reading the meaningful comments for getting insight on each step):
// get the Linker of the underlying native platform
// (operating system + processor that runs the JVM)
Linker linker = Linker.nativeLinker();
// “_getpid” is part of the Universal C Runtime (UCRT) Library
SymbolLookup libLookup = linker.defaultLookup();
// find the “_getpid” foreign function
MemorySegment segmentGetpid = libLookup.find(“_getpid”).get();
// create a method handle for “_getpid”
MethodHandle func = linker.downcallHandle(segmentGetpid,
FunctionDescriptor.of(ValueLayout.JAVA_INT));
// invoke the foreign function, “_getpid” and get the result
int result = (int) func.invokeExact();
System.out.println(result);
This code was tested on Windows 10. If you run a different operating system then consider informing yourself about this foreign function to adjust the code accordingly.