Ранее мы рассмотрели, как работать с сегментами памяти, которые содержат строки UTF-8 и массивы значений примитивного типа. Но возникает вопрос, как быть с более сложными по структуре данными?
В языке C для описания таких данных применяются структуры. В Java для преобразования в нативный формат и обратно применяются кастомные структуры памяти, которые определяются с помощью метода
MemoryLayout.structLayout():
static StructLayout structLayout(MemoryLayout... elements)
В этот метод передается набор значений типа MemoryLayout.
К примеру, если на Си у нас есть следующая структура Point, которая представляет точку:
typedef struct {
int x;
int y;
} Point;
То в Java этой структуре будет соответствовать следующая компоновка памяти:
StructLayout POINT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")
);
Интерфейс MemoryLayout предоставляет ряд специальных методов для управления выделенной памятью, в частности, метод
withName(String name) возвращает структуру памяти (объект MemoryLayout) с теми же характеристиками, что и данная структура, но с заданным именем. Таким образом, с помощью выражения
ValueLayout.JAVA_INT.withName("x")
определяем сегмент памяти размером в 4 байта (размера типа int в Java), который сопоставлен с именем "x".
Выделение сегмента памяти для данной структуры:
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
public class Program {
public static void main(String[] args) {
StructLayout POINT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")
);
try (Arena arena = Arena.ofConfined()) {
// выделяем память для 1 объекта POINT
MemorySegment point_seg = arena.allocate(POINT);
System.out.println(point_seg); // MemorySegment{ kind: native, address: 0x72760c180400, byteSize: 8 }
}
}
}
При сопоставлении типов C и Java длины байтов должны совпадать. Но беззнаковые типы C сопоставляются с их эквивалентами в Java. При обработке этих значений в Java необходимо самостоятельно обрабатывать их как беззнаковые значения.
В примере выше мы не просто так установили для выделенных сегментов памяти имена ("x" и "y"). Используя методы сегментов памяти, мы можем затем получить или установить компоненты структуры:
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
public class Program {
public static void main(String[] args) {
// определяем структуру
StructLayout POINT = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")
);
try (Arena arena = Arena.ofConfined()) {
// выделяем память для 1 объекта POINT
MemorySegment point_seg = arena.allocate(POINT);
// устанавливаем значения x и y
point_seg.set(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("x")), 20);
point_seg.set(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")), 15);
// получаем значения x и y
int x = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("x")));
int y = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")));
System.out.println("x: " + x); // 20
System.out.println("y: " + y); // 15
}
}
}
Методы get/set обращаются к определенным участкам памяти, используя смещения. Для получения смещения применяется метод byteOffset(), в который передается путь к элементу в памяти.
Для получения пути используется имя. Например, выражение
POINT.byteOffset(PathElement.groupElement("x"))
позволяет получить смещение относительно начала структуры для сегмента, который сопоставляется с именем "x".
В принципе мы могли бы передать и числовое значение смещения, если оно нам известно:
// поле y смещено на 4 байта относительно начала структуры
point_seg.set(ValueLayout.JAVA_INT, 4, 17);
// получаем значения y
int y = point_seg.get(ValueLayout.JAVA_INT, POINT.byteOffset(PathElement.groupElement("y")));
System.out.println("y: " + y); // y: 17
Подобно тому, как StructLayout описывает расположение структур C, тип UnionLayout описывает объединение (union) языка C. Работа с UnionLayout
во многом аналогична работе с StructLayout.
Говоря о компоновке памяти, следует отметить интерфейс SequenceLayout, который предоставляет составную компоновку - однородное повторение заданной компоновки элементов или последовательность. Количество повторений называется количеством элементов в последовательности. Последовательность можно рассматривать как структурную компоновку, в которой количество повторений элементов равно количеству элементов в последовательности.
Для создания этого типа компоновки памяти применяется метод MemoryLayout.sequenceLayout()
static SequenceLayout sequenceLayout(long elementCount, MemoryLayout elementLayout)
Метод принимает количество элементов и компоновку элемента в виде значения MemoryLayout
Например, используем SequenceLayout для определения массива структур:
// массив структур
SequenceLayout points_layout = MemoryLayout.sequenceLayout(count,
MemoryLayout.structLayout(
JAVA_INT.withName("x"),
JAVA_INT.withName("y")
)
);
Для обращения к компоненту y структуры с некоторым индексом i, нам нужно вычислить смещение в байтах:
long offset = points_layout.byteOffset(
PathElement.sequenceElement(i),
PathElement.groupElement("y")
);
Здесь метод byteOffset получает последовательность объектов MemoryLayout.PathElement, которые позволяют получить смещение к нужному компоненту.
Получив соответствующий сегмент памяти, мы можем получить или установить само значение компонента:
MemorySegment points = Arena.global().allocate(point_layout); int value = 6; // значение для y points.set(JAVA_FLOAT, offset, value, y);
Пример программы полностью:
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
public class Program {
public static void main(String[] args) {
int count = 4; // количество структур
// массив структур
SequenceLayout points_layout = MemoryLayout.sequenceLayout(count,
MemoryLayout.structLayout(
JAVA_INT.withName("x"),
JAVA_INT.withName("y")
)
);
try (Arena arena = Arena.ofConfined()) {
// выделяем память для массива структур
MemorySegment points = arena.allocate(points_layout);
int index = 1; // индекс нужной структуры в массиве
// получаем смещение поля y для структуры с индексом index
long offset = points_layout.byteOffset(
PathElement.sequenceElement(index),
PathElement.groupElement("y")
);
// устанавливаем значение y
points.set(ValueLayout.JAVA_INT, offset, 24);
// получаем значение y для структуры с индексом index
int y = points.get(ValueLayout.JAVA_INT, offset);
System.out.println("y: " + y); // 24
}
}
}