2323 */
2424package com .sun .jna ;
2525
26+ import com .oracle .svm .core .jdk .NativeLibrarySupport ;
27+ import com .oracle .svm .core .jdk .PlatformNativeLibrarySupport ;
28+ import com .oracle .svm .hosted .FeatureImpl .BeforeAnalysisAccessImpl ;
29+ import org .graalvm .nativeimage .Platform ;
2630import org .graalvm .nativeimage .hosted .Feature ;
2731
32+ import java .io .File ;
33+ import java .io .IOException ;
34+ import java .nio .file .Files ;
35+ import java .util .Collections ;
36+ import java .util .List ;
37+
2838/**
2939 * Feature for use at build time on GraalVM, which enables static JNI support for JNA.
3040 *
3646 *
3747 * <p>This class extends the base {@link com.sun.jna.JavaNativeAccess} feature by providing JNA's JNI layer statically,
3848 * so that no library unpacking step needs to take place.
49+ *
50+ * @since 5.15.0
51+ * @author Sam Gammon ([email protected] ) 52+ * @author Dario Valdespino ([email protected] ) 3953 */
4054public final class SubstrateStaticJNA extends AbstractJNAFeature {
55+ /**
56+ * Name for the FFI native library used during static linking by Native Image.
57+ */
58+ private static final String FFI_LINK_NAME = "ffi" ;
59+
60+ /**
61+ * Name for the JNI Dispatch native library used during static linking by Native Image.
62+ */
63+ private static final String JNA_LINK_NAME = "jnidispatch" ;
64+
65+ /**
66+ * Name prefix used by native functions from the JNI Dispatch library.
67+ */
68+ private static final String JNA_NATIVE_LAYOUT = "com_sun_jna_Native" ;
69+
70+ /**
71+ * Name of the JNI Dispatch static library on UNIX-based platforms.
72+ */
73+ private static final String JNI_DISPATCH_UNIX_NAME = "libjnidispatch.a" ;
74+
75+ /**
76+ * Name of the JNI Dispatch static library on Windows.
77+ */
78+ private static final String JNI_DISPATCH_WINDOWS_NAME = "jnidispatch.lib" ;
79+
80+ /**
81+ * Name of the FFI static library on UNIX-based platforms.
82+ */
83+ private static final String FFI_UNIX_NAME = "libffi.a" ;
84+
85+ /**
86+ * Name of the FFI static library on Windows.
87+ */
88+ private static final String FFI_WINDOWS_NAME = "ffi.lib" ;
89+
90+ /**
91+ * Returns the name of the static JNI Dispatch library for the current platform. On UNIX-based systems,
92+ * {@link #JNI_DISPATCH_UNIX_NAME} is used; on Windows, {@link #JNI_DISPATCH_WINDOWS_NAME} is returned instead.
93+ *
94+ * @see #getStaticLibraryResource
95+ * @return The JNI Dispatch library name for the current platform.
96+ */
97+ private static String getStaticLibraryFileName () {
98+ if (Platform .includedIn (Platform .WINDOWS .class )) return JNI_DISPATCH_WINDOWS_NAME ;
99+ if (Platform .includedIn (Platform .LINUX .class )) return JNI_DISPATCH_UNIX_NAME ;
100+ if (Platform .includedIn (Platform .DARWIN .class )) return JNI_DISPATCH_UNIX_NAME ;
101+
102+ // If the current platform is not in the Platform class, this code would not run at all
103+ throw new UnsupportedOperationException ("Current platform does not support static linking" );
104+ }
105+
106+ /**
107+ * Returns the name of the static FFI library for the current platform. On UNIX-based systems,
108+ * {@link #FFI_UNIX_NAME} is used; on Windows, {@link #FFI_WINDOWS_NAME} is returned instead.
109+ *
110+ * @see #getStaticLibraryResource
111+ * @return The FFI library name for the current platform.
112+ */
113+ private static String getFFILibraryFileName () {
114+ if (Platform .includedIn (Platform .WINDOWS .class )) return FFI_WINDOWS_NAME ;
115+ if (Platform .includedIn (Platform .LINUX .class )) return FFI_UNIX_NAME ;
116+ if (Platform .includedIn (Platform .DARWIN .class )) return FFI_UNIX_NAME ;
117+
118+ // If the current platform is not in the Platform class, this code would not run at all
119+ throw new UnsupportedOperationException ("Current platform does not support static FFI" );
120+ }
121+
122+ /**
123+ * Returns the full path to the static JNI Dispatch library embedded in the JAR, accounting for platform-specific
124+ * library names.
125+ *
126+ * @see #getStaticLibraryFileName()
127+ * @return The JNI Dispatch library resource path for the current platform.
128+ */
129+ private static String getStaticLibraryResource () {
130+ return "/com/sun/jna/" + com .sun .jna .Platform .RESOURCE_PREFIX + "/" + getStaticLibraryFileName ();
131+ }
132+
133+ /**
134+ * Returns the full path to the static FFI library which JNA depends on, accounting for platform-specific
135+ * library names.
136+ *
137+ * @see #getFFILibraryFileName()
138+ * @return The FFI library resource path for the current platform.
139+ */
140+ private static String getFFILibraryResource () {
141+ return "/com/sun/jna/" + com .sun .jna .Platform .RESOURCE_PREFIX + "/" + getFFILibraryFileName ();
142+ }
143+
144+ /**
145+ * Extracts a library resource and returns the file it was extracted to.
146+ *
147+ * @param resource Resource path for the library to extract.
148+ * @param filename Expected filename for the library.
149+ * @return The extracted library file.
150+ */
151+ private static File unpackLibrary (String resource , String filename ) {
152+ // Unpack the static library from resources so Native Image can use it
153+ File extractedLib ;
154+ try {
155+ extractedLib = Native .extractFromResourcePath (resource , Native .class .getClassLoader ());
156+
157+ // The library is extracted into a file with a `.tmp` name, which will not be picked up by the linker
158+ // We need to rename it first using the platform-specific convention or the build will fail
159+ File platformLib = new File (extractedLib .getParentFile (), filename );
160+ if (!extractedLib .renameTo (platformLib )) throw new IllegalStateException ("Renaming extract file failed" );
161+ extractedLib = platformLib ;
162+ } catch (IOException e ) {
163+ throw new RuntimeException ("Failed to extract native dispatch library from resources" , e );
164+ }
165+ return extractedLib ;
166+ }
167+
41168 @ Override
42169 public String getDescription () {
43170 return "Enables optimized static access to JNA at runtime" ;
@@ -48,8 +175,34 @@ public boolean isInConfiguration(IsInConfigurationAccess access) {
48175 return access .findClassByName (JavaNativeAccess .NATIVE_LAYOUT ) != null ;
49176 }
50177
178+ @ Override
179+ public List <Class <? extends Feature >> getRequiredFeatures () {
180+ return Collections .singletonList (JavaNativeAccess .class );
181+ }
182+
51183 @ Override
52184 public void beforeAnalysis (BeforeAnalysisAccess access ) {
53- //
185+ var nativeLibraries = NativeLibrarySupport .singleton ();
186+ var platformLibraries = PlatformNativeLibrarySupport .singleton ();
187+
188+ // Register as a built-in library with Native Image and set the name prefix used by native symbols
189+ nativeLibraries .preregisterUninitializedBuiltinLibrary (JNA_LINK_NAME );
190+ platformLibraries .addBuiltinPkgNativePrefix (JNA_NATIVE_LAYOUT );
191+
192+ // Extract the main JNA library from the platform-specific resource path; next, extract the FFI
193+ // library it depends on
194+ unpackLibrary (getFFILibraryResource (), getFFILibraryFileName ());
195+ var extractedLib = unpackLibrary (getStaticLibraryResource (), getStaticLibraryFileName ());
196+
197+ // WARNING: the static JNI linking feature is unstable and may be removed in the future;
198+ // this code uses the access implementation directly in order to register the static library. We
199+ // inform the Native Image compiler that JNA depends on `ffi`, so that it forces it to load first
200+ // when JNA is initialized at image runtime.
201+ var nativeLibsImpl = ((BeforeAnalysisAccessImpl ) access ).getNativeLibraries ();
202+ nativeLibsImpl .addStaticNonJniLibrary (FFI_LINK_NAME );
203+ nativeLibsImpl .addStaticJniLibrary (JNA_LINK_NAME , FFI_LINK_NAME );
204+
205+ // Enhance the Native Image lib paths so the injected static libraries are available to the linker
206+ nativeLibsImpl .getLibraryPaths ().add (extractedLib .getParentFile ().getAbsolutePath ());
54207 }
55208}
0 commit comments