From 5beedfcabeb1bf01f6e5f5c77d1f4d30d19ae367 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Fri, 7 Jun 2024 18:34:29 -0700 Subject: [PATCH 1/4] Add support for GraalVM Adds a JAR publication at `jna-graalvm.jar`, with accompanying build infrastructure, which provides support for JNA within the context of the Substrate Virtual Machine (SVM). GraalVM Native Image targets use SVM instead of JVM at runtime. JNA's current strategy of unpacking libraries at runtime works under SVM, but is suboptimal; the binary is native, so it can simply include JNA object code for the current platform directly. To accomplish this, several GraalVM "feature" implementations are provided in this new publication. By default, regular JNA access is enabled through the `JavaNativeAccess` feature; this class enables reflection and runtime JNI configurations for downstream projects which use JNA. Another feature, `SubstrateStaticJNA`, is experimental because it relies on unstable GraalVM APIs, but instead of loading JNA at runtime from a dynamic library, it builds JNA into the final native image with a static object. These features are enabled through a resource within `META-INF`, called `native-image.properties`, which is picked up by the native image compiler at build time. The new artifact only needs to be present for GraalVM native targets at build time; otherwise, the classes and libraries in `jna-graalvm.jar` are inert. Includes tested support for: - macOS aarch64 - Linux amd64 - feat: add `jna-graalvm.jar` publication - feat: add base `JavaNativeAccess` feature for auto-config of JNA - feat: add initial implementation of `SubstrateStaticJNA` feature - test: sample/test gradle build for native image - chore: ci config to run native sample - chore: add readme to `lib/gvm` Signed-off-by: Sam Gammon Signed-off-by: Dario Valdespino Co-authored-by: Sam Gammon Co-authored-by: Dario Valdespino --- .github/workflows/graalvm.yaml | 34 +++ build.xml | 213 ++++++++++++- common.xml | 1 + lib/gvm/AbstractJNAFeature.java | 209 +++++++++++++ lib/gvm/JavaNativeAccess.java | 162 ++++++++++ lib/gvm/README.md | 280 ++++++++++++++++++ lib/gvm/SubstrateStaticJNA.java | 55 ++++ lib/gvm/native-image.properties | 24 ++ native/Makefile | 7 +- pom-jna-graalvm.xml | 87 ++++++ samples/README.md | 5 + samples/graalvm-native-jna/.gitignore | 2 + samples/graalvm-native-jna/README.md | 3 + samples/graalvm-native-jna/build.gradle.kts | 88 ++++++ samples/graalvm-native-jna/gradle.properties | 28 ++ .../gradle/libs.versions.toml | 57 ++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/graalvm-native-jna/gradlew | 249 ++++++++++++++++ samples/graalvm-native-jna/gradlew.bat | 92 ++++++ .../graalvm-native-jna/settings.gradle.kts | 42 +++ .../src/main/java/com/example/JnaNative.java | 45 +++ 22 files changed, 1685 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/graalvm.yaml create mode 100644 lib/gvm/AbstractJNAFeature.java create mode 100644 lib/gvm/JavaNativeAccess.java create mode 100644 lib/gvm/README.md create mode 100644 lib/gvm/SubstrateStaticJNA.java create mode 100644 lib/gvm/native-image.properties create mode 100644 pom-jna-graalvm.xml create mode 100644 samples/README.md create mode 100644 samples/graalvm-native-jna/.gitignore create mode 100644 samples/graalvm-native-jna/README.md create mode 100644 samples/graalvm-native-jna/build.gradle.kts create mode 100644 samples/graalvm-native-jna/gradle.properties create mode 100644 samples/graalvm-native-jna/gradle/libs.versions.toml create mode 100644 samples/graalvm-native-jna/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/graalvm-native-jna/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/graalvm-native-jna/gradlew create mode 100644 samples/graalvm-native-jna/gradlew.bat create mode 100644 samples/graalvm-native-jna/settings.gradle.kts create mode 100644 samples/graalvm-native-jna/src/main/java/com/example/JnaNative.java diff --git a/.github/workflows/graalvm.yaml b/.github/workflows/graalvm.yaml new file mode 100644 index 000000000..98bcf3d3a --- /dev/null +++ b/.github/workflows/graalvm.yaml @@ -0,0 +1,34 @@ +# GraalVM build and native test. +name: GraalVM CI + +on: + workflow_dispatch: + workflow_call: + pull_request: + push: + branches: + - master + +permissions: + contents: read + +env: + ANT_OPTS: -Djava.security.manager=allow + +jobs: + build: + runs-on: ubuntu-latest + name: Test GVM 22, ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '22' + distribution: 'graalvm-community' + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Linux requirements + run: sudo apt-get -y install texinfo + - uses: gradle/actions/setup-gradle@v3 + - name: "Build: Native Image" + run: ant dist && ant install && ant nativeImage && ant nativeRun diff --git a/build.xml b/build.xml index 5ba1b0e14..a8f295d18 100644 --- a/build.xml +++ b/build.xml @@ -39,6 +39,7 @@ + @@ -52,6 +53,7 @@ + @@ -60,6 +62,7 @@ + @@ -69,6 +72,9 @@ + + + @@ -88,10 +94,12 @@ + + @@ -112,6 +120,19 @@ uri="antlib:org.codehaus.mojo.animal_sniffer" classpathref="maven-ant-tasks.classpath" /> + + + + + + + + + + + + + @@ -131,6 +152,7 @@ + + + + + + @@ -192,6 +225,7 @@ + @@ -237,6 +271,9 @@ + + + @@ -246,12 +283,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -569,6 +656,9 @@ osname=macosx;processor=aarch64 + + + @@ -649,7 +739,7 @@ osname=macosx;processor=aarch64 - + @@ -1014,21 +1104,37 @@ cd .. + + includes="jnidispatch.dll,libjnidispatch.*" + excludes="*.lib,*.a"/> + + + + + - + + + + + + + + @@ -1036,6 +1142,7 @@ cd .. + @@ -1292,6 +1399,54 @@ cd .. Reports generated in ${reports.clover} + + + + + + + + + + + + + + + + + + + JNA GraalVM Integration +
${header}
+ ${footer} + + + + + + + + + + + +
+ + + +
+ @@ -1351,7 +1506,7 @@ cd .. - + @@ -1366,6 +1521,9 @@ cd .. + + + @@ -1431,6 +1589,12 @@ cd .. + + + + + + @@ -1483,6 +1647,17 @@ cd .. + + + + + + + + + + + @@ -1538,6 +1713,19 @@ cd .. + + + + + + + + + + + + + @@ -1553,6 +1741,7 @@ cd .. + @@ -1581,4 +1770,20 @@ cd .. + + + + + + + + + + + + + + + + diff --git a/common.xml b/common.xml index cb09efa3b..7e26ebcc0 100644 --- a/common.xml +++ b/common.xml @@ -19,6 +19,7 @@ + diff --git a/lib/gvm/AbstractJNAFeature.java b/lib/gvm/AbstractJNAFeature.java new file mode 100644 index 000000000..055b00edc --- /dev/null +++ b/lib/gvm/AbstractJNAFeature.java @@ -0,0 +1,209 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.graalvm.nativeimage.hosted.RuntimeJNIAccess; +import org.graalvm.nativeimage.hosted.RuntimeProxyCreation; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * Provides common logic for JNA-related GraalVM feature classes. + * + *

These classes should only be included at build time for a `native-image` target.

+ * + * @since 5.15.0 + * @author Sam Gammon (sam@elide.dev) + * @author Dario Valdespino (dario@elide.dev) + */ +abstract class AbstractJNAFeature implements Feature { + /** + * Obtain a reference to a method on a class, in order to register it for reflective access + * + * @param clazz Class to obtain method reference from + * @param methodName Name of the method to obtain a reference to + * @param args Method arguments + * @return Method reference + */ + protected static Method method(Class clazz, String methodName, Class... args) { + try { + return clazz.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(e); + } + } + + /** + * Obtain a reference to one or more fields on a class, in order to register them for reflective access + * + * @param clazz Class to obtain field references from + * @param fieldNames Names of the fields to obtain references to + * @return Field references + */ + protected static Field[] fields(Class clazz, String... fieldNames) { + try { + Field[] fields = new Field[fieldNames.length]; + for (int i = 0; i < fieldNames.length; i++) { + fields[i] = clazz.getDeclaredField(fieldNames[i]); + } + return fields; + } catch (NoSuchFieldException e) { + throw new IllegalStateException(e); + } + } + + /** + * Register a class for reflective access at runtime + * + * @param clazz Class to register + */ + protected static void reflectiveClass(Class... clazz) { + RuntimeReflection.register(clazz); + } + + /** + * Register a resource for use in the final image + * + * @param module Module which owns the resource + * @param resource Path to the resource to register + */ + protected static void registerResource(Module module, String resource) { + RuntimeResourceAccess.addResource(module, resource); + } + + /** + * Register a resource for use in the final image + * + * @param resource Path to the resource to register + */ + protected static void registerResource(String resource) { + registerResource(AbstractJNAFeature.class.getModule(), resource); + } + + /** + * Register a class for JNI access at runtime + * + * @param clazz Class to register + */ + protected static void registerJniClass(Class clazz) { + RuntimeJNIAccess.register(clazz); + Arrays.stream(clazz.getConstructors()).forEach(RuntimeJNIAccess::register); + Arrays.stream(clazz.getMethods()).forEach(RuntimeJNIAccess::register); + } + + /** + * Register a class for JNI access at runtime, potentially with reflective access as well + * + * @param clazz Class to register + * @param reflective Whether to register the class and constructors for reflective access + */ + protected static void registerJniClass(Class clazz, Boolean reflective) { + registerJniClass(clazz); + if (reflective) { + RuntimeReflection.register(clazz); + RuntimeReflection.registerAllConstructors(clazz); + } + } + + /** + * Register a suite of JNA methods for use at runtime + * + * @param reflective Whether to register the methods for reflective access + * @param methods Methods to register + */ + protected static void registerJniMethods(Boolean reflective, Method... methods) { + RuntimeJNIAccess.register(methods); + if (reflective) { + RuntimeReflection.register(methods); + } + } + + /** + * Register a suite of JNA methods for use at runtime + * + * @param methods Methods to register + */ + protected static void registerJniMethods(Method... methods) { + registerJniMethods(false, methods); + } + + /** + * Register a suite of JNA fields for use at runtime + * + * @param reflective Whether to register the fields for reflective access + * @param fields Fields to register + */ + protected static void registerJniFields(Boolean reflective, Field[] fields) { + RuntimeJNIAccess.register(fields); + if (reflective) { + RuntimeReflection.register(fields); + } + } + + /** + * Register a suite of JNA fields for use at runtime + * + * @param fields Fields to register + */ + protected static void registerJniFields(Field[] fields) { + registerJniFields(false, fields); + } + + /** + * Register a combination of interfaces used at runtime as a dynamic proxy object + * + * @param classes Combination of interface classes; order matters + */ + protected static void registerProxyInterfaces(Class... classes) { + RuntimeProxyCreation.register(classes); + } + + /** + * Assign the specified class or classes to initialize at image build time + * + * @param clazz Classes to register for build-time initialization + */ + protected static void initializeAtBuildTime(Class... clazz) { + for (Class c : clazz) { + RuntimeClassInitialization.initializeAtBuildTime(c); + } + } + + /** + * Assign the specified class or classes to initialize at image run-time + * + * @param clazz Classes to register for run-time initialization + */ + protected static void initializeAtRunTime(Class... clazz) { + for (Class c : clazz) { + RuntimeClassInitialization.initializeAtRunTime(c); + } + } +} diff --git a/lib/gvm/JavaNativeAccess.java b/lib/gvm/JavaNativeAccess.java new file mode 100644 index 000000000..8a563d558 --- /dev/null +++ b/lib/gvm/JavaNativeAccess.java @@ -0,0 +1,162 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna; + +import com.sun.jna.*; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.PointerByReference; +import org.graalvm.nativeimage.hosted.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * Feature for use at build time on GraalVM, which enables basic support for JNA. + * + *

This "Feature" implementation is discovered on the class path, via the argument file at native-image.properties. + * At build-time, the feature is registered with the Native Image compiler. + * + *

If JNA is detected on the class path, the feature is enabled, and the JNA library is initialized and configured + * for native access support. + * + *

Certain features like reflection and JNI access are configured by this feature; to enable static optimized + * support for JNA, see the {@link SubstrateStaticJNA} feature. + * + * @since 5.15.0 + * @author Sam Gammon (sam@elide.dev) + * @author Dario Valdespino (dario@elide.dev) + */ +public final class JavaNativeAccess extends AbstractJNAFeature implements Feature { + static final String NATIVE_LAYOUT = "com.sun.jna.Native"; + + @Override + public String getDescription() { + return "Enables access to JNA at runtime on SubstrateVM"; + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return access.findClassByName(NATIVE_LAYOUT) != null; + } + + private void registerCommonTypes() { + registerJniClass(Callback.class); + registerJniClass(CallbackReference.class); + registerJniMethods( + method(CallbackReference.class, "getCallback", Class.class, Pointer.class)); + registerJniMethods( + method(CallbackReference.class, "getCallback", Class.class, Pointer.class, boolean.class)); + registerJniMethods( + method(CallbackReference.class, "getFunctionPointer", Callback.class)); + registerJniMethods( + method(CallbackReference.class, "getFunctionPointer", Callback.class, boolean.class)); + registerJniMethods( + method(CallbackReference.class, "getNativeString", Object.class, boolean.class)); + registerJniMethods( + method(CallbackReference.class, "initializeThread", Callback.class, CallbackReference.AttachOptions.class)); + + registerJniClass(com.sun.jna.CallbackReference.AttachOptions.class); + + registerJniClass(FromNativeConverter.class); + registerJniMethods(method(FromNativeConverter.class, "nativeType")); + + registerJniClass(IntegerType.class); + registerJniFields(fields(IntegerType.class, "value")); + + registerJniClass(Native.class); + registerJniMethods( + method(Native.class, "dispose"), + method(Native.class, "fromNative", FromNativeConverter.class, Object.class, Method.class), + method(Native.class, "fromNative", Class.class, Object.class), + method(Native.class, "nativeType", Class.class), + method(Native.class, "toNative", ToNativeConverter.class, Object.class), + method(Native.class, "open", String.class, int.class), + method(Native.class, "close", long.class), + method(Native.class, "findSymbol", long.class, String.class)); + + registerJniClass(Native.ffi_callback.class); + registerJniMethods(method(Native.ffi_callback.class, "invoke", long.class, long.class, long.class)); + + registerJniClass(NativeLong.class); + + registerJniClass(NativeMapped.class); + registerJniMethods(method(NativeMapped.class, "toNative")); + + registerJniClass(Pointer.class); + registerJniFields(fields(Pointer.class, "peer")); + + registerJniClass(PointerType.class); + registerJniFields(fields(PointerType.class, "pointer")); + + registerJniClass(Structure.class); + registerJniFields(fields(Structure.class, "memory", "typeInfo")); + registerJniMethods( + method(Structure.class, "autoRead"), + method(Structure.class, "autoWrite"), + method(Structure.class, "getTypeInfo"), + method(Structure.class, "getTypeInfo", Object.class), + method(Structure.class, "newInstance", Class.class), + method(Structure.class, "newInstance", Class.class, long.class), + method(Structure.class, "newInstance", Class.class, Pointer.class)); + + registerJniClass(Structure.ByValue.class); + registerJniClass(Structure.FFIType.class); + registerJniClass(WString.class); + registerJniClass(PointerByReference.class); + } + + private void registerCommonProxies() { + registerProxyInterfaces(Callback.class); + registerProxyInterfaces(Library.class); + } + + private void registerReflectiveAccess() { + reflectiveClass( + CallbackProxy.class, + CallbackReference.class, + Klass.class, + NativeLong.class, + Structure.class, + IntByReference.class, + PointerByReference.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + registerCommonTypes(); + registerCommonProxies(); + registerReflectiveAccess(); + + // extending `com.sun.jna.Library` should add interfaces as proxies + access.registerSubtypeReachabilityHandler((duringAnalysisAccess, aClass) -> { + // must extend `Library`, be an interface, and not already be a proxy + assert aClass.isInterface(); + if (Library.class.isAssignableFrom(aClass) && !Proxy.isProxyClass(aClass)) { + registerProxyInterfaces(aClass); + } + }, Library.class); + } +} diff --git a/lib/gvm/README.md b/lib/gvm/README.md new file mode 100644 index 000000000..a87f0b781 --- /dev/null +++ b/lib/gvm/README.md @@ -0,0 +1,280 @@ +# JNA for Native Image + +This directory specifies sources for running JNA under [GraalVM Native Image](https://www.graalvm.org/latest/reference-manual/native-image/). + +Native Image targets run under the [Substrate Virtual Machine (SVM)](https://docs.oracle.com/en/graalvm/enterprise/20/docs/reference-manual/native-image/SubstrateVM/), rather than a traditional JVM. + +JNA is usable out of the box on JVM but requires some configuration for SVM: + +- [JNI registration](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/JNI/): Lookups and calls over JNI must be registered in a configuration file. + +- [Proxy registration](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/DynamicProxy/): Classes that extend JNA's `Library` interface must also be registered as dynamic proxies. + +**There are several ways to properly configure SVM: manually or via the built-in GraalVM [`Feature`](https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/hosted/Feature.html) support.** + +## Automatic Configuration + +To automatically configure `native-image` for JNA, **add the `jna-graalvm.jar` to your classpath:** + +**Maven (`pom.xml`):** +```xml + + net.java.dev.jna + jna-graalvm + ... (5.15.0 or greater) ... + +``` + +**Gradle Groovy DSL (`build.gradle`):** +```groovy + // check maven central for the latest version + implementation 'net.java.dev.jna:jna-graalvm:5.15.0' +``` + +**Gradle Kotlin DSL (`build.gradle.kts`):** +```kotlin + // check maven central for the latest version + implementation("net.java.dev.jna:jna-graalvm:5.15.0") +``` + +That's it! You should see `com.sun.jna.JavaNativeAccess` show up during your native image build: + +``` + ======================================================================================================================== + GraalVM Native Image: Generating 'graalvm-native-static-jna' (executable)... + ======================================================================================================================== + For detailed information and explanations on the build output, visit: + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md + ------------------------------------------------------------------------------------------------------------------------ + [1/8] Initializing... (2.7s @ 0.18GB) + Java version: 22.0.1+8, vendor version: Oracle GraalVM 22.0.1+8.1 + Graal compiler: optimization level: 2, target machine: armv8-a, PGO: off + C compiler: cc (apple, arm64, 15.0.0) + Garbage collector: Serial GC (max heap size: 80% of RAM) + 3 user-specific feature(s): + - com.oracle.svm.thirdparty.gson.GsonFeature +→ - com.sun.jna.JavaNativeAccess: Enables access to JNA at runtime on SubstrateVM + ... +``` + +## Manual Configuration + +The configuration produced by the automatic route is generally identical to the configuration needed to run your app, but you can also set it up manually instead. + +**Omit the `jna-graalvm.jar` dependency** + +If you include it on your Native Image classpath at build-time, the `JavaNativeAccess` feature is added automatically. If you want to manually configure JNI for JNA, don't include it on your classpath. + +> [!WARNING] +> These configurations are provided on a best-effort basis and may be different for your app. + +### Specify JNI configurations + +If you don't already have one, create a [`jni-config.json`](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/JNI/#reflection-metadata) file within your Java app, and configure Native Image to find it. + +Then, add the configurations below to your `jni-config.json`. + +

+Click to show JSON configuration entries +
+[
+  {
+  "name":"com.sun.jna.Callback",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.CallbackReference",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[{"name":"getCallback","parameterTypes":["java.lang.Class","com.sun.jna.Pointer","boolean"] }, {"name":"getFunctionPointer","parameterTypes":["com.sun.jna.Callback","boolean"] }, {"name":"getNativeString","parameterTypes":["java.lang.Object","boolean"] }, {"name":"initializeThread","parameterTypes":["com.sun.jna.Callback","com.sun.jna.CallbackReference$AttachOptions"] }]
+},
+{
+  "name":"com.sun.jna.CallbackReference$AttachOptions",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.FromNativeConverter",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[{"name":"nativeType","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.jna.IntegerType",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "fields":[{"name":"value", "allowWrite":true}]
+},
+{
+  "name":"com.sun.jna.JNIEnv",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.Native",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[
+    {"name":"dispose","parameterTypes":[] },
+    {"name":"fromNative","parameterTypes":["com.sun.jna.FromNativeConverter","java.lang.Object","java.lang.reflect.Method"] },
+    {"name":"fromNative","parameterTypes":["java.lang.Class","java.lang.Object"] },
+    {"name":"fromNative","parameterTypes":["java.lang.reflect.Method","java.lang.Object"] },
+    {"name":"nativeType","parameterTypes":["java.lang.Class"] },
+    {"name":"toNative","parameterTypes":["com.sun.jna.ToNativeConverter","java.lang.Object"]},
+    {"name":"open","parameterTypes":["java.lang.String","java.lang.Integer"]},
+    {"name":"close","parameterTypes":["java.lang.Long"]},
+    {"name":"findSymbol","parameterTypes":["java.lang.Long","java.lang.String"]}
+  ]
+},
+{
+  "name":"com.sun.jna.Native$ffi_callback",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[{"name":"invoke","parameterTypes":["long","long","long"] }]
+},
+{
+  "name":"com.sun.jna.NativeLong",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.NativeMapped",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[{"name":"toNative","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.jna.Pointer",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "fields":[{"name":"peer", "allowWrite":true}],
+  "methods":[{"name":"","parameterTypes":["long"] }]
+},
+{
+  "name":"com.sun.jna.PointerType",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "fields":[{"name":"pointer", "allowWrite":true}]
+},
+{
+  "name":"com.sun.jna.Structure",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "fields":[{"name":"memory", "allowWrite":true}, {"name":"typeInfo", "allowWrite":true}],
+  "methods":[{"name":"autoRead","parameterTypes":[] }, {"name":"autoWrite","parameterTypes":[] }, {"name":"getTypeInfo","parameterTypes":[] }, {"name":"getTypeInfo","parameterTypes":["java.lang.Object"] }, {"name":"newInstance","parameterTypes":["java.lang.Class"] }, {"name":"newInstance","parameterTypes":["java.lang.Class","long"] }, {"name":"newInstance","parameterTypes":["java.lang.Class","com.sun.jna.Pointer"] }]
+},
+{
+  "name":"com.sun.jna.Structure$ByValue",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.Structure$FFIType",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.Structure$FFIType$FFITypes",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "fields":[{"name":"ffi_type_double", "allowWrite":true}, {"name":"ffi_type_float", "allowWrite":true}, {"name":"ffi_type_longdouble", "allowWrite":true}, {"name":"ffi_type_pointer", "allowWrite":true}, {"name":"ffi_type_sint16", "allowWrite":true}, {"name":"ffi_type_sint32", "allowWrite":true}, {"name":"ffi_type_sint64", "allowWrite":true}, {"name":"ffi_type_sint8", "allowWrite":true}, {"name":"ffi_type_uint16", "allowWrite":true}, {"name":"ffi_type_uint32", "allowWrite":true}, {"name":"ffi_type_uint64", "allowWrite":true}, {"name":"ffi_type_uint8", "allowWrite":true}, {"name":"ffi_type_void", "allowWrite":true}]
+},
+{
+  "name":"com.sun.jna.WString",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true,
+  "methods":[{"name":"","parameterTypes":["java.lang.String"] }]
+},
+{
+  "name":"com.sun.jna.ptr.PointerByReference",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+},
+{
+  "name":"com.sun.jna.platform.mac.CoreFoundation$CFStringRef",
+  "allDeclaredMethods":true,
+  "allPublicMethods":true,
+  "allDeclaredConstructors":true,
+  "allPublicConstructors":true
+}
+]
+
+
+ +### Specify proxy configurations + +If you don't already have one, create a [`proxy-config.json`](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#dynamic-proxy) file within your Java app, and configure Native Image to find it. + +Then, add the configurations below to your `proxy-config.json`. + +
+Click to show JSON configuration entries +
+[
+  {
+    "interfaces":["com.sun.jna.Callback"]
+  },
+  {
+    "interfaces":["com.sun.jna.Library"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.unix.LibC"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.linux.Udev"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.linux.LibRT"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.mac.SystemB"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.mac.IOKit"]
+  },
+  {
+    "interfaces":["com.sun.jna.platform.mac.CoreFoundation"]
+  },
+  {
+    "interfaces":["com.sun.jna.win32.StdCallLibrary"]
+  }
+]
+
+
diff --git a/lib/gvm/SubstrateStaticJNA.java b/lib/gvm/SubstrateStaticJNA.java new file mode 100644 index 000000000..fa4e974aa --- /dev/null +++ b/lib/gvm/SubstrateStaticJNA.java @@ -0,0 +1,55 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna; + +import org.graalvm.nativeimage.hosted.Feature; + +/** + * Feature for use at build time on GraalVM, which enables static JNI support for JNA. + * + *

This "Feature" implementation is discovered on the classpath, via the argument file at native-image.properties. + * At build-time, the feature is registered with the Native Image compiler. + * + *

If JNA is detected on the classpath, and if static JNI is enabled, the feature is enabled, and the JNA library is + * initialized and configured for native access support. + * + *

This class extends the base {@link com.sun.jna.JavaNativeAccess} feature by providing JNA's JNI layer statically, + * so that no library unpacking step needs to take place. + */ +public final class SubstrateStaticJNA extends AbstractJNAFeature { + @Override + public String getDescription() { + return "Enables optimized static access to JNA at runtime"; + } + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return access.findClassByName(JavaNativeAccess.NATIVE_LAYOUT) != null; + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + // + } +} diff --git a/lib/gvm/native-image.properties b/lib/gvm/native-image.properties new file mode 100644 index 000000000..27763ef53 --- /dev/null +++ b/lib/gvm/native-image.properties @@ -0,0 +1,24 @@ +# Copyright (c) 2015 Adam Marcionek, All Rights Reserved +# +# The contents of this file is dual-licensed under 2 +# alternative Open Source/Free licenses: LGPL 2.1 or later and +# Apache License 2.0. (starting with JNA version 4.0.0). +# +# You can freely decide which license you want to apply to +# the project. +# +# You may obtain a copy of the LGPL License at: +# +# http://www.gnu.org/licenses/licenses.html +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "LGPL2.1". +# +# You may obtain a copy of the Apache License at: +# +# http://www.apache.org/licenses/ +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "AL2.0". + +Args = --features=com.sun.jna.JavaNativeAccess diff --git a/native/Makefile b/native/Makefile index 70078089f..12f72264e 100644 --- a/native/Makefile +++ b/native/Makefile @@ -73,6 +73,7 @@ FFI_ENV=CC="$(CC)" CFLAGS="$(COPT) $(CDEBUG) -DFFI_STATIC_BUILD" CPPFLAGS="$(CDE FFI_CONFIG=--enable-static --disable-shared --with-pic=yes endif LIBRARY=$(BUILD)/$(LIBPFX)jnidispatch$(JNISFX) +LIBRARY_STATIC=$(BUILD)/$(LIBPFX)jnidispatch$(ARSFX) TESTLIB=$(BUILD)/$(LIBPFX)testlib$(LIBSFX) TESTLIB_JAR=$(BUILD)/$(LIBPFX)testlib-jar$(LIBSFX) TESTLIB_PATH=$(BUILD)/$(LIBPFX)testlib-path$(LIBSFX) @@ -85,6 +86,7 @@ LIBSFX=.so ARSFX=.a JNISFX=$(LIBSFX) CC=gcc +AR=ar LD=$(CC) LIBS= # Default to Sun recommendations for JNI compilation @@ -473,7 +475,7 @@ else $(CC) $(CFLAGS) $(LOC_CC_OPTS) -c $< $(COUT) endif -all: $(LIBRARY) $(TESTLIB) $(TESTLIB2) $(TESTLIB_JAR) $(TESTLIB_PATH) $(TESTLIB_TRUNC) +all: $(LIBRARY) $(LIBRARY_STATIC) $(TESTLIB) $(TESTLIB2) $(TESTLIB_JAR) $(TESTLIB_PATH) $(TESTLIB_TRUNC) install: mkdir $(INSTALLDIR) @@ -495,6 +497,9 @@ $(LIBRARY): $(JNIDISPATCH_OBJS) $(FFI_LIB) $(LD) $(LDFLAGS) $(JNIDISPATCH_OBJS) $(FFI_LIB) $(LIBS) $(STRIP) $@ +$(LIBRARY_STATIC): $(JNIDISPATCH_OBJS) $(FFI_LIB) + $(AR) rcs $@ $(JNIDISPATCH_OBJS) + $(TESTLIB): $(BUILD)/testlib.o $(LD) $(LDFLAGS) $< $(LIBS) diff --git a/pom-jna-graalvm.xml b/pom-jna-graalvm.xml new file mode 100644 index 000000000..d1c71c753 --- /dev/null +++ b/pom-jna-graalvm.xml @@ -0,0 +1,87 @@ + + 4.0.0 + + net.java.dev.jna + jna-graalvm + TEMPLATE + jar + + Java Native Access + Java Native Access + https://github.com/java-native-access/jna + + + + LGPL-2.1-or-later + https://www.gnu.org/licenses/old-licenses/lgpl-2.1 + repo + + Java Native Access (JNA) is licensed under the LGPL, version 2.1 or + later, or the Apache License, version 2.0. You can freely decide which + license you want to apply to the project. + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + Java Native Access (JNA) is licensed under the LGPL, version 2.1 or + later, or the Apache License, version 2.0. You can freely decide which + license you want to apply to the project. + + + + + + scm:git:https://github.com/java-native-access/jna + scm:git:ssh://git@github.com/java-native-access/jna.git + https://github.com/java-native-access/jna + + + + + twall + Timothy Wall + + Owner + + + + mblaesing@doppel-helix.eu + Matthias Bläsing + https://github.com/matthiasblaesing/ + + Developer + + + + sam@elide.dev + Sam Gammon + https://github.com/sgammon/ + + Developer + + + + dario@elide.dev + Dario Valdespino + https://github.com/darvld/ + + Developer + + + + + + + org.graalvm.sdk + nativeimage + GRAALVM_VERSION + + + + diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 000000000..07512e3c1 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,5 @@ +# JNA Samples + +This directory contains sample projects that use JNA in different ways. See below for a list of available samples: + +- **GraalVM Native JNA:** Builds a GraalVM native image using JNA features with Gradle. diff --git a/samples/graalvm-native-jna/.gitignore b/samples/graalvm-native-jna/.gitignore new file mode 100644 index 000000000..12eb6a96f --- /dev/null +++ b/samples/graalvm-native-jna/.gitignore @@ -0,0 +1,2 @@ +/.gradle +/build diff --git a/samples/graalvm-native-jna/README.md b/samples/graalvm-native-jna/README.md new file mode 100644 index 000000000..72dae1231 --- /dev/null +++ b/samples/graalvm-native-jna/README.md @@ -0,0 +1,3 @@ +# JNA Sample: GraalVM Native Image + +This directory contains a sample Gradle project which uses JNA with [GraalVM](https://graalvm.org/). The project builds a [native image](https://www.graalvm.org/latest/reference-manual/native-image/) which uses JNA features, powered by JNA's integration library for Substrate. diff --git a/samples/graalvm-native-jna/build.gradle.kts b/samples/graalvm-native-jna/build.gradle.kts new file mode 100644 index 000000000..c6ed78815 --- /dev/null +++ b/samples/graalvm-native-jna/build.gradle.kts @@ -0,0 +1,88 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +plugins { + java + application + alias(libs.plugins.graalvm) +} + +application { + mainClass = "com.example.JnaNative" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(22) + vendor = JvmVendorSpec.GRAAL_VM + } +} + +dependencies { + implementation(libs.bundles.jna) + implementation(libs.bundles.graalvm.api) + nativeImageClasspath(libs.jna.graalvm) +} + +val nativeImageDebug: String by properties + +graalvmNative { + testSupport = true + toolchainDetection = false + + binaries { + named("main") { + buildArgs.addAll(if (nativeImageDebug != "true") emptyList() else listOf( + "--verbose", + "--debug-attach", + "-H:+UnlockExperimentalVMOptions", + "-H:+JNIEnhancedErrorCodes", + "-H:+SourceLevelDebug", + "-H:-DeleteLocalSymbols", + "-H:-RemoveUnusedSymbols", + "-H:+PreserveFramePointer", + "-H:+ReportExceptionStackTraces", + )) + } + } +} + +// Allow the outer Ant build to override the version of JNA or GraalVM. +// These properties are used in JNA's CI and don't need to be in projects that use JNA. + +val jnaVersion: String by properties +val graalvmVersion: String by properties +val overrides = jnaVersion.isNotBlank() || graalvmVersion.isNotBlank() + +if (overrides) configurations.all { + resolutionStrategy.eachDependency { + if (requested.group == "net.java.dev.jna") { + useVersion(jnaVersion) + because("overridden by ant build") + } + if (requested.group == "org.graalvm") { + useVersion(graalvmVersion) + because("overridden by ant build") + } + } +} diff --git a/samples/graalvm-native-jna/gradle.properties b/samples/graalvm-native-jna/gradle.properties new file mode 100644 index 000000000..d88e8c59d --- /dev/null +++ b/samples/graalvm-native-jna/gradle.properties @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Adam Marcionek, All Rights Reserved +# +# The contents of this file is dual-licensed under 2 +# alternative Open Source/Free licenses: LGPL 2.1 or later and +# Apache License 2.0. (starting with JNA version 4.0.0). +# +# You can freely decide which license you want to apply to +# the project. +# +# You may obtain a copy of the LGPL License at: +# +# http://www.gnu.org/licenses/licenses.html +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "LGPL2.1". +# +# You may obtain a copy of the Apache License at: +# +# http://www.apache.org/licenses/ +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "AL2.0". + +# These properties are left blank, to be filled in by the outer Ant build. +# When given a value, these versions override the values declared in the version catalog. +jnaVersion= +graalvmVersion= +nativeImageDebug= diff --git a/samples/graalvm-native-jna/gradle/libs.versions.toml b/samples/graalvm-native-jna/gradle/libs.versions.toml new file mode 100644 index 000000000..81d0b8e67 --- /dev/null +++ b/samples/graalvm-native-jna/gradle/libs.versions.toml @@ -0,0 +1,57 @@ +# Copyright (c) 2015 Adam Marcionek, All Rights Reserved +# +# The contents of this file is dual-licensed under 2 +# alternative Open Source/Free licenses: LGPL 2.1 or later and +# Apache License 2.0. (starting with JNA version 4.0.0). +# +# You can freely decide which license you want to apply to +# the project. +# +# You may obtain a copy of the LGPL License at: +# +# http://www.gnu.org/licenses/licenses.html +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "LGPL2.1". +# +# You may obtain a copy of the Apache License at: +# +# http://www.apache.org/licenses/ +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "AL2.0". + +[versions] +jna = "5.15.0-SNAPSHOT" +graalvm = "24.0.1" +graalvm-plugin = "0.10.2" + +[plugins] +graalvm = { id = "org.graalvm.buildtools.native", version.ref = "graalvm-plugin" } + +[libraries] +jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } +jna-graalvm = { group = "net.java.dev.jna", name = "jna-graalvm", version.ref = "jna" } +jna-jpms = { group = "net.java.dev.jna", name = "jna-jpms", version.ref = "jna" } +jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } +jna-platform-jpms = { group = "net.java.dev.jna", name = "jna-platform-jpms", version.ref = "jna" } +graalvm-nativeimage-svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalvm" } +graalvm-sdk-nativeimage = { group = "org.graalvm.sdk", name = "nativeimage", version.ref = "graalvm" } +graalvm-sdk-jniutils = { group = "org.graalvm.sdk", name = "jniutils", version.ref = "graalvm" } + +[bundles] + +jna = [ + "jna", + "jna-platform" +] + +jna-jpms = [ + "jna-jpms", + "jna-platform-jpms" +] + +graalvm-api = [ + "graalvm-sdk-nativeimage", + "graalvm-sdk-jniutils" +] diff --git a/samples/graalvm-native-jna/gradle/wrapper/gradle-wrapper.jar b/samples/graalvm-native-jna/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/graalvm-native-jna/gradlew.bat b/samples/graalvm-native-jna/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/samples/graalvm-native-jna/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/graalvm-native-jna/settings.gradle.kts b/samples/graalvm-native-jna/settings.gradle.kts new file mode 100644 index 000000000..27052984f --- /dev/null +++ b/samples/graalvm-native-jna/settings.gradle.kts @@ -0,0 +1,42 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version ("0.8.0") +} + +dependencyResolutionManagement { + repositoriesMode = RepositoriesMode.PREFER_PROJECT + + repositories { + mavenLocal() + mavenCentral() + } +} diff --git a/samples/graalvm-native-jna/src/main/java/com/example/JnaNative.java b/samples/graalvm-native-jna/src/main/java/com/example/JnaNative.java new file mode 100644 index 000000000..5265bbaf9 --- /dev/null +++ b/samples/graalvm-native-jna/src/main/java/com/example/JnaNative.java @@ -0,0 +1,45 @@ +/* Copyright (c) 2007-2015 Timothy Wall, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.example; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; + +public final class JnaNative { + public interface CLibrary extends Library { + CLibrary INSTANCE = (CLibrary) + Native.load((Platform.isWindows() ? "msvcrt" : "c"), + CLibrary.class); + + void printf(String format, Object... args); + } + + public static void main(String[] args) { + System.out.println("Hello, JNA!"); + for (int i=0;i < args.length;i++) { + CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]); + } + } +} From 2611c4169f4bf93333ddfd3c948295d052db86ff Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Wed, 12 Jun 2024 20:32:30 -0700 Subject: [PATCH 2/4] Static-compatible init of JNA native layer When operating under static linkage in SVM (Native Image), JNA's `JNI_OnLoad` hooks are not run. We need to sanity-check at the first JNI border and run static initialization manually. Additionally, `JNI_OnLoad` should be provided in static contexts as `JNI_OnLoad_jnidispatch`. This changeset fixes both issues. Signed-off-by: Sam Gammon Signed-off-by: Dario Valdespino --- common.xml | 2 +- native/dispatch.c | 67 +++++++++++++++++++++++++++---------- src/com/sun/jna/Native.java | 16 +++++++++ 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/common.xml b/common.xml index 7e26ebcc0..bc1c63b32 100644 --- a/common.xml +++ b/common.xml @@ -27,7 +27,7 @@ - + diff --git a/native/dispatch.c b/native/dispatch.c index 8a03a643b..fc9c4a0a0 100644 --- a/native/dispatch.c +++ b/native/dispatch.c @@ -3314,6 +3314,20 @@ is_protected() { return JNI_FALSE; } +jint initializeJnaStatics(JNIEnv *env) { + int result = JNI_VERSION_1_4; + const char* err; + if ((err = JNA_init(env)) != NULL) { + fprintf(stderr, "JNA: Problems loading core IDs: %s\n", err); + result = 0; + } + else if ((err = JNA_callback_init(env)) != NULL) { + fprintf(stderr, "JNA: Problems loading callback IDs: %s\n", err); + result = 0; + } + return result; +} + JNIEXPORT jboolean JNICALL Java_com_sun_jna_Native_isProtected(JNIEnv *UNUSED(env), jclass UNUSED(classp)) { return is_protected(); @@ -3335,6 +3349,10 @@ Java_com_sun_jna_Native_getNativeVersion(JNIEnv *env, jclass UNUSED(classp)) { #ifndef JNA_JNI_VERSION #define JNA_JNI_VERSION "undefined" #endif + // sanity check initialization + if (classString == NULL) { + initializeJnaStatics(env); + } return newJavaString(env, JNA_JNI_VERSION, CHARSET_UTF8); } @@ -3346,39 +3364,43 @@ Java_com_sun_jna_Native_getAPIChecksum(JNIEnv *env, jclass UNUSED(classp)) { return newJavaString(env, CHECKSUM, CHARSET_UTF8); } -JNIEXPORT jint JNICALL -JNI_OnLoad(JavaVM *jvm, void *UNUSED(reserved)) { +JNIEXPORT jboolean JNICALL Java_com_sun_jna_Native_isStaticEnabled(JNIEnv * UNUSED(env), jclass UNUSED(classp)) { + return JNI_TRUE; +} + +JNIEXPORT jint JNICALL Java_com_sun_jna_Native_initializeStatic(JNIEnv *env, jclass UNUSED(classp)) { + return initializeJnaStatics(env); +} + +jint setupJna(JavaVM *jvm) { JNIEnv* env; - int result = JNI_VERSION_1_4; int attached = (*jvm)->GetEnv(jvm, (void *)&env, JNI_VERSION_1_4) == JNI_OK; - const char* err; - if (!attached) { if ((*jvm)->AttachCurrentThread(jvm, (void *)&env, NULL) != JNI_OK) { fprintf(stderr, "JNA: Can't attach native thread to VM on load\n"); return 0; } } - - if ((err = JNA_init(env)) != NULL) { - fprintf(stderr, "JNA: Problems loading core IDs: %s\n", err); - result = 0; - } - else if ((err = JNA_callback_init(env)) != NULL) { - fprintf(stderr, "JNA: Problems loading callback IDs: %s\n", err); - result = 0; - } + int result = initializeJnaStatics(env); if (!attached) { if ((*jvm)->DetachCurrentThread(jvm) != 0) { fprintf(stderr, "JNA: could not detach thread on initial load\n"); } } - return result; } -JNIEXPORT void JNICALL -JNI_OnUnload(JavaVM *vm, void *UNUSED(reserved)) { +JNIEXPORT jint JNICALL +JNI_OnLoad_jnidispatch(JavaVM *jvm, void *UNUSED(reserved)) { + return setupJna(jvm); +} + +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *jvm, void *UNUSED(reserved)) { + return setupJna(jvm); +} + +void unloadJna(JavaVM *vm) { jobject* refs[] = { &classObject, &classClass, &classMethod, &classString, @@ -3443,6 +3465,17 @@ JNI_OnUnload(JavaVM *vm, void *UNUSED(reserved)) { } } +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *jvm, void *UNUSED(reserved)) { + unloadJna(jvm); +} + + +JNIEXPORT void JNICALL +JNI_OnUnload_jnidispatch(JavaVM *jvm, void *UNUSED(reserved)) { + unloadJna(jvm); +} + JNIEXPORT void JNICALL Java_com_sun_jna_Native_unregister(JNIEnv *env, jclass UNUSED(ncls), jclass cls, jlongArray handles) { jlong* data = (*env)->GetLongArrayElements(env, handles, NULL); diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 7edeb05c5..6a9eec9a8 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.lang.System; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; @@ -947,6 +948,18 @@ private static void loadNativeDispatchLibrary() { } String libName = System.getProperty("jna.boot.library.name", "jnidispatch"); + + // if in static init mode, load the library by name and force-run the initializer + if (!Boolean.getBoolean("jna.skipStatic")) { + try { + System.loadLibrary(libName); + assert isStaticEnabled(); + assert initializeStatic() == 0x00010004; + return; + } catch (UnsatisfiedLinkError err) { + // continue + } + } String bootPath = System.getProperty("jna.boot.library.path"); if (bootPath != null) { // String.split not available in 1.4 @@ -1198,6 +1211,8 @@ else if (!Boolean.getBoolean("jna.nounpack")) { **/ private static native int sizeof(int type); + private static synchronized native boolean isStaticEnabled(); + private static synchronized native int initializeStatic(); private static native String getNativeVersion(); private static native String getAPIChecksum(); @@ -2020,6 +2035,7 @@ public static void main(String[] args) { ? pkg.getImplementationVersion() : DEFAULT_BUILD; if (version == null) version = DEFAULT_BUILD; System.out.println("Version: " + version); + System.out.println(" Static: " + isStaticEnabled()); System.out.println(" Native: " + getNativeVersion() + " (" + getAPIChecksum() + ")"); System.out.println(" Prefix: " + Platform.RESOURCE_PREFIX); From bfc01f851980092c7014024a709787e41ea2a9e4 Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Wed, 12 Jun 2024 20:43:17 -0700 Subject: [PATCH 3/4] Avoid polymorphic dispatch over JNI When linked statically on GraalVM, JNI symbols declared in the overloaded form cannot be resolved. Luckily, all of `Native`'s callsites are in `Pointer` or itself, and all `native` methods of `Native` are non-public. This PR adjusts the JNA C API to avoid using overloaded `read`, `write`, or `getDirectByteBuffer`. Callsites are amended in `Pointer` accordingly. Signed-off-by: Sam Gammon Signed-off-by: Dario Valdespino --- common.xml | 2 +- native/dispatch.c | 58 ++++++++++++++++++------------------ src/com/sun/jna/Native.java | 28 ++++++++--------- src/com/sun/jna/Pointer.java | 28 ++++++++--------- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/common.xml b/common.xml index bc1c63b32..eada46b5d 100644 --- a/common.xml +++ b/common.xml @@ -27,7 +27,7 @@ - + diff --git a/native/dispatch.c b/native/dispatch.c index fc9c4a0a0..98c971352 100644 --- a/native/dispatch.c +++ b/native/dispatch.c @@ -2334,10 +2334,10 @@ Java_com_sun_jna_Native_findSymbol(JNIEnv *env, jclass UNUSED(cls), /* * Class: com_sun_jna_Native - * Method: write + * Method: writeBytes * Signature: (Lcom/sun/jna/Pointer;JJ[BII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3BII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeBytes (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jbyteArray arr, jint off, jint n) { PSTART(); @@ -2347,10 +2347,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeChars * Signature: (Lcom/sun/jna/Pointer;JJ[CII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3CII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeChars (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jcharArray arr, jint off, jint n) { getChars(env, (wchar_t*)L2A(addr + offset), arr, off, n); @@ -2358,10 +2358,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeDoubles * Signature: (Lcom/sun/jna/Pointer;JJ[DII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3DII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeDoubles (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jdoubleArray arr, jint off, jint n) { PSTART(); @@ -2371,10 +2371,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeFloats * Signature: (Lcom/sun/jna/Pointer;JJ[FII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3FII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeFloats (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jfloatArray arr, jint off, jint n) { PSTART(); @@ -2384,10 +2384,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeInts * Signature: (Lcom/sun/jna/Pointer;JJ[III)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3III +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeInts (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jintArray arr, jint off, jint n) { PSTART(); @@ -2397,10 +2397,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeLongs * Signature: (Lcom/sun/jna/Pointer;JJ[JII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3JII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeLongs (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jlongArray arr, jint off, jint n) { PSTART(); @@ -2410,10 +2410,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3 /* * Class: com_sun_jna_Native - * Method: write + * Method: writeShorts * Signature: (Lcom/sun/jna/Pointer;JJ[SII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_write__Lcom_sun_jna_Pointer_2JJ_3SII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_writeShorts (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jshortArray arr, jint off, jint n) { PSTART(); @@ -2445,10 +2445,10 @@ JNIEXPORT jlong JNICALL Java_com_sun_jna_Native_indexOf /* * Class: com_sun_jna_Native - * Method: read + * Method: readBytes * Signature: (Lcom/sun/jna/Pointer;JJ[BII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3BII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readBytes (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jbyteArray arr, jint off, jint n) { PSTART(); @@ -2458,10 +2458,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3B /* * Class: com_sun_jna_Native - * Method: read + * Method: readChars * Signature: (Lcom/sun/jna/Pointer;JJ[CII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3CII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readChars (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jcharArray arr, jint off, jint n) { setChars(env, (wchar_t*)L2A(addr + offset), arr, off, n); @@ -2469,10 +2469,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3C /* * Class: com_sun_jna_Native - * Method: read + * Method: readDoubles * Signature: (Lcom/sun/jna/Pointer;JJ[DII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3DII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readDoubles (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jdoubleArray arr, jint off, jint n) { PSTART(); @@ -2482,10 +2482,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3D /* * Class: com_sun_jna_Native - * Method: read + * Method: readFloats * Signature: (Lcom/sun/jna/Pointer;JJ[FII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3FII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readFloats (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jfloatArray arr, jint off, jint n) { PSTART(); @@ -2495,10 +2495,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3F /* * Class: com_sun_jna_Native - * Method: read + * Method: readInts * Signature: (Lcom/sun/jna/Pointer;JJ[III)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3III +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readInts (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jintArray arr, jint off, jint n) { PSTART(); @@ -2508,10 +2508,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3I /* * Class: com_sun_jna_Native - * Method: read + * Method: readLongs * Signature: (Lcom/sun/jna/Pointer;JJ[JII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3JII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readLongs (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jlongArray arr, jint off, jint n) { PSTART(); @@ -2521,10 +2521,10 @@ JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3J /* * Class: com_sun_jna_Native - * Method: read + * Method: readShorts * Signature: (Lcom/sun/jna/Pointer;JJ[SII)V */ -JNIEXPORT void JNICALL Java_com_sun_jna_Native_read__Lcom_sun_jna_Pointer_2JJ_3SII +JNIEXPORT void JNICALL Java_com_sun_jna_Native_readShorts (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jshortArray arr, jint off, jint n) { PSTART(); @@ -2576,7 +2576,7 @@ JNIEXPORT jlong JNICALL Java_com_sun_jna_Native__1getPointer * Method: getDirectByteBuffer * Signature: (Lcom/sun/jna/Pointer;JJJ)Ljava/nio/ByteBuffer; */ -JNIEXPORT jobject JNICALL Java_com_sun_jna_Native_getDirectByteBuffer__Lcom_sun_jna_Pointer_2JJJ +JNIEXPORT jobject JNICALL Java_com_sun_jna_Native_getDirectByteBuffer (JNIEnv *env, jclass UNUSED(cls), jobject UNUSED(pointer), jlong addr, jlong offset, jlong length) { #ifdef NO_NIO_BUFFERS diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 6a9eec9a8..c98dc6a22 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -2229,33 +2229,33 @@ static long open(String name) { */ static native long indexOf(Pointer pointer, long baseaddr, long offset, byte value); - static native void read(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length); + static native void readBytes(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, short[] buf, int index, int length); + static native void readShorts(Pointer pointer, long baseaddr, long offset, short[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, char[] buf, int index, int length); + static native void readChars(Pointer pointer, long baseaddr, long offset, char[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, int[] buf, int index, int length); + static native void readInts(Pointer pointer, long baseaddr, long offset, int[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, long[] buf, int index, int length); + static native void readLongs(Pointer pointer, long baseaddr, long offset, long[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, float[] buf, int index, int length); + static native void readFloats(Pointer pointer, long baseaddr, long offset, float[] buf, int index, int length); - static native void read(Pointer pointer, long baseaddr, long offset, double[] buf, int index, int length); + static native void readDoubles(Pointer pointer, long baseaddr, long offset, double[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length); + static native void writeBytes(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, short[] buf, int index, int length); + static native void writeShorts(Pointer pointer, long baseaddr, long offset, short[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, char[] buf, int index, int length); + static native void writeChars(Pointer pointer, long baseaddr, long offset, char[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, int[] buf, int index, int length); + static native void writeInts(Pointer pointer, long baseaddr, long offset, int[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, long[] buf, int index, int length); + static native void writeLongs(Pointer pointer, long baseaddr, long offset, long[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, float[] buf, int index, int length); + static native void writeFloats(Pointer pointer, long baseaddr, long offset, float[] buf, int index, int length); - static native void write(Pointer pointer, long baseaddr, long offset, double[] buf, int index, int length); + static native void writeDoubles(Pointer pointer, long baseaddr, long offset, double[] buf, int index, int length); static native byte getByte(Pointer pointer, long baseaddr, long offset); diff --git a/src/com/sun/jna/Pointer.java b/src/com/sun/jna/Pointer.java index 444035f51..3dad56909 100644 --- a/src/com/sun/jna/Pointer.java +++ b/src/com/sun/jna/Pointer.java @@ -137,7 +137,7 @@ public long indexOf(long offset, byte value) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, byte[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readBytes(this, this.peer, offset, buf, index, length); } /** @@ -150,7 +150,7 @@ public void read(long offset, byte[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, short[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readShorts(this, this.peer, offset, buf, index, length); } /** @@ -163,7 +163,7 @@ public void read(long offset, short[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, char[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readChars(this, this.peer, offset, buf, index, length); } /** @@ -176,7 +176,7 @@ public void read(long offset, char[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, int[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readInts(this, this.peer, offset, buf, index, length); } /** @@ -189,7 +189,7 @@ public void read(long offset, int[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, long[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readLongs(this, this.peer, offset, buf, index, length); } /** @@ -202,7 +202,7 @@ public void read(long offset, long[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, float[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readFloats(this, this.peer, offset, buf, index, length); } /** @@ -215,7 +215,7 @@ public void read(long offset, float[] buf, int index, int length) { * @param length number of elements from native pointer that must be copied */ public void read(long offset, double[] buf, int index, int length) { - Native.read(this, this.peer, offset, buf, index, length); + Native.readDoubles(this, this.peer, offset, buf, index, length); } /** @@ -254,7 +254,7 @@ public void read(long offset, Pointer[] buf, int index, int length) { * copied */ public void write(long offset, byte[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeBytes(this, this.peer, offset, buf, index, length); } /** @@ -268,7 +268,7 @@ public void write(long offset, byte[] buf, int index, int length) { * copied */ public void write(long offset, short[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeShorts(this, this.peer, offset, buf, index, length); } /** @@ -282,7 +282,7 @@ public void write(long offset, short[] buf, int index, int length) { * copied */ public void write(long offset, char[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeChars(this, this.peer, offset, buf, index, length); } /** @@ -296,7 +296,7 @@ public void write(long offset, char[] buf, int index, int length) { * copied */ public void write(long offset, int[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeInts(this, this.peer, offset, buf, index, length); } /** @@ -310,7 +310,7 @@ public void write(long offset, int[] buf, int index, int length) { * copied */ public void write(long offset, long[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeLongs(this, this.peer, offset, buf, index, length); } /** @@ -324,7 +324,7 @@ public void write(long offset, long[] buf, int index, int length) { * copied */ public void write(long offset, float[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeFloats(this, this.peer, offset, buf, index, length); } /** @@ -338,7 +338,7 @@ public void write(long offset, float[] buf, int index, int length) { * copied */ public void write(long offset, double[] buf, int index, int length) { - Native.write(this, this.peer, offset, buf, index, length); + Native.writeDoubles(this, this.peer, offset, buf, index, length); } /** Write the given array of Pointer to native memory. From 874c2727e97089a08a32d12a599b761526edee4b Mon Sep 17 00:00:00 2001 From: Sam Gammon Date: Wed, 12 Jun 2024 20:27:50 -0700 Subject: [PATCH 4/4] Support JNA over Static JNI on GraalVM Implements a new optional linkage feature, called Static JNI, under GraalVM Native Image. With `com.sun.jna.SubstrateStaticJNA` enabled (opt-in), JNA is loaded eagerly at image build time, and then linked against a static copy of `libjnidispatch` at image link-time. The result is that `libjnidispatch.a` is embedded within the final image. No precursor library unpacking step is necessary before using JNA in this circumstance, because JNA's native layer is built directly into the image itself. - feat: implement static jni feature - chore: full gvm ci build - chore: add static jni sample Signed-off-by: Sam Gammon Signed-off-by: Dario Valdespino Co-authored-by: Sam Gammon Co-authored-by: Dario Valdespino --- .github/workflows/graalvm.yaml | 8 +- build.xml | 35 ++- lib/gvm/SubstrateStaticJNA.java | 155 ++++++++++- lib/gvm/native-image.properties | 7 +- samples/README.md | 8 +- samples/graalvm-native-static-jna/.gitignore | 2 + samples/graalvm-native-static-jna/README.md | 10 + .../build.gradle.kts | 93 +++++++ .../gradle.properties | 28 ++ .../gradle/libs.versions.toml | 57 ++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + samples/graalvm-native-static-jna/gradlew | 249 ++++++++++++++++++ samples/graalvm-native-static-jna/gradlew.bat | 92 +++++++ .../settings.gradle.kts | 42 +++ .../src/main/java/com/example/JnaNative.java | 47 ++++ src/com/sun/jna/Native.java | 7 + 17 files changed, 838 insertions(+), 9 deletions(-) create mode 100644 samples/graalvm-native-static-jna/.gitignore create mode 100644 samples/graalvm-native-static-jna/README.md create mode 100644 samples/graalvm-native-static-jna/build.gradle.kts create mode 100644 samples/graalvm-native-static-jna/gradle.properties create mode 100644 samples/graalvm-native-static-jna/gradle/libs.versions.toml create mode 100644 samples/graalvm-native-static-jna/gradle/wrapper/gradle-wrapper.jar create mode 100644 samples/graalvm-native-static-jna/gradle/wrapper/gradle-wrapper.properties create mode 100755 samples/graalvm-native-static-jna/gradlew create mode 100644 samples/graalvm-native-static-jna/gradlew.bat create mode 100644 samples/graalvm-native-static-jna/settings.gradle.kts create mode 100644 samples/graalvm-native-static-jna/src/main/java/com/example/JnaNative.java diff --git a/.github/workflows/graalvm.yaml b/.github/workflows/graalvm.yaml index 98bcf3d3a..66ab1b56e 100644 --- a/.github/workflows/graalvm.yaml +++ b/.github/workflows/graalvm.yaml @@ -30,5 +30,9 @@ jobs: - name: Linux requirements run: sudo apt-get -y install texinfo - uses: gradle/actions/setup-gradle@v3 - - name: "Build: Native Image" - run: ant dist && ant install && ant nativeImage && ant nativeRun + - name: "Build: Compile & Install JNA" + run: ant && ant install + - name: "Build: Native Images (Dynamic JNI)" + run: ant nativeImage && ant nativeRun + - name: "Build: Native Image (Static JNI)" + run: ant nativeImageStatic && ant nativeRunStatic diff --git a/build.xml b/build.xml index a8f295d18..430542648 100644 --- a/build.xml +++ b/build.xml @@ -292,9 +292,9 @@ + - @@ -656,9 +656,6 @@ osname=macosx;processor=aarch64 - - - @@ -1123,6 +1120,8 @@ cd .. + @@ -1135,6 +1134,7 @@ cd .. + @@ -1781,9 +1781,36 @@ cd .. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/gvm/SubstrateStaticJNA.java b/lib/gvm/SubstrateStaticJNA.java index fa4e974aa..35828e4c0 100644 --- a/lib/gvm/SubstrateStaticJNA.java +++ b/lib/gvm/SubstrateStaticJNA.java @@ -23,8 +23,18 @@ */ package com.sun.jna; +import com.oracle.svm.core.jdk.NativeLibrarySupport; +import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.hosted.Feature; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; + /** * Feature for use at build time on GraalVM, which enables static JNI support for JNA. * @@ -36,8 +46,125 @@ * *

This class extends the base {@link com.sun.jna.JavaNativeAccess} feature by providing JNA's JNI layer statically, * so that no library unpacking step needs to take place. + * + * @since 5.15.0 + * @author Sam Gammon (sam@elide.dev) + * @author Dario Valdespino (dario@elide.dev) */ public final class SubstrateStaticJNA extends AbstractJNAFeature { + /** + * Name for the FFI native library used during static linking by Native Image. + */ + private static final String FFI_LINK_NAME = "ffi"; + + /** + * Name for the JNI Dispatch native library used during static linking by Native Image. + */ + private static final String JNA_LINK_NAME = "jnidispatch"; + + /** + * Name prefix used by native functions from the JNI Dispatch library. + */ + private static final String JNA_NATIVE_LAYOUT = "com_sun_jna_Native"; + + /** + * Name of the JNI Dispatch static library on UNIX-based platforms. + */ + private static final String JNI_DISPATCH_UNIX_NAME = "libjnidispatch.a"; + + /** + * Name of the JNI Dispatch static library on Windows. + */ + private static final String JNI_DISPATCH_WINDOWS_NAME = "jnidispatch.lib"; + + /** + * Name of the FFI static library on UNIX-based platforms. + */ + private static final String FFI_UNIX_NAME = "libffi.a"; + + /** + * Name of the FFI static library on Windows. + */ + private static final String FFI_WINDOWS_NAME = "ffi.lib"; + + /** + * Returns the name of the static JNI Dispatch library for the current platform. On UNIX-based systems, + * {@link #JNI_DISPATCH_UNIX_NAME} is used; on Windows, {@link #JNI_DISPATCH_WINDOWS_NAME} is returned instead. + * + * @see #getStaticLibraryResource + * @return The JNI Dispatch library name for the current platform. + */ + private static String getStaticLibraryFileName() { + if (Platform.includedIn(Platform.WINDOWS.class)) return JNI_DISPATCH_WINDOWS_NAME; + if (Platform.includedIn(Platform.LINUX.class)) return JNI_DISPATCH_UNIX_NAME; + if (Platform.includedIn(Platform.DARWIN.class)) return JNI_DISPATCH_UNIX_NAME; + + // If the current platform is not in the Platform class, this code would not run at all + throw new UnsupportedOperationException("Current platform does not support static linking"); + } + + /** + * Returns the name of the static FFI library for the current platform. On UNIX-based systems, + * {@link #FFI_UNIX_NAME} is used; on Windows, {@link #FFI_WINDOWS_NAME} is returned instead. + * + * @see #getStaticLibraryResource + * @return The FFI library name for the current platform. + */ + private static String getFFILibraryFileName() { + if (Platform.includedIn(Platform.WINDOWS.class)) return FFI_WINDOWS_NAME; + if (Platform.includedIn(Platform.LINUX.class)) return FFI_UNIX_NAME; + if (Platform.includedIn(Platform.DARWIN.class)) return FFI_UNIX_NAME; + + // If the current platform is not in the Platform class, this code would not run at all + throw new UnsupportedOperationException("Current platform does not support static FFI"); + } + + /** + * Returns the full path to the static JNI Dispatch library embedded in the JAR, accounting for platform-specific + * library names. + * + * @see #getStaticLibraryFileName() + * @return The JNI Dispatch library resource path for the current platform. + */ + private static String getStaticLibraryResource() { + return "/com/sun/jna/" + com.sun.jna.Platform.RESOURCE_PREFIX + "/" + getStaticLibraryFileName(); + } + + /** + * Returns the full path to the static FFI library which JNA depends on, accounting for platform-specific + * library names. + * + * @see #getFFILibraryFileName() + * @return The FFI library resource path for the current platform. + */ + private static String getFFILibraryResource() { + return "/com/sun/jna/" + com.sun.jna.Platform.RESOURCE_PREFIX + "/" + getFFILibraryFileName(); + } + + /** + * Extracts a library resource and returns the file it was extracted to. + * + * @param resource Resource path for the library to extract. + * @param filename Expected filename for the library. + * @return The extracted library file. + */ + private static File unpackLibrary(String resource, String filename) { + // Unpack the static library from resources so Native Image can use it + File extractedLib; + try { + extractedLib = Native.extractFromResourcePath(resource, Native.class.getClassLoader()); + + // The library is extracted into a file with a `.tmp` name, which will not be picked up by the linker + // We need to rename it first using the platform-specific convention or the build will fail + File platformLib = new File(extractedLib.getParentFile(), filename); + if (!extractedLib.renameTo(platformLib)) throw new IllegalStateException("Renaming extract file failed"); + extractedLib = platformLib; + } catch (IOException e) { + throw new RuntimeException("Failed to extract native dispatch library from resources", e); + } + return extractedLib; + } + @Override public String getDescription() { return "Enables optimized static access to JNA at runtime"; @@ -48,8 +175,34 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { return access.findClassByName(JavaNativeAccess.NATIVE_LAYOUT) != null; } + @Override + public List> getRequiredFeatures() { + return Collections.singletonList(JavaNativeAccess.class); + } + @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - // + var nativeLibraries = NativeLibrarySupport.singleton(); + var platformLibraries = PlatformNativeLibrarySupport.singleton(); + + // Register as a built-in library with Native Image and set the name prefix used by native symbols + nativeLibraries.preregisterUninitializedBuiltinLibrary(JNA_LINK_NAME); + platformLibraries.addBuiltinPkgNativePrefix(JNA_NATIVE_LAYOUT); + + // Extract the main JNA library from the platform-specific resource path; next, extract the FFI + // library it depends on + unpackLibrary(getFFILibraryResource(), getFFILibraryFileName()); + var extractedLib = unpackLibrary(getStaticLibraryResource(), getStaticLibraryFileName()); + + // WARNING: the static JNI linking feature is unstable and may be removed in the future; + // this code uses the access implementation directly in order to register the static library. We + // inform the Native Image compiler that JNA depends on `ffi`, so that it forces it to load first + // when JNA is initialized at image runtime. + var nativeLibsImpl = ((BeforeAnalysisAccessImpl) access).getNativeLibraries(); + nativeLibsImpl.addStaticNonJniLibrary(FFI_LINK_NAME); + nativeLibsImpl.addStaticJniLibrary(JNA_LINK_NAME, FFI_LINK_NAME); + + // Enhance the Native Image lib paths so the injected static libraries are available to the linker + nativeLibsImpl.getLibraryPaths().add(extractedLib.getParentFile().getAbsolutePath()); } } diff --git a/lib/gvm/native-image.properties b/lib/gvm/native-image.properties index 27763ef53..95904edb4 100644 --- a/lib/gvm/native-image.properties +++ b/lib/gvm/native-image.properties @@ -21,4 +21,9 @@ # A copy is also included in the downloadable source code package # containing JNA, in file "AL2.0". -Args = --features=com.sun.jna.JavaNativeAccess +Args = --features=com.sun.jna.JavaNativeAccess \ + -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.hosted.jni=ALL-UNNAMED \ + -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jni=ALL-UNNAMED \ + -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.hosted=ALL-UNNAMED \ + -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.hosted.c=ALL-UNNAMED \ + -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED diff --git a/samples/README.md b/samples/README.md index 07512e3c1..80326eb8d 100644 --- a/samples/README.md +++ b/samples/README.md @@ -2,4 +2,10 @@ This directory contains sample projects that use JNA in different ways. See below for a list of available samples: -- **GraalVM Native JNA:** Builds a GraalVM native image using JNA features with Gradle. +- **[GraalVM Native JNA][0]:** Builds a GraalVM native image using JNA features with Gradle. + +- **[Graalvm Native JNA (Static)][1]:** Uses the [SubstrateStaticJNA](../lib/gvm/SubstrateStaticJNA.java) feature to build + JNA code statically into the Native Image. + +[0]: ./graalvm-native-jna +[1]: ./graalvm-native-static-jna diff --git a/samples/graalvm-native-static-jna/.gitignore b/samples/graalvm-native-static-jna/.gitignore new file mode 100644 index 000000000..12eb6a96f --- /dev/null +++ b/samples/graalvm-native-static-jna/.gitignore @@ -0,0 +1,2 @@ +/.gradle +/build diff --git a/samples/graalvm-native-static-jna/README.md b/samples/graalvm-native-static-jna/README.md new file mode 100644 index 000000000..f8a969548 --- /dev/null +++ b/samples/graalvm-native-static-jna/README.md @@ -0,0 +1,10 @@ +# JNA Sample: GraalVM Native Image (Static) + +This directory contains a sample Gradle project which uses JNA with [GraalVM](https://graalvm.org/). The project builds a +[native image](https://www.graalvm.org/latest/reference-manual/native-image/) which uses JNA features, powered by JNA's integration library for Substrate. + +This sample leverages [Static JNI](https://www.blog.akhil.cc/static-jni) to build JNA and JNA-related user code +directly into the native image. + +Using this technique can optimize startup time and other performance factors, because no dynamic library unpack-and-load +step is required to use JNA. diff --git a/samples/graalvm-native-static-jna/build.gradle.kts b/samples/graalvm-native-static-jna/build.gradle.kts new file mode 100644 index 000000000..6481a3030 --- /dev/null +++ b/samples/graalvm-native-static-jna/build.gradle.kts @@ -0,0 +1,93 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +plugins { + java + application + alias(libs.plugins.graalvm) +} + +application { + mainClass = "com.example.JnaNative" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(22) + vendor = JvmVendorSpec.GRAAL_VM + } +} + +dependencies { + implementation(libs.bundles.jna) + implementation(libs.bundles.graalvm.api) + nativeImageClasspath(libs.jna.graalvm) +} + +val nativeImageDebug: String by properties + +graalvmNative { + testSupport = true + toolchainDetection = false + + binaries { + named("main") { + buildArgs.addAll(listOf( + "--features=com.sun.jna.SubstrateStaticJNA", + ).plus(if (nativeImageDebug != "true") emptyList() else listOf( + "--verbose", + "--debug-attach", + "-J-Xlog:library=info", + "-H:+UnlockExperimentalVMOptions", + "-H:+JNIEnhancedErrorCodes", + "-H:+SourceLevelDebug", + "-H:-DeleteLocalSymbols", + "-H:-RemoveUnusedSymbols", + "-H:+PreserveFramePointer", + "-H:+ReportExceptionStackTraces", + "-H:CCompilerOption=-v", + "-H:NativeLinkerOption=-v", + ))) + } + } +} + +// Allow the outer Ant build to override the version of JNA or GraalVM. +// These properties are used in JNA's CI and don't need to be in projects that use JNA. + +val jnaVersion: String by properties +val graalvmVersion: String by properties +val overrides = jnaVersion.isNotBlank() || graalvmVersion.isNotBlank() + +if (overrides) configurations.all { + resolutionStrategy.eachDependency { + if (requested.group == "net.java.dev.jna") { + useVersion(jnaVersion) + because("overridden by ant build") + } + if (requested.group == "org.graalvm") { + useVersion(graalvmVersion) + because("overridden by ant build") + } + } +} diff --git a/samples/graalvm-native-static-jna/gradle.properties b/samples/graalvm-native-static-jna/gradle.properties new file mode 100644 index 000000000..d88e8c59d --- /dev/null +++ b/samples/graalvm-native-static-jna/gradle.properties @@ -0,0 +1,28 @@ +# Copyright (c) 2015 Adam Marcionek, All Rights Reserved +# +# The contents of this file is dual-licensed under 2 +# alternative Open Source/Free licenses: LGPL 2.1 or later and +# Apache License 2.0. (starting with JNA version 4.0.0). +# +# You can freely decide which license you want to apply to +# the project. +# +# You may obtain a copy of the LGPL License at: +# +# http://www.gnu.org/licenses/licenses.html +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "LGPL2.1". +# +# You may obtain a copy of the Apache License at: +# +# http://www.apache.org/licenses/ +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "AL2.0". + +# These properties are left blank, to be filled in by the outer Ant build. +# When given a value, these versions override the values declared in the version catalog. +jnaVersion= +graalvmVersion= +nativeImageDebug= diff --git a/samples/graalvm-native-static-jna/gradle/libs.versions.toml b/samples/graalvm-native-static-jna/gradle/libs.versions.toml new file mode 100644 index 000000000..81d0b8e67 --- /dev/null +++ b/samples/graalvm-native-static-jna/gradle/libs.versions.toml @@ -0,0 +1,57 @@ +# Copyright (c) 2015 Adam Marcionek, All Rights Reserved +# +# The contents of this file is dual-licensed under 2 +# alternative Open Source/Free licenses: LGPL 2.1 or later and +# Apache License 2.0. (starting with JNA version 4.0.0). +# +# You can freely decide which license you want to apply to +# the project. +# +# You may obtain a copy of the LGPL License at: +# +# http://www.gnu.org/licenses/licenses.html +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "LGPL2.1". +# +# You may obtain a copy of the Apache License at: +# +# http://www.apache.org/licenses/ +# +# A copy is also included in the downloadable source code package +# containing JNA, in file "AL2.0". + +[versions] +jna = "5.15.0-SNAPSHOT" +graalvm = "24.0.1" +graalvm-plugin = "0.10.2" + +[plugins] +graalvm = { id = "org.graalvm.buildtools.native", version.ref = "graalvm-plugin" } + +[libraries] +jna = { group = "net.java.dev.jna", name = "jna", version.ref = "jna" } +jna-graalvm = { group = "net.java.dev.jna", name = "jna-graalvm", version.ref = "jna" } +jna-jpms = { group = "net.java.dev.jna", name = "jna-jpms", version.ref = "jna" } +jna-platform = { group = "net.java.dev.jna", name = "jna-platform", version.ref = "jna" } +jna-platform-jpms = { group = "net.java.dev.jna", name = "jna-platform-jpms", version.ref = "jna" } +graalvm-nativeimage-svm = { group = "org.graalvm.nativeimage", name = "svm", version.ref = "graalvm" } +graalvm-sdk-nativeimage = { group = "org.graalvm.sdk", name = "nativeimage", version.ref = "graalvm" } +graalvm-sdk-jniutils = { group = "org.graalvm.sdk", name = "jniutils", version.ref = "graalvm" } + +[bundles] + +jna = [ + "jna", + "jna-platform" +] + +jna-jpms = [ + "jna-jpms", + "jna-platform-jpms" +] + +graalvm-api = [ + "graalvm-sdk-nativeimage", + "graalvm-sdk-jniutils" +] diff --git a/samples/graalvm-native-static-jna/gradle/wrapper/gradle-wrapper.jar b/samples/graalvm-native-static-jna/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/graalvm-native-static-jna/gradlew.bat b/samples/graalvm-native-static-jna/gradlew.bat new file mode 100644 index 000000000..25da30dbd --- /dev/null +++ b/samples/graalvm-native-static-jna/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/graalvm-native-static-jna/settings.gradle.kts b/samples/graalvm-native-static-jna/settings.gradle.kts new file mode 100644 index 000000000..27052984f --- /dev/null +++ b/samples/graalvm-native-static-jna/settings.gradle.kts @@ -0,0 +1,42 @@ +/* Copyright (c) 2015 Adam Marcionek, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version ("0.8.0") +} + +dependencyResolutionManagement { + repositoriesMode = RepositoriesMode.PREFER_PROJECT + + repositories { + mavenLocal() + mavenCentral() + } +} diff --git a/samples/graalvm-native-static-jna/src/main/java/com/example/JnaNative.java b/samples/graalvm-native-static-jna/src/main/java/com/example/JnaNative.java new file mode 100644 index 000000000..4a341fa80 --- /dev/null +++ b/samples/graalvm-native-static-jna/src/main/java/com/example/JnaNative.java @@ -0,0 +1,47 @@ +/* Copyright (c) 2007-2015 Timothy Wall, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.example; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; + +public final class JnaNative { + static { + System.loadLibrary("jnidispatch"); + } + + public interface CLibrary extends Library { + CLibrary INSTANCE = (CLibrary) + Native.load((Platform.isWindows() ? "msvcrt" : "c"), + CLibrary.class); + + void puts(String value); + } + + public static void main(String[] args) { + System.out.println("Hello, JNA!"); + CLibrary.INSTANCE.puts("Hello from C!"); + } +} diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index c98dc6a22..2d1ea03d2 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -106,6 +106,13 @@ *

Native Library Loading

* Native libraries loaded via {@link #load(Class)} may be found in * several locations. + *

Static Linkage under GraalVM Native Image

+ *

When using Static JNI under GraalVM, JNA's native library must be + * loaded statically; this call happens before all others. For platforms + * that want to force this check to be skipped (for instance, if it is known + * that the library will never ship to Substrate), the property + * jna.skipStatic=true can be set; in this case, the static + * load step is skipped.

* @see Library * @author Todd Fast, todd.fast@sun.com * @author twall@users.sf.net