From 7d69ab4633a3f6ed704a2f4c8a44f81dd2e933ea Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Wed, 15 May 2024 22:13:01 +0200 Subject: [PATCH 01/15] Patch g_strfreev to make it usable from java --- .../jwharm/javagi/patches/GLibPatch.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/patches/GLibPatch.java b/buildSrc/src/main/java/io/github/jwharm/javagi/patches/GLibPatch.java index b30cd97a..6f10fd96 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/patches/GLibPatch.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/patches/GLibPatch.java @@ -86,6 +86,26 @@ public GirElement patch(GirElement element, String namespace) { return r; } + /* + * g_strfreev is nicely specified in the Gir file to take an array + * of Strings. However, this makes Java-GI generate code to allocate + * a new memory segment from a Java String[] parameter. So we patch + * it to expect a pointer (a MemorySegment parameter in Java). + */ + if (element instanceof Function f + && "g_strfreev".equals(f.callableAttrs().cIdentifier())) { + Parameter current = f.parameters().parameters().getFirst(); + Parameter replacement = current.withChildren( + current.infoElements().doc(), + new Type(Map.of("name", "gpointer", "c:type", "gpointer"), + Collections.emptyList())); + return f.withChildren( + f.infoElements().doc(), + f.infoElements().sourcePosition(), + f.returnValue(), + f.parameters().withChildren(replacement)); + } + /* * GLib.List and GLib.SList are not generated from the gir data. * Java-GI provides custom List and SList classes that implement From f2acb5b36dd6401e25725869cf36bd686b370633 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Wed, 15 May 2024 22:14:17 +0200 Subject: [PATCH 02/15] Marshal native char*** to Java String[][] --- .../generators/TypedValueGenerator.java | 30 +++++----- .../jwharm/javagi/test/gio/StrvArrayTest.java | 28 +++++++++ .../github/jwharm/javagi/interop/Interop.java | 60 +++++++++++++++---- 3 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 modules/gio/src/test/java/io/github/jwharm/javagi/test/gio/StrvArrayTest.java diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/TypedValueGenerator.java b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/TypedValueGenerator.java index a822607a..88944f0e 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/TypedValueGenerator.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/TypedValueGenerator.java @@ -72,6 +72,14 @@ boolean checkNull() { || target instanceof FlaggedType)); } + String doFree() { + return switch(v) { + case Parameter p when p.transferOwnership() == TransferOwnership.FULL -> "true"; + case ReturnValue rv when rv.transferOwnership() == TransferOwnership.FULL -> "true"; + default -> "false"; + }; + } + TypeName getType() { return getType(true); } @@ -261,8 +269,12 @@ PartialStatement marshalNativeToJava(String identifier, boolean upcall) { return marshalNativeToJava(type, identifier, upcall); } - if (array != null && array.anyType() instanceof Array) - return PartialStatement.of("null /* unsupported */"); + if (array != null && array.anyType() instanceof Array inner + && inner.anyType() instanceof Type t + && t.isString()) + return PartialStatement.of( + "$interop:T.getStrvArrayFrom(" + identifier + ", " + doFree() + ")", + "interop", ClassNames.INTEROP); if (array != null && array.anyType() instanceof Type arrayType) return marshalNativeToJavaArray( @@ -277,12 +289,7 @@ PartialStatement marshalNativeToJava(String identifier, boolean upcall) { PartialStatement marshalNativeToJava(Type type, String identifier, boolean upcall) { - String free = switch(v) { - case Parameter p when p.transferOwnership() == TransferOwnership.FULL -> "true"; - case ReturnValue rv when rv.transferOwnership() == TransferOwnership.FULL -> "true"; - default -> "false"; - }; - + String free = doFree(); String targetTypeTag = target == null ? null : type.toTypeTag(); boolean isTypeClass = target instanceof Record @@ -376,12 +383,7 @@ PartialStatement marshalNativeToJava(Type type, private PartialStatement marshalNativeToJavaArray(Type type, String size, String identifier) { - String free = switch(v) { - case Parameter p when p.transferOwnership() == TransferOwnership.FULL -> "true"; - case ReturnValue rv when rv.transferOwnership() == TransferOwnership.FULL -> "true"; - default -> "false"; - }; - + String free = doFree(); RegisteredType target = type.get(); String targetTypeTag = target != null ? type.toTypeTag() : null; diff --git a/modules/gio/src/test/java/io/github/jwharm/javagi/test/gio/StrvArrayTest.java b/modules/gio/src/test/java/io/github/jwharm/javagi/test/gio/StrvArrayTest.java new file mode 100644 index 00000000..02bbd428 --- /dev/null +++ b/modules/gio/src/test/java/io/github/jwharm/javagi/test/gio/StrvArrayTest.java @@ -0,0 +1,28 @@ +package io.github.jwharm.javagi.test.gio; + +import org.gnome.gio.DesktopAppInfo; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Get a String[][] array from a Gio function, and check that it contains + * usable data. + */ +public class StrvArrayTest { + + @Test + public void testStrvArrayToJava() { + String[][] array = DesktopAppInfo.search("gnome"); + assertNotNull(array); + String result = ""; + for (String[] inner : array) { + assertNotNull(inner); + for (String str : inner) { + if (str.contains("org.gnome")) + result = str; + } + } + assertNotEquals("", result); + } +} diff --git a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java index a7609025..c00dfbfc 100644 --- a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java +++ b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java @@ -32,6 +32,7 @@ import io.github.jwharm.javagi.base.*; import org.gnome.glib.Type; +import org.jetbrains.annotations.Nullable; /** * The Interop class contains functionality for interoperability with native @@ -39,7 +40,8 @@ */ public class Interop { - private final static int UNBOUNDED = Integer.MAX_VALUE; + private final static int INT_UNBOUNDED = Integer.MAX_VALUE; + private final static long LONG_UNBOUNDED = Long.MAX_VALUE; private final static Linker LINKER = Linker.nativeLinker(); public static SymbolLookup symbolLookup = SymbolLookup.loaderLookup() .or(Linker.nativeLinker().defaultLookup()); @@ -240,7 +242,7 @@ public static String getStringFrom(MemorySegment address, boolean free) { return null; try { - return address.reinterpret(Long.MAX_VALUE).getString(0); + return address.reinterpret(LONG_UNBOUNDED).getString(0); } finally { if (free) GLib.free(address); @@ -275,7 +277,7 @@ public static String[] getStringArrayFrom(MemorySegment address, } if (free) - GLib.free(array); + GLib.strfreev(array); return result; } @@ -294,14 +296,14 @@ public static String[] getStringArrayFrom(MemorySegment address, return null; MemorySegment array = address; - if (array.byteSize() == 0) { - array = address.reinterpret(Long.MAX_VALUE); - } + if (array.byteSize() == 0) + array = address.reinterpret(LONG_UNBOUNDED); ArrayList result = new ArrayList<>(); long offset = 0; while (true) { - MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset); + MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset) + .reinterpret(LONG_UNBOUNDED); if (MemorySegment.NULL.equals(ptr)) break; result.add(ptr.getString(0)); @@ -309,11 +311,45 @@ public static String[] getStringArrayFrom(MemorySegment address, } if (free) - GLib.free(address); + GLib.strfreev(array); return result.toArray(new String[0]); } + /** + * Read {@code NULL}-terminated arrays of Strings from a + * {@code NULL}-terminated array in native memory. + * + * @param address address of the memory segment + * @param free if the strings and the array must be freed + * @return two-dimensional array of Strings + */ + public static String[][] getStrvArrayFrom(MemorySegment address, + boolean free) { + if (address == null || MemorySegment.NULL.equals(address)) + return null; + + MemorySegment array = address; + if (array.byteSize() == 0) + array = address.reinterpret(LONG_UNBOUNDED); + + ArrayList result = new ArrayList<>(); + + long offset = 0; + while (true) { + MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset); + if (MemorySegment.NULL.equals(ptr)) + break; + result.add(getStringArrayFrom(ptr, free)); + offset += ValueLayout.ADDRESS.byteSize(); + } + + if (free) + GLib.free(address); + + return result.toArray(new String[0][0]); + } + /** * Read an array of pointers with the requested length from native memory. * @@ -363,7 +399,7 @@ public static MemorySegment[] getAddressArrayFrom(MemorySegment address, MemorySegment array = address; if (array.byteSize() == 0) - array = address.reinterpret(Long.MAX_VALUE); + array = address.reinterpret(LONG_UNBOUNDED); ArrayList result = new ArrayList<>(); long offset = 0; @@ -441,7 +477,7 @@ public static byte[] getByteArrayFrom(MemorySegment address, Arena arena, boolean free) { // Find the null byte - MemorySegment array = address.reinterpret(Long.MAX_VALUE, arena, null); + MemorySegment array = address.reinterpret(LONG_UNBOUNDED, arena, null); long idx = 0; while (array.get(ValueLayout.JAVA_BYTE, idx) != 0) { idx++; @@ -555,7 +591,7 @@ public static int[] getIntegerArrayFrom(MemorySegment address, boolean free) { // Find the null byte - MemorySegment array = address.reinterpret(UNBOUNDED, arena, null); + MemorySegment array = address.reinterpret(INT_UNBOUNDED, arena, null); long idx = 0; while (array.get(ValueLayout.JAVA_INT, idx) != 0) { idx++; @@ -631,7 +667,7 @@ public static short[] getShortArrayFrom(MemorySegment address, MemorySegment array = address; if (array.byteSize() == 0) - array = address.reinterpret(Long.MAX_VALUE); + array = address.reinterpret(LONG_UNBOUNDED); long offset = 0; while (!MemorySegment.NULL.equals( From 828d21ef17b8c6e904d855e98398bddf66f1dce4 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Thu, 16 May 2024 20:29:54 +0200 Subject: [PATCH 03/15] Fix inconsistent sentence in website --- website/docs/register.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/register.md b/website/docs/register.md index dfbafe63..d0614f23 100644 --- a/website/docs/register.md +++ b/website/docs/register.md @@ -83,7 +83,7 @@ When a virtual method is not available as a regular instance method, you can saf ## Properties -You can define GObject properties with the `@Property` annotation on the getter and setter methods. You must annotate both the getter and setter methods (if applicable). The `@Property` annotation can optionally specify the `name` parameter; all other parameters are optional. +You can define GObject properties with the `@Property` annotation on the getter and setter methods. You must annotate both the getter and setter methods (if applicable). All `@Property` annotation parameters are optional. Example definition of an `int` property with name `n-items`: From b77b081c62a21186862bb0dcd6d197b1f307a8c2 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Thu, 16 May 2024 20:39:27 +0200 Subject: [PATCH 04/15] Add dependency-submission.yml --- .github/workflows/dependency-submission.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/dependency-submission.yml diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 00000000..7f38559b --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,21 @@ +name: Dependency Submission + +on: + push: + branches: [ 'main' ] + +permissions: + contents: write + +jobs: + dependency-submission: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 22 + + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v3 From e02cd8d73941e2706ecb8300a2c2c69424802066 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij <112160955+jwharm@users.noreply.github.com> Date: Thu, 16 May 2024 20:41:14 +0200 Subject: [PATCH 05/15] Update dependency-submission.yml --- .github/workflows/dependency-submission.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index 7f38559b..00794414 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: temurin - java-version: 22 + java-version: 17 - name: Generate and submit dependency graph uses: gradle/actions/dependency-submission@v3 From ae3e12cc0643d33472d95187e8c0eb82a9f3f841 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij <112160955+jwharm@users.noreply.github.com> Date: Thu, 16 May 2024 20:45:23 +0200 Subject: [PATCH 06/15] Update dependency-submission.yml --- .github/workflows/dependency-submission.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index 00794414..ad6bb00d 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -15,7 +15,9 @@ jobs: - uses: actions/setup-java@v4 with: distribution: temurin - java-version: 17 + java-version: 22 - name: Generate and submit dependency graph uses: gradle/actions/dependency-submission@v3 + with: + gradle-version: 8.7 From 6bb489eeeb42fdc09575afc76e84f553ad554468 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij <112160955+jwharm@users.noreply.github.com> Date: Thu, 16 May 2024 20:49:17 +0200 Subject: [PATCH 07/15] Update dependency-submission.yml --- .github/workflows/dependency-submission.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml index ad6bb00d..930910e3 100644 --- a/.github/workflows/dependency-submission.yml +++ b/.github/workflows/dependency-submission.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: temurin - java-version: 22 + java-version: 21 - name: Generate and submit dependency graph uses: gradle/actions/dependency-submission@v3 From b487b5d1eab5d58fccf29900e825d749eece5b62 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Wed, 29 May 2024 22:23:28 +0200 Subject: [PATCH 08/15] Remove unnecessary null-check in constructors; some indentation fixes --- .../javagi/generators/ClassGenerator.java | 133 +++++++++--------- .../generators/RegisteredTypeGenerator.java | 8 +- 2 files changed, 71 insertions(+), 70 deletions(-) diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/ClassGenerator.java b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/ClassGenerator.java index 3d4b1191..7f7bc985 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/ClassGenerator.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/ClassGenerator.java @@ -130,15 +130,15 @@ private MethodSpec parentAccessor() { return MethodSpec.methodBuilder("asParent") .addJavadoc(""" - Returns this instance as if it were its parent type. This is mostly - synonymous to the Java {@code super} keyword, but will set the native - typeclass function pointers to the parent type. When overriding a native - virtual method in Java, "chaining up" with {@code super.methodName()} - doesn't work, because it invokes the overridden function pointer again. - To chain up, call {@code asParent().methodName()}. This will call the - native function pointer of this virtual method in the typeclass of the - parent type. - """) + Returns this instance as if it were its parent type. This is mostly + synonymous to the Java {@code super} keyword, but will set the native + typeclass function pointers to the parent type. When overriding a native + virtual method in Java, "chaining up" with {@code super.methodName()} + doesn't work, because it invokes the overridden function pointer again. + To chain up, call {@code asParent().methodName()}. This will call the + native function pointer of this virtual method in the typeclass of the + parent type. + """) .addModifiers(Modifier.PROTECTED) .returns(cls.typeName()) .addStatement("$T _parent = new $T(handle())", cls.typeName(), className) @@ -153,10 +153,11 @@ protected MethodSpec memoryAddressConstructor() { .addModifiers(Modifier.PUBLIC) .addJavadoc(""" Create a $L proxy instance for the provided memory address. + @param address the memory address of the native object """, name()) .addParameter(MemorySegment.class, "address") - .addStatement("super(address == null ? null : $T.reinterpret(address, getMemoryLayout().byteSize()))", + .addStatement("super($T.reinterpret(address, getMemoryLayout().byteSize()))", ClassNames.INTEROP); /* @@ -181,10 +182,10 @@ protected MethodSpec memoryAddressConstructor() { protected MethodSpec paramSpecGetTypeMethod() { return MethodSpec.methodBuilder("getType") .addJavadoc(""" - Get the GType of the $L class - - @return always {@link $T#PARAM} - """, cls.cType(), ClassNames.TYPES) + Get the GType of the $L class + + @return always {@link $T#PARAM} + """, cls.cType(), ClassNames.TYPES) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(ClassNames.GTYPE) .addStatement("return $T.PARAM", ClassNames.TYPES) @@ -194,11 +195,11 @@ protected MethodSpec paramSpecGetTypeMethod() { private MethodSpec gobjectConstructor() { return MethodSpec.methodBuilder("newInstance") .addJavadoc(""" - Creates a new GObject instance of the provided GType. - - @param objectType the GType of the new GObject - @return the newly created GObject instance - """) + Creates a new GObject instance of the provided GType. + + @param objectType the GType of the new GObject + @return the newly created GObject instance + """) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("T", ClassNames.GOBJECT)) .returns(TypeVariableName.get("T")) @@ -213,15 +214,15 @@ private MethodSpec gobjectConstructor() { private MethodSpec gobjectConstructorVarargs() { return MethodSpec.methodBuilder("newInstance") .addJavadoc(""" - Creates a new GObject instance of the provided GType and with the - provided property values. - - @param objectType the GType of the new GObject - @param propertyNamesAndValues pairs of property names and values - (Strings and Objects) - @return the newly created GObject instance - @throws IllegalArgumentException invalid property name - """) + Creates a new GObject instance of the provided GType and with the + provided property values. + + @param objectType the GType of the new GObject + @param propertyNamesAndValues pairs of property names and values + (Strings and Objects) + @return the newly created GObject instance + @throws IllegalArgumentException invalid property name + """) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariable(TypeVariableName.get("T", ClassNames.GOBJECT)) .returns(TypeVariableName.get("T")) @@ -236,12 +237,12 @@ private MethodSpec gobjectConstructorVarargs() { private MethodSpec gobjectGetProperty() { return MethodSpec.methodBuilder("getProperty") .addJavadoc(""" - Get a property of an object. - - @param propertyName the name of the property to get - @return the property value - @throws IllegalArgumentException invalid property name - """) + Get a property of an object. + + @param propertyName the name of the property to get + @return the property value + @throws IllegalArgumentException invalid property name + """) .addModifiers(Modifier.PUBLIC) .returns(Object.class) .addParameter(String.class, "propertyName") @@ -253,12 +254,12 @@ private MethodSpec gobjectGetProperty() { private MethodSpec gobjectSetProperty() { return MethodSpec.methodBuilder("setProperty") .addJavadoc(""" - Set a property of an object. - - @param propertyName the name of the property to set - @param value the new property value - @throws IllegalArgumentException invalid property name - """) + Set a property of an object. + + @param propertyName the name of the property to set + @param value the new property value + @throws IllegalArgumentException invalid property name + """) .addModifiers(Modifier.PUBLIC) .addParameter(String.class, "propertyName") .addParameter(Object.class, "value") @@ -270,14 +271,14 @@ private MethodSpec gobjectSetProperty() { private MethodSpec gobjectConnect() { return MethodSpec.methodBuilder("connect") .addJavadoc(""" - Connect a callback to a signal for this object. The handler will be - called before the default handler of the signal. - - @param detailedSignal a string of the form "signal-name::detail" - @param callback the callback to connect - @return a SignalConnection object to track, block and disconnect the - signal connection - """) + Connect a callback to a signal for this object. The handler will be + called before the default handler of the signal. + + @param detailedSignal a string of the form "signal-name::detail" + @param callback the callback to connect + @return a SignalConnection object to track, block and disconnect the + signal connection + """) .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("T")) .returns(ClassNames.SIGNAL_CONNECTION) @@ -290,15 +291,15 @@ private MethodSpec gobjectConnect() { private MethodSpec gobjectConnectAfter() { return MethodSpec.methodBuilder("connect") .addJavadoc(""" - Connect a callback to a signal for this object. - - @param detailedSignal a string of the form "signal-name::detail" - @param callback the callback to connect - @param after whether the handler should be called before or - after the default handler of the signal - @return a SignalConnection object to track, block and disconnect the - signal connection - """) + Connect a callback to a signal for this object. + + @param detailedSignal a string of the form "signal-name::detail" + @param callback the callback to connect + @param after whether the handler should be called before or + after the default handler of the signal + @return a SignalConnection object to track, block and disconnect the + signal connection + """) .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("T")) .returns(ClassNames.SIGNAL_CONNECTION) @@ -317,15 +318,15 @@ private MethodSpec gobjectConnectAfter() { private MethodSpec gobjectEmit() { return MethodSpec.methodBuilder("emit") .addJavadoc(""" - Emits a signal from this object. - - @param detailedSignal a string of the form "signal-name::detail" - @param params the parameters to emit for this signal - @return the return value of the signal, or {@code null} if the signal - has no return value - @throws IllegalArgumentException if a signal with this name is not found - for the object - """) + Emits a signal from this object. + + @param detailedSignal a string of the form "signal-name::detail" + @param params the parameters to emit for this signal + @return the return value of the signal, or {@code null} if the signal + has no return value + @throws IllegalArgumentException if a signal with this name is not found + for the object + """) .addModifiers(Modifier.PUBLIC) .returns(Object.class) .addParameter(String.class, "detailedSignal") diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java index 2827b38a..af41f303 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java @@ -125,10 +125,10 @@ protected MethodSpec memoryAddressConstructor() { MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addJavadoc(""" - Create a $L proxy instance for the provided memory address. - - @param address the memory address of the native object - """, name()) + Create a $L proxy instance for the provided memory address. + + @param address the memory address of the native object + """, name()) .addParameter(MemorySegment.class, "address"); if (rt instanceof Record rec && rec.isOpaque() From 6ab6d9d1ea39248e77173d0072cdaea38ac1ae2e Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Wed, 29 May 2024 22:24:23 +0200 Subject: [PATCH 09/15] Various cleanups in interop functions --- .../github/jwharm/javagi/interop/Interop.java | 168 +++++++----------- 1 file changed, 69 insertions(+), 99 deletions(-) diff --git a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java index c00dfbfc..649d076c 100644 --- a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java +++ b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java @@ -32,7 +32,8 @@ import io.github.jwharm.javagi.base.*; import org.gnome.glib.Type; -import org.jetbrains.annotations.Nullable; + +import static java.lang.foreign.MemorySegment.NULL; /** * The Interop class contains functionality for interoperability with native @@ -206,9 +207,7 @@ public static Type getType(String getTypeFunction) { */ public static MemorySegment allocateNativeString(String string, SegmentAllocator alloc) { - return string == null - ? MemorySegment.NULL - : alloc.allocateFrom(string); + return string == null ? NULL : alloc.allocateFrom(string); } /** @@ -238,7 +237,7 @@ public static String getStringFrom(MemorySegment address) { */ public static String getStringFrom(MemorySegment address, boolean free) { - if (MemorySegment.NULL.equals(address)) + if (NULL.equals(address)) return null; try { @@ -261,24 +260,22 @@ public static String[] getStringArrayFrom(MemorySegment address, int length, boolean free) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; long size = ValueLayout.ADDRESS.byteSize(); - if (array.byteSize() == 0) - array = address.reinterpret(size * length); + MemorySegment array = reinterpret(address, size * length); String[] result = new String[length]; for (int i = 0; i < length; i++) { - result[i] = array.getString(i * size); + MemorySegment ptr = array.getAtIndex(ValueLayout.ADDRESS, i); + result[i] = getStringFrom(ptr); if (free) GLib.free(array.getAtIndex(ValueLayout.ADDRESS, i)); } if (free) - GLib.strfreev(array); - + GLib.free(array); return result; } @@ -292,21 +289,18 @@ public static String[] getStringArrayFrom(MemorySegment address, */ public static String[] getStringArrayFrom(MemorySegment address, boolean free) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(LONG_UNBOUNDED); + MemorySegment array = reinterpret(address, LONG_UNBOUNDED); ArrayList result = new ArrayList<>(); long offset = 0; while (true) { - MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset) - .reinterpret(LONG_UNBOUNDED); - if (MemorySegment.NULL.equals(ptr)) + MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset); + if (NULL.equals(ptr)) break; - result.add(ptr.getString(0)); + result.add(getStringFrom(ptr)); offset += ValueLayout.ADDRESS.byteSize(); } @@ -326,19 +320,16 @@ public static String[] getStringArrayFrom(MemorySegment address, */ public static String[][] getStrvArrayFrom(MemorySegment address, boolean free) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(LONG_UNBOUNDED); + MemorySegment array = reinterpret(address, LONG_UNBOUNDED); ArrayList result = new ArrayList<>(); - long offset = 0; while (true) { MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset); - if (MemorySegment.NULL.equals(ptr)) + if (NULL.equals(ptr)) break; result.add(getStringArrayFrom(ptr, free)); offset += ValueLayout.ADDRESS.byteSize(); @@ -355,27 +346,22 @@ public static String[][] getStrvArrayFrom(MemorySegment address, * * @param address address of the memory segment * @param length length of the array - * @param free if the addresses and the array must be freed + * @param free if the array must be freed * @return array of pointers */ public static MemorySegment[] getAddressArrayFrom(MemorySegment address, int length, boolean free) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; long size = ValueLayout.ADDRESS.byteSize(); - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(size * length); + MemorySegment array = reinterpret(address, size * length); MemorySegment[] result = new MemorySegment[length]; - for (int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) result[i] = array.getAtIndex(ValueLayout.ADDRESS, i); - if (free) - GLib.free(array.getAtIndex(ValueLayout.ADDRESS, i)); - } if (free) GLib.free(address); @@ -388,24 +374,22 @@ public static MemorySegment[] getAddressArrayFrom(MemorySegment address, * memory. * * @param address address of the memory segment - * @param free if the addresses and the array must be freed + * @param free if the array must be freed * @return array of pointers */ public static MemorySegment[] getAddressArrayFrom(MemorySegment address, boolean free) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(LONG_UNBOUNDED); + MemorySegment array = reinterpret(address, LONG_UNBOUNDED); ArrayList result = new ArrayList<>(); long offset = 0; while (true) { MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset); - if (MemorySegment.NULL.equals(ptr)) + if (NULL.equals(ptr)) break; result.add(ptr); offset += ValueLayout.ADDRESS.byteSize(); @@ -657,20 +641,18 @@ public static short[] getShortArrayFrom(MemorySegment address, * @param the type of the Proxy instances * @return array of Proxy instances */ - public static T[] - getProxyArrayFrom(MemorySegment address, - Class cls, - Function make) { + public static + T[] getProxyArrayFrom(MemorySegment address, + Class cls, + Function make) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(LONG_UNBOUNDED); + MemorySegment array = reinterpret(address, LONG_UNBOUNDED); long offset = 0; - while (!MemorySegment.NULL.equals( + while (!NULL.equals( array.get(ValueLayout.ADDRESS, offset))) { offset += ValueLayout.ADDRESS.byteSize(); } @@ -689,19 +671,17 @@ public static short[] getShortArrayFrom(MemorySegment address, * @param the type of the Proxy instances * @return array of Proxy instances */ - public static T[] - getProxyArrayFrom(MemorySegment address, - int length, - Class cls, - Function make) { + public static + T[] getProxyArrayFrom(MemorySegment address, + int length, + Class cls, + Function make) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; long size = AddressLayout.ADDRESS.byteSize(); - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(size * length); + MemorySegment array = reinterpret(address, size * length); @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length); for (int i = 0; i < length; i++) { @@ -721,19 +701,17 @@ public static short[] getShortArrayFrom(MemorySegment address, * @param the type of the Proxy instances * @return array of Proxy instances */ - public static T[] - getStructArrayFrom(MemorySegment address, - int length, - Class cls, - Function make, - MemoryLayout layout) { - - if (address == null || MemorySegment.NULL.equals(address)) + public static + T[] getStructArrayFrom(MemorySegment address, + int length, + Class cls, + Function make, + MemoryLayout layout) { + + if (address == null || NULL.equals(address)) return null; - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(layout.byteSize() * length); + MemorySegment array = reinterpret(address, layout.byteSize() * length); @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length); List elements = array.elements(layout).toList(); @@ -761,13 +739,11 @@ public static T[] getArrayFromIntPointer(MemorySegment address, Class cls, Function make) { - if (address == null || MemorySegment.NULL.equals(address)) + if (address == null || NULL.equals(address)) return null; long size = AddressLayout.ADDRESS.byteSize(); - MemorySegment array = address; - if (array.byteSize() == 0) - array = address.reinterpret(size * length); + MemorySegment array = reinterpret(address, size * length); @SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length); for (int i = 0; i < length; i++) { @@ -793,14 +769,12 @@ public static MemorySegment allocateNativeArray(String[] strings, var memorySegment = arena.allocate(ValueLayout.ADDRESS, length); for (int i = 0; i < strings.length; i++) { - var cString = strings[i] == null ? MemorySegment.NULL - : arena.allocateFrom(strings[i]); - memorySegment.setAtIndex(ValueLayout.ADDRESS, i, cString); + var s = strings[i] == null ? NULL : arena.allocateFrom(strings[i]); + memorySegment.setAtIndex(ValueLayout.ADDRESS, i, s); } if (zeroTerminated) - memorySegment.setAtIndex( - ValueLayout.ADDRESS, strings.length, MemorySegment.NULL); + memorySegment.setAtIndex(ValueLayout.ADDRESS, strings.length, NULL); return memorySegment; } @@ -973,9 +947,7 @@ public static MemorySegment allocateNativeArray(Proxy[] array, MemorySegment[] addressArray = new MemorySegment[array.length]; for (int i = 0; i < array.length; i++) { - addressArray[i] = array[i] == null - ? MemorySegment.NULL - : array[i].handle(); + addressArray[i] = array[i] == null ? NULL : array[i].handle(); } return allocateNativeArray(addressArray, zeroTerminated, arena); @@ -997,29 +969,26 @@ public static MemorySegment allocateNativeArray(Proxy[] array, boolean zeroTerminated, Arena arena) { + long size = layout.byteSize(); int length = zeroTerminated ? array.length + 1 : array.length; - MemorySegment memorySegment = arena.allocate(layout, length); + MemorySegment segment = arena.allocate(layout, length); for (int i = 0; i < array.length; i++) { - if (array[i] != null - && (!MemorySegment.NULL.equals(array[i].handle()))) { + if (array[i] != null && (!NULL.equals(array[i].handle()))) { // Copy array element to the native array MemorySegment element = array[i].handle() .reinterpret(layout.byteSize(), arena, null); - memorySegment.asSlice(i * layout.byteSize()) - .copyFrom(element); + segment.asSlice(i * layout.byteSize()).copyFrom(element); } else { - // Fill the array with zeros - long size = layout.byteSize(); - memorySegment.asSlice(i * size, size).fill((byte) 0); + // Fill the array slice with zeros + segment.asSlice(i * size, size).fill((byte) 0); } } if (zeroTerminated) - memorySegment.setAtIndex( - ValueLayout.ADDRESS, array.length, MemorySegment.NULL); + segment.set(ValueLayout.ADDRESS, length * size, NULL); - return memorySegment; + return segment; } /** @@ -1039,13 +1008,12 @@ public static MemorySegment allocateNativeArray(MemorySegment[] array, var memorySegment = arena.allocate(ValueLayout.ADDRESS, length); for (int i = 0; i < array.length; i++) { - MemorySegment s = array[i] == null ? MemorySegment.NULL : array[i]; + MemorySegment s = array[i] == null ? NULL : array[i]; memorySegment.setAtIndex(ValueLayout.ADDRESS, i, s); } if (zeroTerminated) - memorySegment.setAtIndex( - ValueLayout.ADDRESS, array.length, MemorySegment.NULL); + memorySegment.setAtIndex(ValueLayout.ADDRESS, array.length, NULL); return memorySegment; } @@ -1059,7 +1027,9 @@ public static MemorySegment allocateNativeArray(MemorySegment[] array, * @return an EnumSet containing the enum values as set in the bitfield */ public static & Enumeration> - EnumSet intToEnumSet(Class cls, Function make, int bitfield) { + EnumSet intToEnumSet(Class cls, + Function make, + int bitfield) { int n = bitfield; EnumSet enumSet = EnumSet.noneOf(cls); int position = 0; @@ -1082,8 +1052,8 @@ EnumSet intToEnumSet(Class cls, Function make, int bitfield) { public static & Enumeration> int enumSetToInt(Set set) { int bitfield = 0; - for (T value : set) - bitfield |= value.getValue(); + for (T element : set) + bitfield |= element.getValue(); return bitfield; } From 7c06217c80619bab358d7b41765feca907ee7b91 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 14:47:49 +0200 Subject: [PATCH 10/15] Change default base class for interface impl classes to GObject (fixes #101) --- .../jwharm/javagi/generators/RegisteredTypeGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java index af41f303..77e9e7c4 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/generators/RegisteredTypeGenerator.java @@ -179,7 +179,7 @@ private TypeName getInterfaceSuperclass(Interface i) { if (target instanceof Class) return target.typeName(); } - return ClassNames.TYPE_INSTANCE; + return ClassNames.GOBJECT; } public boolean hasDowncallHandles() { From f4d6113cb104c520f305e1a8202b32491325669c Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 21:03:03 +0200 Subject: [PATCH 11/15] Fix array marshaling bugs --- .../github/jwharm/javagi/interop/Interop.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java index 649d076c..7afb2924 100644 --- a/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java +++ b/modules/glib/src/main/java/io/github/jwharm/javagi/interop/Interop.java @@ -484,7 +484,8 @@ public static char[] getCharacterArrayFrom(MemorySegment address, Arena arena, boolean free) { - char[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_CHAR.byteSize(); + char[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_CHAR); if (free) @@ -507,7 +508,8 @@ public static double[] getDoubleArrayFrom(MemorySegment address, Arena arena, boolean free) { - double[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_DOUBLE.byteSize(); + double[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_DOUBLE); if (free) @@ -530,7 +532,8 @@ public static float[] getFloatArrayFrom(MemorySegment address, Arena arena, boolean free) { - float[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_FLOAT.byteSize(); + float[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_FLOAT); if (free) @@ -553,7 +556,8 @@ public static int[] getIntegerArrayFrom(MemorySegment address, Arena arena, boolean free) { - int[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_INT.byteSize(); + int[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_INT); if (free) @@ -577,7 +581,7 @@ public static int[] getIntegerArrayFrom(MemorySegment address, // Find the null byte MemorySegment array = address.reinterpret(INT_UNBOUNDED, arena, null); long idx = 0; - while (array.get(ValueLayout.JAVA_INT, idx) != 0) { + while (array.getAtIndex(ValueLayout.JAVA_INT, idx) != 0) { idx++; } @@ -598,7 +602,8 @@ public static long[] getLongArrayFrom(MemorySegment address, Arena arena, boolean free) { - long[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_LONG.byteSize(); + long[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_LONG); if (free) @@ -621,7 +626,8 @@ public static short[] getShortArrayFrom(MemorySegment address, Arena arena, boolean free) { - short[] array = address.reinterpret(length, arena, null) + long size = ValueLayout.JAVA_SHORT.byteSize(); + short[] array = address.reinterpret(length * size, arena, null) .toArray(ValueLayout.JAVA_SHORT); if (free) From f81f173b373956376fbe6a8f9702f5fa146a5993 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 21:04:32 +0200 Subject: [PATCH 12/15] Add string and array marshaling tests --- .../jwharm/javagi/test/glib/MarshalTest.java | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 modules/glib/src/test/java/io/github/jwharm/javagi/test/glib/MarshalTest.java diff --git a/modules/glib/src/test/java/io/github/jwharm/javagi/test/glib/MarshalTest.java b/modules/glib/src/test/java/io/github/jwharm/javagi/test/glib/MarshalTest.java new file mode 100644 index 00000000..f15f3083 --- /dev/null +++ b/modules/glib/src/test/java/io/github/jwharm/javagi/test/glib/MarshalTest.java @@ -0,0 +1,203 @@ +package io.github.jwharm.javagi.test.glib; + +import io.github.jwharm.javagi.base.Proxy; +import io.github.jwharm.javagi.interop.Interop; +import org.gnome.glib.GString; +import org.gnome.glib.OptionFlags; +import org.gnome.glib.Variant; +import org.junit.jupiter.api.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test conversion of strings and arrays to native memory and back + */ +public class MarshalTest { + + @Test + void testString() { + try (Arena arena = Arena.ofConfined()) { + String input = "123 abc"; + MemorySegment allocation = Interop.allocateNativeString(input, arena); + String output = Interop.getStringFrom(allocation); + assertEquals(input, output); + } + } + + @Test + void testStringArray() { + try (Arena arena = Arena.ofConfined()) { + String[] input = {"123 abc", "456 def", "789 ghi"}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + String[] output = Interop.getStringArrayFrom(allocation, 3, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + + allocation = Interop.allocateNativeArray(input, true, arena); + output = Interop.getStringArrayFrom(allocation, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testPointerArray() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment[] input = { + arena.allocate(10), + arena.allocate(10), + arena.allocate(10) + }; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + MemorySegment[] output = Interop.getAddressArrayFrom(allocation, 3, false); + for (int i = 0; i < input.length; i++) + assertEquals(input[i].address(), output[i].address()); + + allocation = Interop.allocateNativeArray(input, true, arena); + output = Interop.getAddressArrayFrom(allocation, false); + for (int i = 0; i < input.length; i++) + assertEquals(input[i].address(), output[i].address()); + } + } + + @Test + void testBooleanArray() { + try (Arena arena = Arena.ofConfined()) { + boolean[] input = {true, false, true, true, false}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + boolean[] output = Interop.getBooleanArrayFrom(allocation, 5, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testByteArray() { + try (Arena arena = Arena.ofConfined()) { + byte[] input = "1234567890".getBytes(); + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + byte[] output = Interop.getByteArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + + allocation = Interop.allocateNativeArray(input, true, arena); + output = Interop.getByteArrayFrom(allocation, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testCharArray() { + try (Arena arena = Arena.ofConfined()) { + char[] input = "1234567890".toCharArray(); + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + char[] output = Interop.getCharacterArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testDoubleArray() { + try (Arena arena = Arena.ofConfined()) { + double[] input = {1d, 2d, 3d, Math.PI, Double.MIN_VALUE, Double.MAX_VALUE}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + double[] output = Interop.getDoubleArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testFloatArray() { + try (Arena arena = Arena.ofConfined()) { + float[] input = {1.2f, 2.3f, 3.35f, Float.MIN_VALUE, Float.MAX_VALUE}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + float[] output = Interop.getFloatArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testIntArray() { + try (Arena arena = Arena.ofConfined()) { + int[] input = {1, 2, 3, 0, Integer.MIN_VALUE, Integer.MAX_VALUE}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + int[] output = Interop.getIntegerArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + + output = Interop.getIntegerArrayFrom(allocation, arena, false); + assertEquals(3, output.length); + } + } + + @Test + void testLongArray() { + try (Arena arena = Arena.ofConfined()) { + long[] input = {1L, 2L, 3L, Long.MIN_VALUE, Long.MAX_VALUE}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + long[] output = Interop.getLongArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testShortArray() { + try (Arena arena = Arena.ofConfined()) { + short[] input = {(short) 1, (short) 2, (short) 3, + Short.MIN_VALUE, Short.MAX_VALUE}; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + short[] output = Interop.getShortArrayFrom(allocation, input.length, arena, false); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } + + @Test + void testProxyArray() { + try (Arena arena = Arena.ofConfined()) { + Proxy[] input = { + Variant.int32(1), + Variant.int32(2), + Variant.int32(3) + }; + MemorySegment allocation = Interop.allocateNativeArray(input, false, arena); + Proxy[] output = Interop.getProxyArrayFrom(allocation, 3, Variant.class, Variant::new); + for (int i = 0; i < input.length; i++) + assertEquals(input[i], output[i]); + + allocation = Interop.allocateNativeArray(input, true, arena); + output = Interop.getProxyArrayFrom(allocation, Variant.class, Variant::new); + for (int i = 0; i < input.length; i++) + assertEquals(input[i], output[i]); + } + } + + @Test + void testStructArray() { + try (Arena arena = Arena.ofConfined()) { + GString[] input = { + new GString("abc"), + new GString((String) null), + new GString("12345 67890") + }; + MemorySegment allocation = Interop.allocateNativeArray(input, GString.getMemoryLayout(), false, arena); + GString[] output = Interop.getStructArrayFrom(allocation, 3, GString.class, GString::new, GString.getMemoryLayout()); + for (int i = 0; i < input.length; i++) + assertTrue(input[i].equal(output[i])); + } + } + + @Test + void testFlagsArray() { + try (Arena arena = Arena.ofConfined()) { + OptionFlags[] input = { + OptionFlags.IN_MAIN, + OptionFlags.FILENAME, + OptionFlags.NOALIAS + }; + int[] values = Interop.getValues(input); + MemorySegment allocation = Interop.allocateNativeArray(values, false, arena); + OptionFlags[] output = Interop.getArrayFromIntPointer(allocation, 3, OptionFlags.class, OptionFlags::of); + assertEquals(Arrays.toString(input), Arrays.toString(output)); + } + } +} From a6b3277cc83a973b12609696299e00a69270b885 Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 21:12:48 +0200 Subject: [PATCH 13/15] Update gir-files to latest --- ext/gir-files | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/gir-files b/ext/gir-files index c2dab7de..5793ea5c 160000 --- a/ext/gir-files +++ b/ext/gir-files @@ -1 +1 @@ -Subproject commit c2dab7dee23d5b5aa29bfb43c9191412cef65f59 +Subproject commit 5793ea5cda5b49bcc55b4b498e8f22d6d6f64caf From 2f73f3faf221b4ae90d5717f5da0b2e2f12f9dfb Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 21:32:47 +0200 Subject: [PATCH 14/15] Add conversion of time_t to long --- .../src/main/java/io/github/jwharm/javagi/util/Conversions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/io/github/jwharm/javagi/util/Conversions.java b/buildSrc/src/main/java/io/github/jwharm/javagi/util/Conversions.java index 08e2fcfd..2d8746f6 100644 --- a/buildSrc/src/main/java/io/github/jwharm/javagi/util/Conversions.java +++ b/buildSrc/src/main/java/io/github/jwharm/javagi/util/Conversions.java @@ -183,7 +183,7 @@ public static String toJavaBaseType(String name) { case "gshort", "gushort", "gint16", "guint16" -> "short"; case "gint", "guint", "gint32", "guint32", "gunichar" -> "int"; case "gint64", "gssize", "gsize", "goffset", "guint64", "gintptr", - "guintptr", "glong", "gulong" -> "long"; + "guintptr", "glong", "gulong", "time_t" -> "long"; case "gdouble", "long double" -> "double"; case "gfloat" -> "float"; case "none" -> "void"; From 9235bb60bf7747e98b46a48bda99c6bcd75d30dc Mon Sep 17 00:00:00 2001 From: Jan-Willem Harmannij Date: Sat, 1 Jun 2024 21:35:16 +0200 Subject: [PATCH 15/15] Change version to 0.10.1 --- buildSrc/src/main/groovy/java-gi.library-conventions.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/groovy/java-gi.library-conventions.gradle b/buildSrc/src/main/groovy/java-gi.library-conventions.gradle index 0f16df8e..72270fba 100644 --- a/buildSrc/src/main/groovy/java-gi.library-conventions.gradle +++ b/buildSrc/src/main/groovy/java-gi.library-conventions.gradle @@ -33,7 +33,7 @@ dependencies { } group = 'io.github.jwharm.javagi' -version = '0.10.0' +version = '0.10.1' java { if (! System.getenv('CI')) {