Skip to content

Commit 3823533

Browse files
committed
add support for loading native libraries from classpath (FS or jar)
1 parent fecaf92 commit 3823533

9 files changed

Lines changed: 194 additions & 90 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Features
55
--------
66
* Basic [COM support](https://github.com/twall/jna/blob/master/www/PlatformLibrary.md) for w32 - [@wolftobias](https://github.com/wolftobias).
77
* Avoid superfluous Structure memory allocation by using Structure(Pointer) ctors if available - [@twall](https://github.com/twall).
8+
* [PR#120](https://github.com/twall/jna/pull/120): Provide methods for extracting native libraries from the class path for use by JNA - [@Zlika](https://github.com/Zlika).
89
* [#163](https://github.com/twall/jna/pull/163): The Java `GUID` structure can be used directly as alternative to `Ole32Util.getGUIDFromString()` - [@wolftobias](https://github.com/wolftobias).
910
* [#163](https://github.com/twall/jna/pull/163): Ported Win32 `dbt.h` - [@wolftobias](https://github.com/wolftobias).
1011
* [#163](https://github.com/twall/jna/pull/163): Added Win32 `WTSRegisterSessionNotification()` and `WTSUnRegisterSessionNotification()` from `Wtsapi32.dll` - [@wolftobias](https://github.com/wolftobias).

build.xml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
<path id="test.runpath">
265265
<pathelement path="${build}/${jar}"/>
266266
<pathelement path="${test.classes}"/>
267+
<pathelement path="${build}/${testjar}"/>
267268
<pathelement path="lib/clover.jar"/>
268269
<path refid="test.libs"/>
269270
</path>
@@ -708,12 +709,28 @@ osname=macos
708709
<src path="${test.src}"/>
709710
<exclude name="${tests.exclude}"/>
710711
</javac>
711-
<!-- Create a jar for easy movement of tests -->
712+
<!-- Move (not copy) embedded testlib to test class folder so that it will be -->
713+
<!-- packaged into the test jar and NOT available in the FS-based class path -->
714+
<move todir="${test.classes}/${os.prefix}">
715+
<fileset dir="${build.native}">
716+
<patternset id="embedded-testlib">
717+
<include name="**/*embedded-testlib*"/>
718+
</patternset>
719+
</fileset>
720+
</move>
721+
<!-- Create a jar for easy movement of tests, and embedded load test -->
712722
<jar jarfile="${build}/${testjar}">
713723
<fileset dir="${test.classes}">
714724
<patternset refid="jar-compiled"/>
725+
<include name="**/*embedded-testlib*"/>
715726
</fileset>
716727
</jar>
728+
<!-- Ensure embedded library unavailable on FS-based class path -->
729+
<delete>
730+
<fileset dir="${test.classes}">
731+
<include name="**/*embedded-testlib*"/>
732+
</fileset>
733+
</delete>
717734
<mkdir dir="${build}/jws"/>
718735
<copy todir="${build}/jws" file="${build}/${jar}"/>
719736
<copy todir="${build}/jws" file="${build}/${testjar}"/>

native/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ FFI_CONFIG=--enable-static --disable-shared --with-pic=yes
6767
endif
6868
LIBRARY=$(BUILD)/$(LIBPFX)jnidispatch$(JNISFX)
6969
TESTLIB=$(BUILD)/$(LIBPFX)testlib$(LIBSFX)
70+
TESTLIB_EMBEDDED=$(BUILD)/$(LIBPFX)embedded-testlib$(LIBSFX)
7071
TESTLIB2=$(BUILD)/$(LIBPFX)testlib2$(LIBSFX)
7172

7273
# Reasonable defaults based on GCC
@@ -308,6 +309,7 @@ LDFLAGS=$(ARCHFLAGS) -dynamiclib -o $@ -framework JavaVM \
308309
-compatibility_version $(shell echo ${JNA_JNI_VERSION}|sed 's/^\([0-9][0-9]*\).*/\1/g') \
309310
-current_version $(JNA_JNI_VERSION) \
310311
-mmacosx-version-min=10.3 \
312+
-framework Foundation \
311313
-install_name ${@F} \
312314
$(SYSLIBROOT)
313315
# JAWT linkage handled by -framework JavaVM
@@ -329,7 +331,7 @@ else
329331
$(CC) $(CFLAGS) -c $< $(COUT)
330332
endif
331333

332-
all: $(LIBRARY) $(TESTLIB) $(TESTLIB2)
334+
all: $(LIBRARY) $(TESTLIB) $(TESTLIB2) $(TESTLIB_EMBEDDED)
333335

334336
install:
335337
mkdir $(INSTALLDIR)
@@ -349,6 +351,9 @@ $(LIBRARY): $(JNIDISPATCH_OBJS) $(FFI_LIB)
349351
$(TESTLIB): $(BUILD)/testlib.o
350352
$(LD) $(LDFLAGS) $< $(LIBS)
351353

354+
$(TESTLIB_EMBEDDED): $(BUILD)/testlib.o
355+
$(LD) $(LDFLAGS) $< $(LIBS)
356+
352357
ifeq ($(ARSFX),.lib)
353358
TESTDEP=$(TESTLIB:.dll=.lib)
354359
else

src/com/sun/jna/Native.java

Lines changed: 88 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@
1616
import java.awt.GraphicsEnvironment;
1717
import java.awt.HeadlessException;
1818
import java.awt.Window;
19-
20-
import java.nio.Buffer;
21-
import java.nio.ByteBuffer;
22-
2319
import java.io.File;
24-
import java.io.FilenameFilter;
2520
import java.io.FileOutputStream;
21+
import java.io.FilenameFilter;
2622
import java.io.IOException;
2723
import java.io.InputStream;
2824
import java.io.UnsupportedEncodingException;
@@ -36,6 +32,8 @@
3632
import java.net.URI;
3733
import java.net.URISyntaxException;
3834
import java.net.URL;
35+
import java.nio.Buffer;
36+
import java.nio.ByteBuffer;
3937
import java.security.AccessController;
4038
import java.security.PrivilegedAction;
4139
import java.util.ArrayList;
@@ -173,14 +171,13 @@ private static void dispose() {
173171
that introduces issues with cleaning up any extant JNA bits
174172
(e.g. Memory) which may still need use of the library before shutdown.
175173
*/
176-
private static boolean deleteNativeLibrary(String path) {
177-
File flib = new File(path);
178-
if (flib.delete()) {
174+
static boolean deleteLibrary(File lib) {
175+
if (lib.delete()) {
179176
return true;
180177
}
181178

182179
// Couldn't delete it, mark for later deletion
183-
markTemporaryFile(flib);
180+
markTemporaryFile(lib);
184181

185182
return false;
186183
}
@@ -594,10 +591,20 @@ public static char[] toCharArray(String s) {
594591
return buf;
595592
}
596593

594+
/** Generate a canonical String prefix based on the current OS
595+
type/arch/name.
596+
*/
597+
public static String getNativeLibraryResourcePrefix() {
598+
return getNativeLibraryResourcePrefix(Platform.getOSType(), System.getProperty("os.arch"), System.getProperty("os.name"));
599+
}
600+
597601
/** Generate a canonical String prefix based on the given OS
598602
type/arch/name.
603+
@param osType from {@link Platform}
604+
@param arch from <code>os.arch</code> System property
605+
@param name from <code>os.name</code> System property
599606
*/
600-
static String getNativeLibraryResourcePath(int osType, String arch, String name) {
607+
public static String getNativeLibraryResourcePrefix(int osType, String arch, String name) {
601608
String osPrefix;
602609
arch = arch.toLowerCase();
603610
if ("powerpc".equals(arch)) {
@@ -652,7 +659,7 @@ else if ("x86_64".equals(arch)) {
652659
osPrefix += "-" + arch;
653660
break;
654661
}
655-
return "/com/sun/jna/" + osPrefix;
662+
return osPrefix;
656663
}
657664

658665
/**
@@ -727,28 +734,76 @@ private static void loadNativeLibrary() {
727734
throw new UnsatisfiedLinkError("Native jnidispatch library not found");
728735
}
729736

737+
static final String JNA_TMPLIB_PREFIX = "jna";
730738
/**
731739
* Attempts to load the native library resource from the filesystem,
732740
* extracting the JNA stub library from jna.jar if not already available.
733741
*/
734742
private static void loadNativeLibraryFromJar() {
735-
String libname = System.mapLibraryName("jnidispatch");
736-
String arch = System.getProperty("os.arch");
737-
String name = System.getProperty("os.name");
738-
String resourceName = getNativeLibraryResourcePath(Platform.getOSType(), arch, name) + "/" + libname;
739-
URL url = Native.class.getResource(resourceName);
740-
boolean unpacked = false;
741-
742-
// Add an ugly hack for OpenJDK (soylatte) - JNI libs use the usual
743-
// .dylib extension
744-
if (url == null && Platform.isMac()
745-
&& resourceName.endsWith(".dylib")) {
746-
resourceName = resourceName.substring(0, resourceName.lastIndexOf(".dylib")) + ".jnilib";
747-
url = Native.class.getResource(resourceName);
743+
try {
744+
String prefix = "com/sun/jna/" + getNativeLibraryResourcePrefix();
745+
File lib = extractFromResourcePath("jnidispatch", prefix, Native.class.getClassLoader());
746+
System.load(lib.getAbsolutePath());
747+
nativeLibraryPath = lib.getAbsolutePath();
748+
// Attempt to delete immediately once jnidispatch is successfully
749+
// loaded. This avoids the complexity of trying to do so on "exit",
750+
// which point can vary under different circumstances (native
751+
// compilation, dynamically loaded modules, normal application, etc).
752+
if (isUnpacked(lib)) {
753+
deleteLibrary(lib);
754+
}
755+
}
756+
catch(IOException e) {
757+
throw new UnsatisfiedLinkError(e.getMessage());
758+
}
759+
}
760+
761+
/** Identify temporary files unpacked from classpath jar files. */
762+
static boolean isUnpacked(File file) {
763+
return file.getName().startsWith(JNA_TMPLIB_PREFIX);
764+
}
765+
766+
/** Attempt to extract a native library from the current resource path.
767+
* Expects native libraries to be stored under
768+
* the path returned by {@link #getNativeLibraryResourcePrefix()},
769+
* and reachable by the current thread context class loader.
770+
* @param name Base name of native library to extract
771+
* @return File indicating extracted resource on disk
772+
* @throws IOException if resource not found
773+
*/
774+
static File extractFromResourcePath(String name) throws IOException {
775+
return extractFromResourcePath(name, getNativeLibraryResourcePrefix(), Thread.currentThread().getContextClassLoader());
776+
}
777+
778+
/** Attempt to extract a native library from the current resource path.
779+
* Expects native libraries to be stored under
780+
* the path returned by {@link #getNativeLibraryResourcePath(int, String,
781+
* String)}.
782+
* @param name Base name of native library to extract
783+
* @param loader Class loader to use to load resources
784+
* @param resourcePrefix prefix to use when looking for the resource
785+
* @return File indicating extracted resource on disk
786+
* @throws IOException if resource not found
787+
*/
788+
static File extractFromResourcePath(String name, String resourcePrefix, ClassLoader loader) throws IOException {
789+
String libname = System.mapLibraryName(name);
790+
String resourcePath = resourcePrefix + "/" + libname;
791+
URL url = loader.getResource(resourcePath);
792+
793+
// User libraries will have '.dylib'
794+
if (url == null && Platform.isMac()) {
795+
if (resourcePath.endsWith(".jnilib")) {
796+
resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(".jnilib")) + ".dylib";
797+
}
798+
// Ugly hack for OpenJDK (soylatte) - JNI libs use the usual
799+
// .dylib extension
800+
else if (resourcePath.endsWith(".dylib")) {
801+
resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(".dylib")) + ".jnilib";
802+
}
803+
url = loader.getResource(resourcePath);
748804
}
749805
if (url == null) {
750-
throw new UnsatisfiedLinkError("JNA native support (" + resourceName
751-
+ ") not found in resource path");
806+
throw new IOException("JNA native support (" + resourcePath + ") not found in resource path (" + System.getProperty("java.class.path") + ")");
752807
}
753808

754809
File lib = null;
@@ -760,13 +815,13 @@ private static void loadNativeLibraryFromJar() {
760815
lib = new File(url.getPath());
761816
}
762817
if (!lib.exists()) {
763-
throw new Error("File URL " + url + " could not be properly decoded");
818+
throw new IOException("File URL " + url + " could not be properly decoded");
764819
}
765820
}
766821
else {
767-
InputStream is = Native.class.getResourceAsStream(resourceName);
822+
InputStream is = loader.getResourceAsStream(resourcePath);
768823
if (is == null) {
769-
throw new Error("Can't obtain jnidispatch InputStream");
824+
throw new IOException("Can't obtain InputStream for " + resourcePath);
770825
}
771826

772827
FileOutputStream fos = null;
@@ -775,18 +830,17 @@ private static void loadNativeLibraryFromJar() {
775830
// Let Java pick the suffix, except on windows, to avoid
776831
// problems with Web Start.
777832
File dir = getTempDir();
778-
lib = File.createTempFile("jna", Platform.isWindows()?".dll":null, dir);
833+
lib = File.createTempFile(JNA_TMPLIB_PREFIX, Platform.isWindows()?".dll":null, dir);
779834
lib.deleteOnExit();
780835
fos = new FileOutputStream(lib);
781836
int count;
782837
byte[] buf = new byte[1024];
783838
while ((count = is.read(buf, 0, buf.length)) > 0) {
784839
fos.write(buf, 0, count);
785840
}
786-
unpacked = true;
787841
}
788842
catch(IOException e) {
789-
throw new Error("Failed to create temporary file for jnidispatch library", e);
843+
throw new IOException("Failed to create temporary file for " + name + " library", e);
790844
}
791845
finally {
792846
try { is.close(); } catch(IOException e) { }
@@ -795,15 +849,7 @@ private static void loadNativeLibraryFromJar() {
795849
}
796850
}
797851
}
798-
System.load(lib.getAbsolutePath());
799-
nativeLibraryPath = lib.getAbsolutePath();
800-
// Attempt to delete immediately once jnidispatch is successfully
801-
// loaded. This avoids the complexity of trying to do so on "exit",
802-
// which point can vary under different circumstances (native
803-
// compilation, dynamically loaded modules, normal application, etc).
804-
if (unpacked) {
805-
deleteNativeLibrary(lib.getAbsolutePath());
806-
}
852+
return lib;
807853
}
808854

809855
/**
@@ -966,7 +1012,7 @@ static void removeTemporaryFiles() {
9661012
File dir = getTempDir();
9671013
FilenameFilter filter = new FilenameFilter() {
9681014
public boolean accept(File dir, String name) {
969-
return name.endsWith(".x") && name.indexOf("jna") != -1;
1015+
return name.endsWith(".x") && name.startsWith(JNA_TMPLIB_PREFIX);
9701016
}
9711017
};
9721018
File[] files = dir.listFiles(filter);

src/com/sun/jna/NativeLibrary.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.io.File;
1818
import java.io.FilenameFilter;
19+
import java.io.IOException;
1920
import java.lang.ref.WeakReference;
2021
import java.lang.ref.Reference;
2122
import java.lang.reflect.Method;
@@ -193,6 +194,20 @@ else if (Platform.isWindows()) {
193194
try { handle = Native.open(libraryPath, openFlags); }
194195
catch(UnsatisfiedLinkError e2) { e = e2; }
195196
}
197+
// As a last resort, try to extract the library from the class
198+
// path, using the current context class loader.
199+
if (handle == 0) {
200+
try {
201+
File embedded = Native.extractFromResourcePath(libraryName);
202+
handle = Native.open(embedded.getAbsolutePath());
203+
// Don't leave temporary files around
204+
if (Native.isUnpacked(embedded)) {
205+
Native.deleteLibrary(embedded);
206+
}
207+
}
208+
catch(IOException e2) { e = new UnsatisfiedLinkError(e2.getMessage()); }
209+
}
210+
196211
if (handle == 0) {
197212
throw new UnsatisfiedLinkError("Unable to load library '" + libraryName + "': "
198213
+ e.getMessage());

src/com/sun/jna/overview.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,8 @@ <h3>Use direct mapping of methods</h3>
715715
Using <a href="#direct-mapping">direct mapping</a> of methods makes native
716716
calls more efficiently than does interface mapping. Direct mapping does not
717717
support varargs calls or arrays of Pointer, String, or WString as an argument
718-
or return value.
718+
or return value. For optimium results, use only primitive arguments and
719+
return values; you'll have to convert to and from objects yourself explicitly.
719720
<h3>Avoid type mapping</h3>
720721
Type mapping incurs additional overhead on each function call. You can avoid
721722
this by ensuring that your arguments and/or return types are already primitive

test/com/sun/jna/NativeLibraryTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ public void testLoadLibraryWithOptions() {
249249
Native.loadLibrary("testlib", TestLibrary.class, options);
250250
}
251251

252+
public void testEmbeddedLibrary() {
253+
Native.loadLibrary("embedded-testlib", TestLibrary.class);
254+
}
255+
252256
public static void main(String[] args) {
253257
junit.textui.TestRunner.run(NativeLibraryTest.class);
254258
}

0 commit comments

Comments
 (0)