diff --git a/contrib/platform/src/com/sun/jna/platform/win32/OaIdl.java b/contrib/platform/src/com/sun/jna/platform/win32/OaIdl.java index 2cfd896162..9319986d3f 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/OaIdl.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/OaIdl.java @@ -553,6 +553,16 @@ public static class SAFEARRAY extends Structure implements Closeable { public static class ByReference extends SAFEARRAY implements Structure.ByReference { + + public ByReference() { + super(); + } + + public ByReference(Pointer pointer) { + super(pointer); + this.read(); + } + } public USHORT cDims; diff --git a/pom-jna-jpms.xml b/pom-jna-jpms.xml index 0a509e528d..37ae1cac20 100644 --- a/pom-jna-jpms.xml +++ b/pom-jna-jpms.xml @@ -9,6 +9,11 @@ TEMPLATE jar + + 1.8 + 1.8 + + Java Native Access Java Native Access https://github.com/java-native-access/jna diff --git a/pom-jna-platform-jpms.xml b/pom-jna-platform-jpms.xml index 5ee608e389..7f194ddfa3 100644 --- a/pom-jna-platform-jpms.xml +++ b/pom-jna-platform-jpms.xml @@ -13,6 +13,12 @@ Java Native Access Platform https://github.com/java-native-access/jna + + + 1.8 + 1.8 + + LGPL-2.1-or-later diff --git a/pom-jna-platform.xml b/pom-jna-platform.xml index 29ea3d78c9..18c29c41ee 100644 --- a/pom-jna-platform.xml +++ b/pom-jna-platform.xml @@ -13,6 +13,11 @@ Java Native Access Platform https://github.com/java-native-access/jna + + 1.8 + 1.8 + + LGPL-2.1-or-later diff --git a/pom-jna.xml b/pom-jna.xml index a7322d3d50..953fcbeac5 100644 --- a/pom-jna.xml +++ b/pom-jna.xml @@ -13,6 +13,11 @@ Java Native Access https://github.com/java-native-access/jna + + 1.8 + 1.8 + + LGPL-2.1-or-later diff --git a/src/com/sun/jna/ELFAnalyser.java b/src/com/sun/jna/ELFAnalyser.java index d50f522626..993c1aa824 100644 --- a/src/com/sun/jna/ELFAnalyser.java +++ b/src/com/sun/jna/ELFAnalyser.java @@ -289,7 +289,7 @@ public ELFSectionHeaders(boolean _64bit, boolean bigEndian, ByteBuffer headerDat raf.getChannel().read(data, shoff); for(int i = 0; i < shnum; i++) { - data.position(i * shentsize); + ((Buffer)data).position(i * shentsize); ByteBuffer header = data.slice(); header.order(data.order()); header.limit(shentsize); diff --git a/src/com/sun/jna/Function.java b/src/com/sun/jna/Function.java index c40c0427cb..1396741f07 100644 --- a/src/com/sun/jna/Function.java +++ b/src/com/sun/jna/Function.java @@ -22,6 +22,7 @@ */ package com.sun.jna; +import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; @@ -49,6 +50,8 @@ * @see Pointer */ public class Function extends Pointer { + public static TypeMapper globalFallbackMapper=null; + /** Any argument which implements this interface will have the * {@link #read} method called immediately after function invocation. */ @@ -621,11 +624,49 @@ private Object convertArgument(Object[] args, int index, return ss[0].getPointer(); } } else if (argClass.isArray()){ + if(globalFallbackMapper != null) { + Pointer[] pointers=new Pointer[Array.getLength(arg)]; + for (int i=0; i < pointers.length; i++) { + Object item=Array.get(arg, i); + Class type=item.getClass(); + ToNativeConverter converter=globalFallbackMapper.getToNativeConverter(type); + if (converter != null) { + ToNativeContext context; + if (invokingMethod != null) { + context=new MethodParameterContext(this, args, index, invokingMethod); + } else { + context=new FunctionParameterContext(this, args, index); + } + item=converter.toNative(item, context); + pointers[i]=(Pointer) item; + } + else { + throw new IllegalArgumentException("Unsupported array element type: " + + item.getClass()); + } + } + return new PointerArray(pointers); + } throw new IllegalArgumentException("Unsupported array argument type: " + argClass.getComponentType()); } else if (allowObjects) { return arg; } else if (!Native.isSupportedNativeType(arg.getClass())) { + if(globalFallbackMapper != null) { + Class type=arg.getClass(); + ToNativeConverter converter=globalFallbackMapper.getToNativeConverter(type); + if (converter != null) { + ToNativeContext context; + if (invokingMethod != null) { + context=new MethodParameterContext(this, args, index, invokingMethod); + } else { + context=new FunctionParameterContext(this, args, index); + } + arg=converter.toNative(arg, context); + return arg; + } + } + throw new IllegalArgumentException("Unsupported argument type " + arg.getClass().getName() + " at parameter " + index diff --git a/src/com/sun/jna/IntegerType.java b/src/com/sun/jna/IntegerType.java index b6f8590ba0..759ff70e70 100644 --- a/src/com/sun/jna/IntegerType.java +++ b/src/com/sun/jna/IntegerType.java @@ -156,8 +156,22 @@ public double doubleValue() { @Override public boolean equals(Object rhs) { - return rhs instanceof IntegerType - && number.equals(((IntegerType)rhs).number); + if(rhs instanceof IntegerType) + return number.equals(((IntegerType)rhs).number); + + if(rhs instanceof Integer) + return rhs.equals(number.intValue()); + if(rhs instanceof Long) + return rhs.equals(number.longValue()); + if(rhs instanceof Float) + return rhs.equals(number.floatValue()); + if(rhs instanceof Double) + return rhs.equals(number.doubleValue()); + if(rhs instanceof Byte) + return rhs.equals(number.byteValue()); + if(rhs instanceof Short) + return rhs.equals(number.shortValue()); + return false; } @Override diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 5226b36ddc..c5c27f7a97 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -27,12 +27,7 @@ import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.Window; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; @@ -51,15 +46,10 @@ import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; import java.security.AccessController; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.WeakHashMap; +import java.util.*; import com.sun.jna.Callback.UncaughtExceptionHandler; import com.sun.jna.Structure.FFIType; @@ -141,8 +131,8 @@ public final class Native implements Version { DEFAULT_CHARSET = nativeCharset; DEFAULT_ENCODING = nativeCharset.name(); } - public static final boolean DEBUG_LOAD = Boolean.getBoolean("jna.debug_load"); - public static final boolean DEBUG_JNA_LOAD = Boolean.getBoolean("jna.debug_load.jna"); + public static boolean DEBUG_LOAD = Boolean.getBoolean("jna.debug_load"); + public static boolean DEBUG_JNA_LOAD = Boolean.getBoolean("jna.debug_load.jna"); private final static Level DEBUG_JNA_LOAD_LEVEL = DEBUG_JNA_LOAD ? Level.INFO : Level.FINE; // Used by tests, do not remove @@ -1157,31 +1147,66 @@ public static File extractFromResourcePath(String name, ClassLoader loader) thro } else if (!Boolean.getBoolean("jna.nounpack")) { InputStream is = url.openStream(); + if (is == null) { throw new IOException("Can't obtain InputStream for " + resourcePath); } + if (Boolean.getBoolean("jna.permanentextract")) { + return doPermanentExtract(name, loader, resourcePath, is); + } + + FileOutputStream fos = 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 = getTempDir(); - lib = File.createTempFile(JNA_TMPLIB_PREFIX, Platform.isWindows()?".dll":null, dir); - if (!Boolean.getBoolean("jnidispatch.preserve")) { - lib.deleteOnExit(); - } - LOG.log(DEBUG, "Extracting library to {0}", lib.getAbsolutePath()); - fos = new FileOutputStream(lib); + + File temp = File.createTempFile(JNA_TMPLIB_PREFIX, ".tmp", dir); + temp.deleteOnExit(); + fos = new FileOutputStream(temp); + LOG.log(DEBUG, "Extracting library to temp file {0}", temp.getAbsolutePath()); + int count; byte[] buf = new byte[1024]; while ((count = is.read(buf, 0, buf.length)) > 0) { fos.write(buf, 0, count); } + fos.close(); + fos = null; + + lib=generateTempName(dir); + LOG.log(DEBUG, "Moving temp file to final name {0} attempt 1", lib.getAbsolutePath()); + + boolean moveError = !temp.renameTo(lib); + if (moveError) { + LOG.log(Level.WARNING, String.format("Moving temp file %s to final name %s failed, sleeping for one second and trying one more time with a new random name", temp.getAbsolutePath(), lib.getAbsolutePath())); + Thread.sleep(1000); + + lib = generateTempName(dir); + LOG.log(DEBUG, "Moving temp file to final name {0}, attempt 2", lib.getAbsolutePath()); + + moveError = !temp.renameTo(lib); + if (moveError) { + // We can't move our temp file. Maybe we can copy the bits? + copyFile(temp, lib); + } + } + if (!Boolean.getBoolean("jnidispatch.preserve")) { + lib.deleteOnExit(); + } + if (DEBUG_JNA_LOAD) { + LOG.log(DEBUG_JNA_LOAD_LEVEL, "DLL created without problems "+lib.getAbsolutePath()); + } } catch(IOException e) { throw new IOException("Failed to create temporary file for " + name + " library: " + e.getMessage()); } + catch(InterruptedException e) { + throw new IOException("Failed to create temporary file for " + name + " library: " + e.getMessage()); + } finally { try { is.close(); } catch(IOException e) { } if (fos != null) { @@ -1192,6 +1217,231 @@ else if (!Boolean.getBoolean("jna.nounpack")) { return lib; } + // Generate a random filename in given directory, loop until file doesn't exist + private static File generateTempName(File dir) throws IOException { + File lib = null; + Random random = new Random(); + do { + long n = random.nextLong(); + if (n == Long.MIN_VALUE) { + n = 0; // corner case + } else { + n = Math.abs(n); + } + String tempLibName = dir.getCanonicalPath() + "/" + JNA_TMPLIB_PREFIX + Long.toString(n); + if (Platform.isWindows()) + tempLibName += ".dll"; + lib = new File(tempLibName); + } while (lib.exists()); + return lib; + } + + // Write DLL from jars into a file in given temp path + private static File doPermanentExtract(String name, ClassLoader loader, String resourcePath, InputStream is) throws IOException { + FileOutputStream fos = null; + try { + LOG.log(DEBUG_JNA_LOAD_LEVEL, "Starting permanent extract of "+ resourcePath); + String libSHA1=getHashForFile(loader.getResourceAsStream(resourcePath)); + LOG.log(DEBUG_JNA_LOAD_LEVEL, "DLL hash is "+libSHA1); + + File dir = getTempDir(); + String libName= name.substring(name.lastIndexOf('/')+1, name.length()); + if(!libName.toLowerCase().endsWith(".dll") && Platform.isWindows() ) + libName+=".dll"; + String pathname = dir.getCanonicalPath() + "/sha1_" + libSHA1 + "_" + libName; + File libMaybe=new File(pathname); + if(libMaybe.exists()) + { + LOG.log(DEBUG_JNA_LOAD_LEVEL, "Using existing file " + libMaybe.getAbsolutePath()); + return libMaybe; + } + File temp=File.createTempFile(JNA_TMPLIB_PREFIX, Platform.isWindows() ? ".tmp" : null, dir); + temp.deleteOnExit(); + fos = new FileOutputStream(temp); + int count; + byte[] buf = new byte[1024]; + while ((count = is.read(buf, 0, buf.length)) > 0) { + fos.write(buf, 0, count); + } + fos.close(); + LOG.log(DEBUG_JNA_LOAD_LEVEL, "All bytes have been written to temp file " + temp.getAbsolutePath()+", now renaming to "+libMaybe.getAbsolutePath()); + + boolean moveError = !temp.renameTo(libMaybe); + if(moveError) + { + if(libMaybe.exists()) + { + LOG.log(Level.WARNING, "Had a race happen with " + libMaybe.getAbsolutePath() + ", using existing"); + } else { + LOG.log(Level.SEVERE, String.format("Unable to move %s to %s", temp.getAbsolutePath(), libMaybe.getAbsolutePath())); + // We can't move the file over, maybe we can create a new file and write all the bytes into it? + try{ + copyFile(temp, libMaybe); + LOG.log(Level.SEVERE, "DLL created with problems " + libMaybe.getAbsolutePath()); + if (!temp.delete()) + temp.deleteOnExit(); + return libMaybe; + } catch (Throwable t) { + throw new IOException("Unable to move or copy " + temp.getAbsolutePath() + " to " + libMaybe.getAbsolutePath()); + } + } + } else + { + LOG.log(DEBUG_JNA_LOAD_LEVEL, "DLL created without problems "+libMaybe.getAbsolutePath()); + } + return libMaybe; + } catch (NoSuchAlgorithmException e) { + throw new IOException("Failed to create temporary file for " + name + " library: " + e.getMessage()); + } + catch (IOException e) + { + throw new IOException("Failed to create temporary file for " + name + " library: " + e.getMessage()); + } + finally { + if(is != null) { + try { is.close(); } catch (IOException e) { } + } + if (fos != null) { + try { fos.close(); } catch(IOException e) { } + } + } + } + + private static void copyFile(File source, File destination) throws IOException { + OutputStream out = null; + InputStream in = null; + try { + in = new BufferedInputStream(new FileInputStream(source)); + out = new BufferedOutputStream(new FileOutputStream(destination)); + + byte[] buffer = new byte[65536]; + int lengthRead; + while ((lengthRead = in.read(buffer)) > 0) { + out.write(buffer, 0, lengthRead); + } + out.flush(); + in.close(); + out.close(); + in = null; + out = null; + } finally { + if (out != null) + out.close(); + if (in != null) + in.close(); + } + } + + public static Object loadFromResourcePath(String name, ClassLoader loader) throws IOException { + + final Level DEBUG = (DEBUG_LOAD + || (DEBUG_JNA_LOAD && name.contains("jnidispatch"))) ? Level.INFO : Level.FINE; + if (loader == null) { + loader = Thread.currentThread().getContextClassLoader(); + // Context class loader is not guaranteed to be set + if (loader == null) { + loader = Native.class.getClassLoader(); + } + } + LOG.log(DEBUG, "Looking in classpath from {0} for {1}", new Object[]{loader, name}); + String libname = name.startsWith("/") ? name : NativeLibrary.mapSharedLibraryName(name); + String resourcePath = name.startsWith("/") ? name : Platform.RESOURCE_PREFIX + "/" + libname; + if (resourcePath.startsWith("/")) { + resourcePath = resourcePath.substring(1); + } + URL url = loader.getResource(resourcePath); + if (url == null && resourcePath.startsWith(Platform.RESOURCE_PREFIX)) { + // If not found with the standard resource prefix, try without it + url = loader.getResource(libname); + } + if (url == null) { + String path = System.getProperty("java.class.path"); + if (loader instanceof URLClassLoader) { + path = Arrays.asList(((URLClassLoader)loader).getURLs()).toString(); + } + throw new IOException("Native library (" + resourcePath + ") not found in resource path (" + path + ")"); + } + LOG.log(DEBUG, "Found library resource at {0}", url); + + File lib = null; + if (url.getProtocol().toLowerCase().equals("file")) { + try { + lib = new File(new URI(url.toString())); + } + catch(URISyntaxException e) { + lib = new File(url.getPath()); + } + LOG.log(DEBUG, "Looking in {0}", lib.getAbsolutePath()); + if (!lib.exists()) { + throw new IOException("File URL " + url + " could not be properly decoded"); + } + } + else if (!Boolean.getBoolean("jna.nounpack")) + { + InputStream is=loader.getResourceAsStream(resourcePath); + if(is == null) + { + throw new IOException("Can't obtain InputStream for " + resourcePath); + } + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // Suffix is required on windows, or library fails to load + // Let Java pick the suffix, except on windows, to avoid + // problems with Web Start. + LOG.log(DEBUG, "Extracting library to temp buffer"); + + int count; + byte[] buf = new byte[1024]; + while ((count = is.read(buf, 0, buf.length)) > 0) { + baos.write(buf, 0, count); + } + byte[] ret=baos.toByteArray(); + baos.close(); + + if (DEBUG_JNA_LOAD) { + LOG.log(DEBUG_JNA_LOAD_LEVEL, "DLL streamed to byte array without problems, byte count "+ret.length); + } + return ret; + } + catch(IOException e) { + throw new IOException("Failed to create temporary file for " + name + " library: " + e.getMessage()); + } + finally { + try { is.close(); } catch(IOException e) { } + } + } + return lib; + } + + private static String getHashForFile(InputStream is) throws NoSuchAlgorithmException, IOException + { + try + { + MessageDigest messageDigest=null; + messageDigest=MessageDigest.getInstance("SHA1"); + final byte[] buffer=new byte[1024]; + int read=0; + while((read=is.read(buffer)) > 0) + { + messageDigest.update(buffer, 0, read); + } + + // Convert the byte to hex format + Formatter formatter=new Formatter(); + for(final byte b : messageDigest.digest()) + { + formatter.format("%02x", b); + } + return formatter.toString(); + } finally + { + if(is != null) + is.close(); + is=null; + } + } + /** * Initialize field and method IDs for native methods of this class. * Returns the size of a native pointer. diff --git a/src/com/sun/jna/Version.java b/src/com/sun/jna/Version.java index 93b1c51082..2106c42eed 100644 --- a/src/com/sun/jna/Version.java +++ b/src/com/sun/jna/Version.java @@ -24,6 +24,6 @@ package com.sun.jna; interface Version { // Buildsystem replaces variables and compiled class will hold the right value - String VERSION = "TEMPLATE"; - String VERSION_NATIVE = "TEMPLATE"; + String VERSION = "7.0.0"; + String VERSION_NATIVE = "7.0.0"; } diff --git a/src/com/sun/jna/win32/W32APITypeMapper.java b/src/com/sun/jna/win32/W32APITypeMapper.java index 6653f937db..d9bbd5fa84 100644 --- a/src/com/sun/jna/win32/W32APITypeMapper.java +++ b/src/com/sun/jna/win32/W32APITypeMapper.java @@ -47,7 +47,7 @@ public class W32APITypeMapper extends DefaultTypeMapper { /** Default TypeMapper to use - depends on the value of {@code w32.ascii} system property */ public static final TypeMapper DEFAULT = Boolean.getBoolean("w32.ascii") ? ASCII : UNICODE; - protected W32APITypeMapper(boolean unicode) { + public W32APITypeMapper(boolean unicode) { if (unicode) { TypeConverter stringConverter = new TypeConverter() { @Override diff --git a/test/com/sun/jna/CallbacksTest.java b/test/com/sun/jna/CallbacksTest.java index ff801fb7e5..6d44a45223 100644 --- a/test/com/sun/jna/CallbacksTest.java +++ b/test/com/sun/jna/CallbacksTest.java @@ -1588,6 +1588,31 @@ public void invoke() { } } } + public void testWriteCallback() { + vTable vtable=new vTable(); + vtable.callback=new vTable.functionpointer() { + @Override + public int callback(Pointer runtimeId) { + return 0; + } + }; + vtable.write(); + } + + public static class vTable extends Structure { + public interface functionpointer extends Callback { + int callback(Pointer runtimeId); + } + + public functionpointer callback; + + @Override + protected List getFieldOrder() { + return Arrays.asList(new String[]{"callback"}); + + } + } + public static void main(java.lang.String[] argList) { junit.textui.TestRunner.run(CallbacksTest.class); } diff --git a/test/com/sun/jna/IntegerTypeTest.java b/test/com/sun/jna/IntegerTypeTest.java index df53c15ba1..6196b83b18 100644 --- a/test/com/sun/jna/IntegerTypeTest.java +++ b/test/com/sun/jna/IntegerTypeTest.java @@ -31,13 +31,20 @@ public class IntegerTypeTest extends TestCase { public static class Sized extends IntegerType { private static final long serialVersionUID = 1L; - public Sized() { this(4, 0); } - public Sized(int size, long value) { super(size, value); } + + public Sized() { + this(4, 0); + } + + public Sized(int size, long value) { + super(size, value); + } } public void testWriteNull() { class NTStruct extends Structure { public Sized field; + @Override protected List getFieldOrder() { return Arrays.asList("field"); @@ -46,9 +53,11 @@ protected List getFieldOrder() { NTStruct s = new NTStruct(); assertNotNull("Field not initialized", s.field); } + public void testReadNull() { class NTStruct extends Structure { public Sized field; + @Override protected List getFieldOrder() { return Arrays.asList("field"); @@ -60,34 +69,32 @@ protected List getFieldOrder() { } public void testCheckArgumentSize() { - for (int i=1;i <= 8;i*=2) { - long value = -1L << (i*8-1); + for (int i = 1; i <= 8; i *= 2) { + long value = -1L << (i * 8 - 1); new Sized(i, value); new Sized(i, -1); new Sized(i, 0); new Sized(i, 1); - value = 1L << (i*8-1); + value = 1L << (i * 8 - 1); new Sized(i, value); - value = -1L & ~(-1L << (i*8)); + value = -1L & ~(-1L << (i * 8)); new Sized(i, value); if (i < 8) { try { - value = 1L << (i*8); + value = 1L << (i * 8); new Sized(i, value); fail("Value exceeding size (" + i + ") should fail"); - } - catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { } } if (i < 8) { try { - value = -1L << (i*8); + value = -1L << (i * 8); new Sized(i, value); fail("Negative value (" + value + ") exceeding size (" + i + ") should fail"); - } - catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { } } } @@ -110,20 +117,17 @@ public TestType(int size, long value) { try { new TestType(1, 0x100L); fail("Exception should be thrown if byte value out of bounds"); - } - catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { } try { new TestType(2, 0x10000L); fail("Exception should be thrown if short value out of bounds"); - } - catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { } try { new TestType(4, 0x100000000L); fail("Exception should be thrown if int value out of bounds"); - } - catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { } } @@ -163,6 +167,21 @@ public void testCompareLongs() { assertEquals("Mismatched reversed order comparison", 1, IntegerType.compare(v2, v1)); } + public void testEquals() { + NativeLong l = new NativeLong(5); + assertTrue("Mismatched equal with value type", l.equals(5)); + assertTrue("Mismatched equal with NativeLong", l.equals(new NativeLong(5))); + assertFalse("Mismatched not equal with value type", l.equals(7)); + assertFalse("Mismatched not equal with NativeLong", l.equals(new NativeLong(7))); + } + + public void testCompare() { + NativeLong l = new NativeLong(5); + assertEquals("Mismatched equal native value comparison", 0, IntegerType.compare(l, 5)); + assertTrue("Mismatched larger native value comparison", (IntegerType.compare(l, 7) < 0)); + assertTrue("Mismatched smaller native value comparison", (IntegerType.compare(l, 4) > 0)); + } + public static void main(String[] args) { junit.textui.TestRunner.run(IntegerTypeTest.class); }