Foreign Function and Memory API in Native Image

The Foreign Function and Memory (FFM) API is an interface that enables Java code to interact with native code and vice versa. It is finalized in JDK 22 with JEP 454. Support in Native Image is currently experimental and must be explicitly enabled with -H:+ForeignAPISupport (in addition to -H:+UnlockExperimentalVMOptions). Modules that are permitted to perform “restricted” native operations (including creating handles for calls to or from native code) must be specified using the --enable-native-access= option. This page gives an overview of the FFM API support in Native Image.

Foreign Memory #

Foreign memory functionality is generally supported. Shared arenas are currently not supported.

Foreign Functions #

The FFM API enables Java code to call down to native functions, and conversely allows native code to call up to invoke Java code via method handles. These two kinds of calls are referred to as “downcalls” and “upcalls” respectively, and are collectively referred to as “foreign calls”.

Note: Currently, foreign calls are supported on the x64 architecture. Specifically, downcalls are supported on x64 Linux, Windows and MacOS, while upcalls are supported only on x64 Linux.

Looking Up Native Functions #

The FFM API provides the SymbolLookup interface to find functions in native libraries by name. Native image supports all available symbol lookup methods, i.e., SymbolLookup.loaderLookup(), SymbolLookup.libraryLookup(), and Linker.defaultLookup().

Registering Foreign Calls #

In order to perform calls to native code at run time, supporting code must be generated at image build time. Therefore, the native-image tool must be provided with descriptors that characterize the functions with which downcalls or upcalls can be performed at runtime.

For upcalls, it is recommended to register a specific static method as an upcall target by providing its declaring class and the method name. This allows native-image to create specialized upcall code that can be orders of magnitude faster than a upcall registered only by function descriptor. Whenever possible, this should be the preferred way to register upcalls.

Descriptors and target methods can be registered using a custom Feature, for example:

import static java.lang.foreign.ValueLayout.*;

class ForeignRegistrationFeature implements Feature { 
  public void duringSetup(DuringSetupAccess access) {
    RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid());
    RuntimeForeignAccess.registerForUpcall(FunctionDescriptor.ofVoid());
    RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(), Linker.Option.critical(false));
    RuntimeForeignAccess.registerForUpcall(FunctionDescriptor.of(JAVA_INT, JAVA_INT));
    RuntimeForeignAccess.registerForUpcall(FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT));
    RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(ADDRESS, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1));
    RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_INT), Linker.Option.captureCallState("errno"));

    MethodHandle target = MethodHandles.lookup().findStatic(UserClass.class, "aStaticMethod", MethodType.of(int.class, int.class, int.class));
    RuntimeForeignAccess.registerForUpcall(target, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT));
  }
}

To activate the custom feature, pass the --features=com.example.ForeignRegistrationFeature option (the fully-qualified name of the feature class) to native-image. It is recommended to do so with a native-image.properties file.

Connect with us