From 1889a473754b51c68835ce02ffefe4173392124a Mon Sep 17 00:00:00 2001 From: idosu Date: Thu, 10 May 2018 13:41:46 +0300 Subject: [PATCH] Add an annotation to define the field order of a structure --- CHANGES.md | 1 + src/com/sun/jna/Structure.java | 98 ++++++++++++++++---- test/com/sun/jna/StructureTest.java | 137 +++++++++++++++++++++++++++- www/GettingStarted.md | 119 ++++++++++++------------ www/StructuresAndUnions.md | 6 +- 5 files changed, 279 insertions(+), 82 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bafdaf5a5a..ac1f85e172 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Features * Bind `com.sun.jna.platform.win32.Kernel32.ExpandEnvironmentStrings` and add helper method for it as `Kernel32Util#expandEnvironmentStrings` - [@matthiasblaesing](https://github.com/matthiasblaesing). * [#935](https://github.com/java-native-access/jna/pull/935): Add RegConnectRegistry to Advapi32 mappings. - [@cxcorp](https://github.com/cxcorp). * [#947](https://github.com/java-native-access/jna/pull/947): Allow retrieval of `ACEs` from `com.sun.jna.platform.win32.WinNT.ACL` even if the contained `ACE` is not currently supported - [@jrobhoward](https://github.com/jrobhoward). +* Add `c.s.j.Structure.FieldOrder` annotation to define the field order of a structures without implementing `Structure#getFieldOrder()` - [@idosu](https://github.com/idosu). Bug Fixes --------- diff --git a/src/com/sun/jna/Structure.java b/src/com/sun/jna/Structure.java index 42df030933..e53fd4efb6 100644 --- a/src/com/sun/jna/Structure.java +++ b/src/com/sun/jna/Structure.java @@ -23,6 +23,11 @@ */ package com.sun.jna; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -38,6 +43,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -66,11 +72,12 @@ * public. If your structure is to have no fields of its own, it must be * declared abstract. *

- *

You must define {@link #getFieldOrder} to return a List of - * field names (Strings) indicating the proper order of the fields. When - * dealing with multiple levels of subclasses of Structure, you must add to - * the list provided by the superclass {@link #getFieldOrder} - * the fields defined in the current class. + *

You must annotate the class with {@link FieldOrder} or implement + * {@link #getFieldOrder}, whichever you choose it must contain the field names + * (Strings) indicating the proper order of the fields. If you chose to implement + * {@link #getFieldOrder} notice that when dealing with multiple levels of + * subclasses of Structure, you must add to the list provided by the superclass + * {@link #getFieldOrder} the fields defined in the current class. *

*

In the past, most VMs would return them in a predictable order, but the JVM * spec does not require it, so {@link #getFieldOrder} is now required to @@ -865,20 +872,66 @@ protected void writeField(StructField structField) { } } - /** Return this Structure's field names in their proper order. For - * example, + /** Used to declare fields order as metadata instead of method. + * example: *


-     * protected List getFieldOrder() {
-     *     return Arrays.asList(new String[] { ... });
+     * // New
+     * {@literal @}FieldOrder({ "n", "s" })
+     * class Parent extends Structure {
+     *     public int n;
+     *     public String s;
+     * }
+     * {@literal @}FieldOrder({ "d", "c" })
+     * class Son extends Parent {
+     *     public double d;
+     *     public char c;
+     * }
+     * // Old
+     * class Parent extends Structure {
+     *     public int n;
+     *     public String s;
+     *     protected List getFieldOrder() {
+     *         return Arrays.asList("n", "s");
+     *     }
+     * }
+     * class Son extends Parent {
+     *     public double d;
+     *     public char c;
+     *     protected List getFieldOrder() {
+     *         List fields = new LinkedList(super.getFieldOrder());
+     *         fields.addAll(Arrays.asList("d", "c"));
+     *         return fields;
+     *     }
+     * }
+     * 
+ */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface FieldOrder { + String[] value(); + } + + /** Returns this Structure's field names in their proper order.
+ * + * When defining a new {@link Structure} you shouldn't override this + * method, but use {@link FieldOrder} annotation to define your field + * order(this also works with inheritance)
+ * + * If you want to do something non-standard you can override the method + * and define it as followed + *

+     * protected List getFieldOrder() {
+     *     return Arrays.asList(...);
      * }
      * 
* IMPORTANT * When deriving from an existing Structure subclass, ensure that * you augment the list provided by the superclass, e.g. *

-     * protected List getFieldOrder() {
-     *     List fields = new ArrayList(super.getFieldOrder());
-     *     fields.addAll(Arrays.asList(new String[] { ... }));
+     * protected List getFieldOrder() {
+     *     List fields = new LinkedList(super.getFieldOrder());
+     *     fields.addAll(Arrays.asList(...));
      *     return fields;
      * }
      * 
@@ -888,7 +941,19 @@ protected void writeField(StructField structField) { * guaranteed to be predictable. * @return ordered list of field names */ - protected abstract List getFieldOrder(); + // TODO(idosu 28 Apr 2018): Maybe deprecate this method to let users know they should use @FieldOrder + protected List getFieldOrder() { + List fields = new LinkedList(); + for (Class clazz = getClass(); clazz != Structure.class; clazz = clazz.getSuperclass()) { + FieldOrder order = clazz.getAnnotation(FieldOrder.class); + if (order != null) { + fields.addAll(0, Arrays.asList(order.value())); + } + } + + // fields.isEmpty() can be true because it is check somewhere else + return Collections.unmodifiableList(fields); + } /** Sort the structure fields according to the given array of names. * @param fields list of fields to be sorted @@ -1855,6 +1920,7 @@ public String toString() { * structure for use by libffi. The lifecycle of this structure is easier * to manage on the Java side than in native code. */ + @FieldOrder({ "size", "alignment", "type", "elements" }) static class FFIType extends Structure { public static class size_t extends IntegerType { private static final long serialVersionUID = 1L; @@ -1951,11 +2017,7 @@ private FFIType(Object array, Class type) { } init(els); } - - @Override - protected List getFieldOrder() { - return Arrays.asList(new String[] { "size", "alignment", "type", "elements" }); - } + private void init(Pointer[] els) { elements = new Memory(Native.POINTER_SIZE * els.length); elements.write(0, els, 0, els.length); diff --git a/test/com/sun/jna/StructureTest.java b/test/com/sun/jna/StructureTest.java index 1752672fbe..e53d95294b 100644 --- a/test/com/sun/jna/StructureTest.java +++ b/test/com/sun/jna/StructureTest.java @@ -33,6 +33,7 @@ import junit.framework.TestCase; +import com.sun.jna.Structure.FieldOrder; import com.sun.jna.Structure.StructureSet; import com.sun.jna.ptr.ByteByReference; import com.sun.jna.ptr.IntByReference; @@ -227,6 +228,11 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1" }) + public static class AnnotatedTestStructure0 extends FilledStructure { + public byte field0 = 0x01; + public short field1 = 0x0202; + } public static class TestStructure1 extends FilledStructure { public static final List FIELDS = createFieldsOrder("field0", "field1"); public byte field0 = 0x01; @@ -236,6 +242,11 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1" }) + public static class AnnotatedTestStructure1 extends FilledStructure { + public byte field0 = 0x01; + public int field1 = 0x02020202; + } public static class TestStructure2 extends FilledStructure { public static final List FIELDS = createFieldsOrder("field0", "field1"); public short field0 = 0x0101; @@ -245,6 +256,11 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1" }) + public static class AnnotatedTestStructure2 extends FilledStructure { + public short field0 = 0x0101; + public int field1 = 0x02020202; + } public static class TestStructure3 extends FilledStructure { public static final List FIELDS = createFieldsOrder("field0", "field1", "field2"); public int field0 = 0x01010101; @@ -255,6 +271,12 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1", "field2" }) + public static class AnnotatedTestStructure3 extends FilledStructure { + public int field0 = 0x01010101; + public short field1 = 0x0202; + public int field2 = 0x03030303; + } public static class TestStructure4 extends FilledStructure { public static final List FIELDS = createFieldsOrder("field0", "field1", "field2", "field3"); public int field0 = 0x01010101; @@ -266,6 +288,23 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1", "field2", "field3" }) + public static class AnnotatedTestStructure4 extends FilledStructure { + public int field0 = 0x01010101; + public long field1 = 0x0202020202020202L; + public int field2 = 0x03030303; + public long field3 = 0x0404040404040404L; + } + @FieldOrder({ "field0", "field1" }) + public static class _InheritanceTestStructure4Parent extends FilledStructure { + public int field0 = 0x01010101; + public long field1 = 0x0202020202020202L; + } + @FieldOrder({ "field2", "field3" }) + public static class InheritanceTestStructure4 extends _InheritanceTestStructure4Parent { + public int field2 = 0x03030303; + public long field3 = 0x0404040404040404L; + } public static class TestStructure5 extends FilledStructure { public static final List FIELDS = createFieldsOrder("field0", "field1"); public long field0 = 0x0101010101010101L; @@ -275,13 +314,21 @@ protected List getFieldOrder() { return FIELDS; } } + @FieldOrder({ "field0", "field1" }) + public static class AnnotatedTestStructure5 extends FilledStructure { + public long field0 = 0x0101010101010101L; + public byte field1 = 0x02; + } public interface SizeTest extends Library { int getStructureSize(int type); } private void testStructureSize(int index) { + testStructureSize("", index); + } + private void testStructureSize(String prefix, int index) { try { SizeTest lib = Native.loadLibrary("testlib", SizeTest.class); - Class cls = (Class) Class.forName(getClass().getName() + "$TestStructure" + index); + Class cls = (Class) Class.forName(getClass().getName() + "$" + prefix + "TestStructure" + index); Structure s = Structure.newInstance(cls); assertEquals("Incorrect size for structure " + index + "=>" + s.toString(true), lib.getStructureSize(index), s.size()); } @@ -292,21 +339,42 @@ private void testStructureSize(int index) { public void testStructureSize0() { testStructureSize(0); } + public void annotatedTestStructureSize0() { + testStructureSize("Eazy", 0); + } public void testStructureSize1() { testStructureSize(1); } + public void annotatedTestStructureSize1() { + testStructureSize("Eazy", 1); + } public void testStructureSize2() { testStructureSize(2); } + public void annotatedTestStructureSize2() { + testStructureSize("Eazy", 2); + } public void testStructureSize3() { testStructureSize(3); } + public void annotatedTestStructureSize3() { + testStructureSize("Eazy", 3); + } public void testStructureSize4() { testStructureSize(4); } + public void annotatedTestStructureSize4() { + testStructureSize("Eazy", 4); + } + public void inheritanceTestStructureSize4() { + testStructureSize("Inheritance", 4); + } public void testStructureSize5() { testStructureSize(5); } + public void annotatedTestStructureSize5() { + testStructureSize("Eazy", 5); + } public interface AlignmentTest extends Library { int testStructureAlignment(Structure s, int type, @@ -314,11 +382,14 @@ int testStructureAlignment(Structure s, int type, } private void testAlignStruct(int index) { + testAlignStruct("", index); + } + private void testAlignStruct(String prefix,int index) { AlignmentTest lib = Native.loadLibrary("testlib", AlignmentTest.class); try { IntByReference offset = new IntByReference(); LongByReference value = new LongByReference(); - Class cls = Class.forName(getClass().getName() + "$TestStructure" + index); + Class cls = Class.forName(getClass().getName() + "$" + prefix + "TestStructure" + index); Structure s = (Structure)cls.newInstance(); int result = lib.testStructureAlignment(s, index, offset, value); assertEquals("Wrong native value at field " + result @@ -333,21 +404,58 @@ private void testAlignStruct(int index) { public void testAlignStruct0() { testAlignStruct(0); } + public void annotatedTestAlignStruct0() { + testAlignStruct("Annotated", 0); + } public void testAlignStruct1() { testAlignStruct(1); } + public void annotatedTestAlignStruct1() { + testAlignStruct("Annotated", 1); + } public void testAlignStruct2() { testAlignStruct(2); } + public void annotatedTestAlignStruct2() { + testAlignStruct("Annotated", 2); + } public void testAlignStruct3() { testAlignStruct(3); } + public void annotatedTestAlignStruct3() { + testAlignStruct("Annotated", 3); + } public void testAlignStruct4() { testAlignStruct(4); } + public void annotatedTestAlignStruct4() { + testAlignStruct("Annotated", 4); + } + public void inheritanceTestAlignStruct4() { + testAlignStruct("Inheritance", 4); + } public void testAlignStruct5() { testAlignStruct(5); } + public void annotatedTestAlignStruct5() { + testAlignStruct("Annotated", 5); + } + + public void testInheritanceFieldOrder() { + @FieldOrder({ "a", "b" }) + class Parent extends Structure { + public int a; + public int b; + } + @FieldOrder({ "c", "d" }) + class Son extends Parent { + public int c; + public int d; + } + + Son test = new Son(); + assertEquals("Wrong field order", Arrays.asList("a", "b", "c", "d"), test.getFieldOrder()); + } public void testStructureWithNoFields() { class TestStructure extends Structure { @@ -365,6 +473,31 @@ protected List getFieldOrder() { } } + public void testAnnotatedStructureWithNoFields() { + class TestStructure extends Structure { + } + try { + new TestStructure(); + fail("Structure should not be instantiable if it has no public member fields"); + } + catch(IllegalArgumentException e) { + // expected - ignored + } + } + + public void testAnnotatedStructureWithNoFieldsWithAnnot() { + @FieldOrder({ }) + class TestStructure extends Structure { + } + try { + new TestStructure(); + fail("Structure should not be instantiable if it has no public member fields"); + } + catch(IllegalArgumentException e) { + // expected - ignored + } + } + public void testStructureWithOnlyNonPublicMemberFields() { class TestStructure extends Structure { int field; diff --git a/www/GettingStarted.md b/www/GettingStarted.md index a577291246..d620c31c46 100644 --- a/www/GettingStarted.md +++ b/www/GettingStarted.md @@ -6,34 +6,35 @@ Java Native Access (JNA) has a single component, `jna.jar`; the supporting nativ Begin by downloading the latest release of JNA and referencing `jna.jar` in your project's `CLASSPATH`. The following example maps the printf function from the standard C library and calls it. +``` java +package com.sun.jna.examples; - package com.sun.jna.examples; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; - import com.sun.jna.Library; - import com.sun.jna.Native; - import com.sun.jna.Platform; +/** Simple example of JNA interface mapping and usage. */ +public class HelloWorld { - /** Simple example of JNA interface mapping and usage. */ - public class HelloWorld { + // This is the standard, stable way of mapping, which supports extensive + // customization and mapping of Java to native types. - // This is the standard, stable way of mapping, which supports extensive - // customization and mapping of Java to native types. + public interface CLibrary extends Library { + CLibrary INSTANCE = (CLibrary) + Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), + CLibrary.class); - public interface CLibrary extends Library { - CLibrary INSTANCE = (CLibrary) - Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), - CLibrary.class); - - void printf(String format, Object... args); - } + void printf(String format, Object... args); + } - public static void main(String[] args) { - CLibrary.INSTANCE.printf("Hello, World\n"); - for (int i=0;i < args.length;i++) { - CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]); - } + public static void main(String[] args) { + CLibrary.INSTANCE.printf("Hello, World\n"); + for (int i=0;i < args.length;i++) { + CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]); } } +} +``` Identify a native target library that you want to use. This can be any shared library with exported functions. Many examples of mappings for common system libraries, especially on Windows, may be found in the platform package. @@ -46,56 +47,56 @@ Make your target library available to your Java program. There are several ways Declare a Java interface to hold the native library methods by extending the Library interface. Following is an example of mapping for the Windows kernel32 library. +``` java +package com.sun.jna.examples.win32; - package com.sun.jna.examples.win32; +import com.sun.jna.*; - import com.sun.jna.*; - - // kernel32.dll uses the __stdcall calling convention (check the function - // declaration for "WINAPI" or "PASCAL"), so extend StdCallLibrary - // Most C libraries will just extend com.sun.jna.Library, - public interface Kernel32 extends StdCallLibrary { - // Method declarations, constant and structure definitions go here - } +// kernel32.dll uses the __stdcall calling convention (check the function +// declaration for "WINAPI" or "PASCAL"), so extend StdCallLibrary +// Most C libraries will just extend com.sun.jna.Library, +public interface Kernel32 extends StdCallLibrary { + // Method declarations, constant and structure definitions go here +} +``` Within this interface, define an instance of the native library using the Native.loadLibrary(Class) method, providing the native library interface you defined previously. - - Kernel32 INSTANCE = (Kernel32) - Native.loadLibrary("kernel32", Kernel32.class); - // Optional: wraps every call to the native library in a - // synchronized block, limiting native calls to one at a time - Kernel32 SYNC_INSTANCE = (Kernel32) - Native.synchronizedLibrary(INSTANCE); +``` java +Kernel32 INSTANCE = (Kernel32) + Native.loadLibrary("kernel32", Kernel32.class); +// Optional: wraps every call to the native library in a +// synchronized block, limiting native calls to one at a time +Kernel32 SYNC_INSTANCE = (Kernel32) + Native.synchronizedLibrary(INSTANCE); +``` The `INSTANCE` variable is for convenient reuse of a single instance of the library. Alternatively, you can load the library into a local variable so that it will be available for garbage collection when it goes out of scope. A Map of options may be provided as the third argument to loadLibrary to customize the library behavior; some of these options are explained in more detail below. The `SYNC_INSTANCE` is also optional; use it if you need to ensure that your native library has only one call to it at a time. Declare methods that mirror the functions in the target library by defining Java methods with the same name and argument types as the native function (refer to the basic mappings below or the detailed table of type mappings). You may also need to declare native structures to pass to your native functions. To do this, create a class within the interface definition that extends Structure and add public fields (which may include arrays or nested structures). - - public static class SYSTEMTIME extends Structure { - public short wYear; - public short wMonth; - public short wDayOfWeek; - public short wDay; - public short wHour; - public short wMinute; - public short wSecond; - public short wMilliseconds; - protected List getFieldOrder() { - return Arrays.asList(new String[] { - "wYear", "wMonth", "wDayOfWeek", "wDay", "wHour", "wMinute", "wSecond", "wMilliseconds", - }); - } - } - - void GetSystemTime(SYSTEMTIME result); +``` java +@FieldOrder({ "wYear", "wMonth", "wDayOfWeek", "wDay", "wHour", "wMinute", "wSecond", "wMilliseconds" }) +public static class SYSTEMTIME extends Structure { + public short wYear; + public short wMonth; + public short wDayOfWeek; + public short wDay; + public short wHour; + public short wMinute; + public short wSecond; + public short wMilliseconds; +} + +void GetSystemTime(SYSTEMTIME result); +``` You can now invoke methods on the library instance just like any other Java class. +``` java +Kernel32 lib = Kernel32.INSTANCE; +SYSTEMTIME time = new SYSTEMTIME(); +lib.GetSystemTime(time); - Kernel32 lib = Kernel32.INSTANCE; - SYSTEMTIME time = new SYSTEMTIME(); - lib.GetSystemTime(time); - - System.out.println("Today's integer value is " + time.wDay); +System.out.println("Today's integer value is " + time.wDay); +``` Several [example applications](https://github.com/java-native-access/jna/tree/master/contrib) are available, as well as [platform-specific mappings](https://github.com/java-native-access/jna/tree/master/contrib/platform). diff --git a/www/StructuresAndUnions.md b/www/StructuresAndUnions.md index 3fa7ecb9a9..65dd7b8b9a 100644 --- a/www/StructuresAndUnions.md +++ b/www/StructuresAndUnions.md @@ -1,12 +1,12 @@ Using Structures And Unions =========================== -When a function requires a pointer to a struct, a Java Structure should be used. If the struct is passed or returned by value, you need only make minor modifications to the parameter or return type class declaration. +When a function requires a pointer to a struct, a Java `Structure` should be used. If the struct is passed or returned by value, you need only make minor modifications to the parameter or return type class declaration. -Typically you define a public static class derived from `Structure` within your library interface definition. This allows the structure to share any options (like custom type mapping) defined for the library interface. You must include each declared field name in order in the list returned by the `getFieldOrder()` method. +Typically you define a public static class derived from `Structure` within your library interface definition. This allows the structure to share any options (like custom type mapping) defined for the library interface. You must include each declared field name in order in the `FieldOrder` annotation or the list returned by the `getFieldOrder()` method. If a function requires an array of struct (allocated contiguously in memory), a Java `Structure[]` may be used. When passing in an array of `Structure`, it is not necessary to initialize the array elements (the function call will allocate, zero memory, and assign the elements for you). If you do need to initialize the array, you should use the `Structure.toArray` method to obtain an array of Structure elements contiguous in memory, which you can then initialize as needed. -Unions are generally interchangeable with Structures, but require that you indicate which union field is active with the `setType` method before it can be properly passed to a function call. +`Union`s are generally interchangeable with `Structure`s, but require that you indicate which union field is active with the `setType` method before it can be properly passed to a function call. If you have particularly long or complicated structures, you might consider using the [JNAerator](http://code.google.com/p/jnaerator/) tool written by Olivier Chafik which can generate JNA mappings for you.