
io.github.jwharm.javagi.interop.Interop Maven / Gradle / Ivy
/* Java-GI - Java language bindings for GObject-Introspection-based libraries
* Copyright (C) 2022-2024 Jan-Willem Harmannij
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see .
*/
package io.github.jwharm.javagi.interop;
import java.lang.foreign.*;
import java.lang.invoke.*;
import java.lang.ref.Cleaner;
import java.lang.reflect.Array;
import java.util.*;
import java.util.Arrays;
import java.util.function.Function;
import io.github.jwharm.javagi.base.Enumeration;
import org.gnome.glib.GLib;
import io.github.jwharm.javagi.base.*;
/**
* The Interop class contains functionality for interoperability with native
* code.
*/
public class Interop {
private record NamedFunction(String name,
FunctionDescriptor fdesc,
boolean variadic) {
}
private record FunctionPointer(MemorySegment address,
FunctionDescriptor fdesc,
boolean variadic) {
}
private static final Map namedFunctions = new HashMap<>();
private static final Map functionPointers = new HashMap<>();
private final static SymbolLookup symbolLookup;
private final static Linker linker = Linker.nativeLinker();
static {
SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
symbolLookup = name -> loaderLookup.find(name).or(() ->
linker.defaultLookup().find(name));
}
/**
* Convenience method that calls
* {@link #downcallHandle(String, FunctionDescriptor, boolean)} with
* variadic=false.
*
* @param name name of the native function
* @param fdesc function descriptor of the native function
* @return the MethodHandle
*/
public static MethodHandle downcallHandle(String name,
FunctionDescriptor fdesc) {
return downcallHandle(name, fdesc, false);
}
/**
* Create a method handle that is used to call the native function with
* the provided name and function descriptor. The method handle is cached
* and reused in subsequent look-ups.
*
* @param name name of the native function
* @param fdesc function descriptor of the native function
* @param variadic whether the function has varargs
* @return the newly created MethodHandle
*/
public static MethodHandle downcallHandle(String name,
FunctionDescriptor fdesc,
boolean variadic) {
var func = new NamedFunction(name, fdesc, variadic);
if (namedFunctions.containsKey(func))
return namedFunctions.get(func);
var handle = symbolLookup.find(name).map(addr -> variadic
? VarargsInvoker.make(addr, fdesc)
: linker.downcallHandle(addr, fdesc)).orElse(null);
namedFunctions.put(func, handle);
return handle;
}
/**
* Create a method handle that is used to call the native function at the
* provided memory address. The method handle is cached and reused in
* subsequent look-ups.
*
* @param symbol memory address of the native function
* @param fdesc function descriptor of the native function
* @return the newly created MethodHandle
*/
public static MethodHandle downcallHandle(MemorySegment symbol,
FunctionDescriptor fdesc) {
var func = new FunctionPointer(symbol, fdesc, false);
if (functionPointers.containsKey(func))
return functionPointers.get(func);
var handle = linker.downcallHandle(symbol, fdesc);
functionPointers.put(func, handle);
return handle;
}
/**
* Create a method handle for the {@code upcall} method in the provided
* class.
*
* @param cls the callback class
* @param descriptor the function descriptor for the native function
* @return a method handle to use when creating an upcall stub
*/
public static MethodHandle upcallHandle(MethodHandles.Lookup lookup,
Class> cls,
FunctionDescriptor descriptor) {
return upcallHandle(lookup, cls, "upcall", descriptor);
}
/**
* Create a method handle for a method in the provided class.
*
* @param cls the callback class
* @param name the name of the callback method
* @param descriptor the function descriptor for the native function
* @return a method handle to use when creating an upcall stub
*/
public static MethodHandle upcallHandle(MethodHandles.Lookup lookup,
Class> cls,
String name,
FunctionDescriptor descriptor) {
try {
return lookup.findVirtual(cls, name, descriptor.toMethodType());
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Register a Cleaner that will close the arena when the instance is
* garbage-collected, coupling the lifetime of the arena to the lifetime of
* the instance.
*
* @param arena a memory arena that can be closed (normally
* {@link Arena#ofConfined()} or {@link Arena#ofAuto()}.
* @param instance an object
* @return the arena (for method chaining)
*/
public static Arena attachArena(Arena arena, Object instance) {
Cleaner cleaner = Cleaner.create();
cleaner.register(instance, arena::close);
return arena;
}
/**
* Reinterpret {@code address} to {@code newSize} iff {@code newSize} is
* larger than the current size of {@code address}.
*
* @param address a MemorySegment
* @param newSize new size for the MemorySegment
* @return the same MemorySegment reinterpreted to at least {@code newSize}
*/
public static MemorySegment reinterpret(MemorySegment address,
long newSize) {
if (address == null || address.byteSize() >= newSize)
return address;
return address.reinterpret(newSize);
}
/**
* Get a GType by executing the provided get-type function.
*
* @return the gtype from the provided get-type function
*/
public static org.gnome.glib.Type getType(String getTypeFunction) {
if (getTypeFunction == null)
return null;
FunctionDescriptor fdesc = FunctionDescriptor.of(ValueLayout.JAVA_LONG);
try {
MethodHandle handle = downcallHandle(getTypeFunction, fdesc, false);
if (handle == null)
return null;
return new org.gnome.glib.Type((long) handle.invokeExact());
} catch (Throwable err) {
throw new AssertionError("Unexpected exception occurred: ", err);
}
}
/**
* Allocate a native string using
* {@link SegmentAllocator#allocateUtf8String(String)}, but return
* {@link MemorySegment#NULL} for a {@code null} argument.
*
* @param string the string to allocate as a native string (utf8 char*)
* (can be {@code null})
* @param allocator the segment allocator to use
* @return the allocated MemorySegment with the native utf8 string, or
* {@link MemorySegment#NULL}
*/
public static MemorySegment allocateNativeString(String string,
SegmentAllocator allocator) {
return string == null
? MemorySegment.NULL
: allocator.allocateUtf8String(string);
}
/**
* Copy a Java string from native memory using
* {@code MemorySegment.getUtf8String()}. If an error occurs or when the
* native address is NULL, null is returned.
*
* @param address the memory address of the native String
* (a {@code NULL}-terminated {@code char*})
* @param free if the address must be freed
* @return a String or null
*/
public static String getStringFrom(MemorySegment address, boolean free) {
if (MemorySegment.NULL.equals(address))
return null;
try {
return address.reinterpret(Long.MAX_VALUE).getUtf8String(0);
} finally {
if (free)
GLib.free(address);
}
}
/**
* Read an array of Strings with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param free if the strings and the array must be freed
* @return array of Strings
*/
public static String[] getStringArrayFrom(MemorySegment address,
int length,
boolean free) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(ValueLayout.ADDRESS.byteSize() * length);
String[] result = new String[length];
for (int i = 0; i < length; i++) {
result[i] = array.getUtf8String(i * ValueLayout.ADDRESS.byteSize());
if (free)
GLib.free(array.getAtIndex(ValueLayout.ADDRESS, i));
}
if (free)
GLib.free(array);
return result;
}
/**
* Read an array 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 array of Strings
*/
public static String[] getStringArrayFrom(MemorySegment address,
boolean free) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0) {
array = address.reinterpret(Long.MAX_VALUE);
}
ArrayList result = new ArrayList<>();
long offset = 0;
while (true) {
MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset);
if (MemorySegment.NULL.equals(ptr))
break;
result.add(ptr.getUtf8String(0));
offset += ValueLayout.ADDRESS.byteSize();
}
if (free)
GLib.free(address);
return result.toArray(new String[0]);
}
/**
* Read an array of pointers with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param free if the addresses and 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))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(AddressLayout.ADDRESS.byteSize() * length);
MemorySegment[] result = new MemorySegment[length];
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);
return result;
}
/**
* Read an array of pointers from a {@code NULL}-terminated array in native
* memory.
*
* @param address address of the memory segment
* @param free if the addresses and the array must be freed
* @return array of pointers
*/
public static MemorySegment[] getAddressArrayFrom(MemorySegment address,
boolean free) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(Long.MAX_VALUE);
ArrayList result = new ArrayList<>();
long offset = 0;
while (true) {
MemorySegment ptr = array.get(ValueLayout.ADDRESS, offset);
if (MemorySegment.NULL.equals(ptr))
break;
result.add(ptr);
offset += ValueLayout.ADDRESS.byteSize();
}
if (free)
GLib.free(address);
return result.toArray(new MemorySegment[0]);
}
/**
* Read an array of booleans with the requested length from native memory.
* The array is read from native memory as an array of integers with value
* 1 or 0, and converted to booleans with 1 = true and 0 = false.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of booleans
*/
public static boolean[] getBooleanArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
int[] intArray = getIntegerArrayFrom(address, length, arena, free);
boolean[] array = new boolean[intArray.length];
for (int c = 0; c < intArray.length; c++)
array[c] = (intArray[c] != 0);
return array;
}
/**
* Read an array of bytes with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of bytes
*/
public static byte[] getByteArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
byte[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_BYTE);
if (free)
GLib.free(address);
return array;
}
/**
* Read a {@code NULL}-terminated array of bytes from native memory.
*
* @param address address of the memory segment
* @param arena the memory scope
* @param free if the array must be freed
* @return array of bytes
*/
public static byte[] getByteArrayFrom(MemorySegment address,
Arena arena,
boolean free) {
// Find the null byte
MemorySegment array = address.reinterpret(Long.MAX_VALUE, arena, null);
long idx = 0;
while (array.get(ValueLayout.JAVA_BYTE, idx) != 0) {
idx++;
}
return getByteArrayFrom(address, idx, arena, free);
}
/**
* Read an array of chars with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of chars
*/
public static char[] getCharacterArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
char[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_CHAR);
if (free)
GLib.free(address);
return array;
}
/**
* Read an array of doubles with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of doubles
*/
public static double[] getDoubleArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
double[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_DOUBLE);
if (free)
GLib.free(address);
return array;
}
/**
* Read an array of floats with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of floats
*/
public static float[] getFloatArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
float[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_FLOAT);
if (free)
GLib.free(address);
return array;
}
/**
* Read an array of integers with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of integers
*/
public static int[] getIntegerArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
int[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_INT);
if (free)
GLib.free(address);
return array;
}
/**
* Read a {@code NULL}-terminated array of integers from native memory.
*
* @param address address of the memory segment
* @param arena the memory scope
* @param free if the array must be freed
* @return array of integers
*/
public static int[] getIntegerArrayFrom(MemorySegment address,
Arena arena,
boolean free) {
// Find the null byte
MemorySegment array = address.reinterpret(Integer.MAX_VALUE, arena, null);
long idx = 0;
while (array.get(ValueLayout.JAVA_INT, idx) != 0) {
idx++;
}
return getIntegerArrayFrom(address, idx, arena, free);
}
/**
* Read an array of longs with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of longs
*/
public static long[] getLongArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
long[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_LONG);
if (free)
GLib.free(address);
return array;
}
/**
* Read an array of shorts with the requested length from native memory.
*
* @param address address of the memory segment
* @param length length of the array
* @param arena the memory scope
* @param free if the array must be freed
* @return array of shorts
*/
public static short[] getShortArrayFrom(MemorySegment address,
long length,
Arena arena,
boolean free) {
short[] array = address.reinterpret(length, arena, null)
.toArray(ValueLayout.JAVA_SHORT);
if (free)
GLib.free(address);
return array;
}
/**
* Read a {@code NULL}-terminated array of memory addresses from native
* memory, create a Proxy instance for each address, and return an array of
* Proxy instances.
*
* @param address address of the memory segment
* @param cls class of the Proxy type
* @param make constructor of the Proxy type
* @param the type of the Proxy instances
* @return array of Proxy instances
*/
public static T[] getProxyArrayFrom(MemorySegment address,
Class cls,
Function make) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(Long.MAX_VALUE);
long offset = 0;
while (!MemorySegment.NULL.equals(array.get(ValueLayout.ADDRESS, offset))) {
offset += ValueLayout.ADDRESS.byteSize();
}
return getProxyArrayFrom(address, (int) offset, cls, make);
}
/**
* Read an array of memory addresses from native memory, create a Proxy
* instance for each address, and return an array of Proxy instances.
*
* @param address address of the memory segment
* @param length length of the array
* @param cls class of the Proxy type
* @param make constructor of the Proxy type
* @param the type of the Proxy instances
* @return array of Proxy instances
*/
public static T[] getProxyArrayFrom(MemorySegment address,
int length,
Class cls,
Function make) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(AddressLayout.ADDRESS.byteSize() * length);
@SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length);
for (int i = 0; i < length; i++) {
result[i] = make.apply(array.getAtIndex(ValueLayout.ADDRESS, i));
}
return result;
}
/**
* Read an array of structs from native memory, create a Proxy instance for
* each struct, and return an array of Proxy instances.
*
* @param address address of the memory segment
* @param length length of the array
* @param cls class of the Proxy type
* @param make constructor of the Proxy type
* @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))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(layout.byteSize() * length);
@SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length);
List elements = array.elements(layout).toList();
for (int i = 0; i < length; i++) {
result[i] = make.apply(elements.get(i));
}
return result;
}
/**
* Read an array of integers from native memory, create a Java instance for
* each integer value with the provided constructor, and return an array of
* these instances. This is used to create an array of Enumeration or
* Bitfield objects from a native integer array.
*
* @param address address of the memory segment
* @param length length of the array
* @param cls class that will be returned in the array
* @param make constructor to create the instances
* @param the type to construct
* @return array of constructed instances
*/
public static T[] getArrayFromIntPointer(MemorySegment address,
int length,
Class cls,
Function make) {
if (address == null || MemorySegment.NULL.equals(address))
return null;
MemorySegment array = address;
if (array.byteSize() == 0)
array = address.reinterpret(AddressLayout.ADDRESS.byteSize() * length);
@SuppressWarnings("unchecked") T[] result = (T[]) Array.newInstance(cls, length);
for (int i = 0; i < length; i++) {
result[i] = make.apply(array.getAtIndex(ValueLayout.JAVA_INT, i));
}
return result;
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* strings ({@code NULL}-terminated utf8 {@code char*}).
*
* @param strings array of Strings
* @param zeroTerminated whether to add a {@code NULL} to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(String[] strings,
boolean zeroTerminated,
Arena arena) {
int length = zeroTerminated ? strings.length + 1 : strings.length;
var memorySegment = arena.allocateArray(ValueLayout.ADDRESS, length);
for (int i = 0; i < strings.length; i++) {
var cString = strings[i] == null ? MemorySegment.NULL
: arena.allocateUtf8String(strings[i]);
memorySegment.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
if (zeroTerminated)
memorySegment.setAtIndex(ValueLayout.ADDRESS, strings.length, MemorySegment.NULL);
return memorySegment;
}
/**
* Convert a boolean[] array into an int[] array, and calls
* {@link #allocateNativeArray(int[], boolean, Arena)}.
* Each boolean value "true" is converted 1, boolean value "false" to 0.
*
* @param array array of booleans
* @param zeroTerminated when true, an (int) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return The memory segment of the native array
*/
public static MemorySegment allocateNativeArray(boolean[] array,
boolean zeroTerminated,
Arena arena) {
int[] intArray = new int[array.length];
for (int i = 0; i < array.length; i++) {
intArray[i] = array[i] ? 1 : 0;
}
return allocateNativeArray(intArray, zeroTerminated, arena);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* bytes.
*
* @param array array of bytes
* @param zeroTerminated when true, a (byte) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(byte[] array,
boolean zeroTerminated,
Arena arena) {
byte[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_BYTE, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* chars.
*
* @param array array of chars
* @param zeroTerminated when true, a (char) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return whe memory segment of the native array
*/
public static MemorySegment allocateNativeArray(char[] array,
boolean zeroTerminated,
Arena arena) {
char[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_CHAR, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* doubles.
*
* @param array array of doubles
* @param zeroTerminated when true, a (double) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(double[] array,
boolean zeroTerminated,
Arena arena) {
double[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_DOUBLE, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* floats.
*
* @param array array of floats
* @param zeroTerminated when true, a (float) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(float[] array,
boolean zeroTerminated,
Arena arena) {
float[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_FLOAT, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* floats.
*
* @param array array of floats
* @param zeroTerminated when true, a (int) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(int[] array,
boolean zeroTerminated,
Arena arena) {
int[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_INT, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* longs.
*
* @param array array of longs
* @param zeroTerminated when true, a (long) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(long[] array,
boolean zeroTerminated,
Arena arena) {
long[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_LONG, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* shorts.
*
* @param array array of shorts
* @param zeroTerminated when true, a (short) 0 is appended to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(short[] array,
boolean zeroTerminated,
Arena arena) {
short[] copy = zeroTerminated ?
Arrays.copyOf(array, array.length + 1)
: array;
return arena.allocateArray(ValueLayout.JAVA_SHORT, copy);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* pointers (from Proxy instances).
*
* @param array array of Proxy instances
* @param zeroTerminated whether to add a {@code NULL} to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(Proxy[] array,
boolean zeroTerminated,
Arena arena) {
MemorySegment[] addressArray = new MemorySegment[array.length];
for (int i = 0; i < array.length; i++) {
addressArray[i] = array[i] == null
? MemorySegment.NULL
: array[i].handle();
}
return allocateNativeArray(addressArray, zeroTerminated, arena);
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* structs (from Proxy instances). The actual memory segments (not the
* pointers) are copied into the array.
*
* @param array array of Proxy instances
* @param layout the memory layout of the object type
* @param zeroTerminated whether to add a {@code NULL} to the array
* @param arena the allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(Proxy[] array,
MemoryLayout layout,
boolean zeroTerminated,
Arena arena) {
int length = zeroTerminated ? array.length + 1 : array.length;
MemorySegment memorySegment = arena.allocateArray(layout, length);
for (int i = 0; i < array.length; i++) {
if (array[i] != null
&& (!MemorySegment.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);
} else {
// Fill the array with zeros
memorySegment.asSlice(i * layout.byteSize(), layout.byteSize())
.fill((byte) 0);
}
}
if (zeroTerminated)
memorySegment.setAtIndex(ValueLayout.ADDRESS, array.length, MemorySegment.NULL);
return memorySegment;
}
/**
* Allocate and initialize an (optionally {@code NULL}-terminated) array of
* memory addresses.
*
* @param array array of MemorySegments
* @param zeroTerminated whether to add a {@code NULL} to the array
* @param arena the segment allocator for memory allocation
* @return the memory segment of the native array
*/
public static MemorySegment allocateNativeArray(MemorySegment[] array,
boolean zeroTerminated,
Arena arena) {
int length = zeroTerminated ? array.length + 1 : array.length;
var memorySegment = arena.allocateArray(ValueLayout.ADDRESS, length);
for (int i = 0; i < array.length; i++) {
MemorySegment s = array[i] == null ? MemorySegment.NULL : array[i];
memorySegment.setAtIndex(ValueLayout.ADDRESS, i, s);
}
if (zeroTerminated)
memorySegment.setAtIndex(ValueLayout.ADDRESS, array.length, MemorySegment.NULL);
return memorySegment;
}
// Adapted from code that was generated by JExtract
private record VarargsInvoker(MemorySegment symbol, FunctionDescriptor function) {
private static final MethodHandle INVOKE_MH;
private static final SegmentAllocator THROWING_ALLOCATOR = (_, _) -> {
throw new AssertionError("should not reach here");
};
static {
try {
INVOKE_MH = MethodHandles.lookup().findVirtual(
VarargsInvoker.class,
"invoke",
MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class)
);
} catch (ReflectiveOperationException e) {
throw new InteropException(e);
}
}
/**
* Create a MethodHandle with the correct signature
*/
static MethodHandle make(MemorySegment symbol, FunctionDescriptor function) {
VarargsInvoker invoker = new VarargsInvoker(symbol, function);
MethodHandle handle = INVOKE_MH.bindTo(invoker)
.asCollector(Object[].class, function.argumentLayouts().size() + 1);
MethodType mtype = MethodType.methodType(
function.returnLayout().isPresent()
? carrier(function.returnLayout().get(), true)
: void.class);
for (MemoryLayout layout : function.argumentLayouts()) {
mtype = mtype.appendParameterTypes(carrier(layout, false));
}
mtype = mtype.appendParameterTypes(Object[].class);
boolean needsAllocator = function.returnLayout().isPresent()
&& function.returnLayout().get() instanceof GroupLayout;
if (needsAllocator)
mtype = mtype.insertParameterTypes(0, SegmentAllocator.class);
else
handle = MethodHandles.insertArguments(handle, 0, THROWING_ALLOCATOR);
return handle.asType(mtype);
}
/*
* Get the carrier associated with this layout. For GroupLayouts and
* the return layout, the carrier is always MemorySegment.class.
*/
private static Class> carrier(MemoryLayout layout, boolean ret) {
if (layout instanceof ValueLayout valLayout)
return (ret || valLayout.carrier() != MemorySegment.class)
? valLayout.carrier()
: MemorySegment.class;
else if (layout instanceof GroupLayout)
return MemorySegment.class;
throw new AssertionError("Cannot get here!");
}
/*
* This method is used from a MethodHandle (INVOKE_MH).
*/
@SuppressWarnings("unused")
private Object invoke(SegmentAllocator allocator, Object[] args)
throws Throwable {
// one trailing Object[]
int nNamedArgs = function.argumentLayouts().size();
// The last argument is the array of vararg collector
Object[] unnamedArgs = (Object[]) args[args.length - 1];
int argsCount = nNamedArgs + unnamedArgs.length;
MemoryLayout[] argLayouts = new MemoryLayout[argsCount];
int pos;
for (pos = 0; pos < nNamedArgs; pos++) {
argLayouts[pos] = function.argumentLayouts().get(pos);
}
// Unwrap the Java-GI types to their address or primitive value
Object[] unwrappedArgs = new Object[unnamedArgs.length];
for (int i = 0; i < unnamedArgs.length; i++) {
unwrappedArgs[i] = unwrapJavagiTypes(unnamedArgs[i]);
}
for (Object o : unwrappedArgs) {
argLayouts[pos] = variadicLayout(normalize(o.getClass()));
pos++;
}
FunctionDescriptor f = (function.returnLayout().isEmpty())
? FunctionDescriptor.ofVoid(argLayouts)
: FunctionDescriptor.of(function.returnLayout().get(), argLayouts);
MethodHandle mh = linker.downcallHandle(symbol, f);
boolean needsAllocator = function.returnLayout().isPresent()
&& function.returnLayout().get() instanceof GroupLayout;
if (needsAllocator)
mh = mh.bindTo(allocator);
/*
* Flatten argument list so that it can be passed to an asSpreader
* MethodHandle.
*/
Object[] allArgs = new Object[nNamedArgs + unwrappedArgs.length];
System.arraycopy(args, 0, allArgs, 0, nNamedArgs);
System.arraycopy(unwrappedArgs, 0, allArgs, nNamedArgs, unwrappedArgs.length);
return mh.asSpreader(Object[].class, argsCount).invoke(allArgs);
}
private static Class> unboxIfNeeded(Class> c) {
if (c == Boolean.class)
return boolean.class;
else if (c == Void.class)
return void.class;
else if (c == Byte.class)
return byte.class;
else if (c == Character.class)
return char.class;
else if (c == Short.class)
return short.class;
else if (c == Integer.class)
return int.class;
else if (c == Long.class)
return long.class;
else if (c == Float.class)
return float.class;
else if (c == Double.class)
return double.class;
return c;
}
private Class> promote(Class> c) {
if (c == byte.class
|| c == char.class
|| c == short.class
|| c == int.class)
return long.class;
else if (c == float.class)
return double.class;
return c;
}
private Class> normalize(Class> c) {
c = unboxIfNeeded(c);
if (c.isPrimitive())
return promote(c);
if (MemorySegment.class.isAssignableFrom(c))
return MemorySegment.class;
throw new IllegalArgumentException("Invalid type for ABI: " + c.getTypeName());
}
private MemoryLayout variadicLayout(Class> c) {
if (c == long.class)
return ValueLayout.JAVA_LONG;
else if (c == double.class)
return ValueLayout.JAVA_DOUBLE;
else if (MemorySegment.class.isAssignableFrom(c))
return ValueLayout.ADDRESS;
throw new IllegalArgumentException("Unhandled variadic argument class: " + c);
}
/*
* Unwrap the Java-GI types to their memory address or primitive value.
* Arrays are allocated to native memory as-is (no additional NULL is
* appended: the caller must do this).
*/
private Object unwrapJavagiTypes(Object o) {
return switch(o) {
case null -> MemorySegment.NULL;
case MemorySegment[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case boolean[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case byte[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case char[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case double[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case float[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case int[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case long[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case short[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case Proxy[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case String[] values ->
allocateNativeArray(values, false, Arena.ofAuto()).address();
case Boolean bool ->
bool ? 1 : 0;
case String string ->
allocateNativeString(string, Arena.ofAuto()).address();
case Alias> alias ->
alias.getValue();
case Bitfield bitfield ->
bitfield.getValue();
case Bitfield[] bitfields ->
Bitfield.getValues(bitfields);
case Enumeration enumeration ->
enumeration.getValue();
case Enumeration[] enumerations ->
Enumeration.getValues(enumerations);
case Proxy proxy ->
proxy.handle();
default -> o;
};
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy