Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded native library in the JAR file #120

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -526,11 +526,11 @@
<src path="${test.src}"/>
<exclude name="${tests.exclude}"/>
</javac>
<!-- Move testlibjar.so to test class folder so that it will be packaged into the test JAR -->
<move file="${build.native}/libtestlibjar.so" todir="${test.classes}/${os.name}-${os.arch}"/>
<!-- Create a jar for easy movement of tests -->
<jar jarfile="${build}/${testjar}">
<fileset dir="${test.classes}">
<patternset refid="jar-compiled"/>
</fileset>
<fileset dir="${test.classes}"/>
</jar>
<mkdir dir="${build}/jws"/>
<copy todir="${build}/jws" file="${build}/${jar}"/>
Expand Down
6 changes: 5 additions & 1 deletion native/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ endif
LIBRARY=$(BUILD)/$(LIBPFX)jnidispatch$(JNISFX)
TESTLIB=$(BUILD)/$(LIBPFX)testlib$(LIBSFX)
TESTLIB2=$(BUILD)/$(LIBPFX)testlib2$(LIBSFX)
TESTLIBJAR=$(BUILD)/$(LIBPFX)testlibjar$(LIBSFX)

# Reasonable defaults based on GCC
LIBPFX=lib
Expand Down Expand Up @@ -296,7 +297,7 @@ else
$(CC) $(CFLAGS) -c $< $(COUT)
endif

all: $(LIBRARY) $(TESTLIB) $(TESTLIB2)
all: $(LIBRARY) $(TESTLIB) $(TESTLIB2) $(TESTLIBJAR)

install:
mkdir $(INSTALLDIR)
Expand All @@ -321,6 +322,9 @@ endif
$(TESTLIB2): $(BUILD)/testlib2.o
$(LD) $(LDFLAGS) $< $(TESTDEP)

$(TESTLIBJAR): $(BUILD)/testlibjar.o
$(LD) $(LDFLAGS) $< $(LIBS)

ifneq ($(DYNAMIC_LIBFFI),true)
$(FFI_LIB):
@mkdir -p $(FFI_BUILD)
Expand Down
70 changes: 70 additions & 0 deletions src/com/sun/jna/NativeLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
package com.sun.jna;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.ref.Reference;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -165,13 +168,80 @@ else if (Platform.isWindows()) {
try { handle = Native.open(libraryPath); }
catch(UnsatisfiedLinkError e2) { e = e2; }
}
// As a last resort, try to extract the library from the JAR
if (handle == 0) {
final String embeddedLib = getEmbeddedLibraryPathFromJar(mapLibraryName(libraryName));
if (embeddedLib != null)
{
try {
handle = Native.open(embeddedLib);
}
catch (UnsatisfiedLinkError e2) { e = e2; }
}
}
if (handle == 0) {
throw new UnsatisfiedLinkError("Unable to load library '" + libraryName + "': "
+ e.getMessage());
}
}
return new NativeLibrary(libraryName, libraryPath, handle, options);
}

private static String getEmbeddedLibraryPathFromJar(String libraryName)
{
// Do not extract the library from JAR if jna.nounpack=true
if (Boolean.getBoolean("jna.nounpack")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a different flag; jna.nounpack specifically forbids unpacking JNA's native bits; it shouldn't be re-used to forbid unpacking additional native bits unless the two situations are identical (I don't think they are).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the idea was to forbid unpacking because, for example, we were not allowed to write in the temp folder. So I thought both situations were similar.

return null;
}
final String libraryPath = System.getProperty("os.name") + "-" + System.getProperty("os.arch") + "/";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prefix is problematic in that the two properties are not terribly consistent across OS versions and JVMs. JNA's more canonical representation would be better.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. However, the advantage of using these prefixes is that it makes it very easy to copy the native libs in the right folders from Maven or even Ant. But you're right, there are not very consistent (I don't understant why there are so many different names for Windows for exemple).

File libFile;
try
{
libFile = extractEmbeddedLibraryResource(libraryPath, libraryName);
}
catch (IOException e)
{
return null;
}
if (libFile == null)
{
return null;
}
else
{
return libFile.getAbsolutePath();
}
}

private static File extractEmbeddedLibraryResource(String libraryPath, String libraryName) throws IOException {
final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(libraryPath + libraryName);
if (is == null) {
return null;
}
FileOutputStream fos = null;
File lib = null;
try {
// Suffix is required on windows, or library fails to load
// Let Java pick the suffix, except on windows, to avoid
// problems with Web Start.
File dir = Native.getTempDir();
lib = File.createTempFile(libraryName, Platform.isWindows()?".dll":null, dir);
lib.deleteOnExit();
fos = new FileOutputStream(lib);
int count;
byte[] buf = new byte[1024];
while ((count = is.read(buf, 0, buf.length)) > 0) {
fos.write(buf, 0, count);
}
}
finally {
try { is.close(); } catch(IOException e) { }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll let @twall comment on this, but I think catching and ignoring exceptions here is a really bad practice. This is just not supposed to fail, and if it does, something has gone awfully wrong.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is a small copy/paste of Native.LoadNativeLibraryFromJar().
In theory I agree that ignoring exceptions is a bad pratice, but such pattern is very common in finally blocks when a stream has to be closed (because there should be no error here, and even if there is one we do not really care).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a commonly ignored exception in Java; there really is no recourse and in practice I've never actually seen the error.

if (fos != null) {
try { fos.close(); } catch(IOException e) { }
}
}
return lib;
}

private String getLibraryName(String libraryName) {
String simplified = libraryName;
Expand Down
13 changes: 13 additions & 0 deletions test/com/sun/jna/NativeLibraryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.Arrays;
import java.util.List;

import junit.framework.Assert;
import junit.framework.TestCase;

public class NativeLibraryTest extends TestCase {
Expand Down Expand Up @@ -190,6 +191,18 @@ public void testGetProcess() {
// Access a common C library function
process.getFunction("printf");
}

public static interface TestLibraryInJar extends Library {
int test();
}

/**
* Tests that we can load a library which is embedded in the test JAR.
*/
public void testEmbeddedLibrary() {
TestLibraryInJar lib = (TestLibraryInJar)Native.loadLibrary("testlibjar", TestLibraryInJar.class);
Assert.assertEquals(0, lib.test());
}

public static void main(String[] args) {
junit.textui.TestRunner.run(NativeLibraryTest.class);
Expand Down