Spec-Zone.ru › OpenJDK 24

Интерфейсный линкер

public sealed interface Linker
Линкер предоставляет доступ к внешним функциям из кода Java и к коду Java из внешних функций.

Внешние функции обычно находятся в библиотеках, которые могут загружаться по требованию. Каждая библиотека соответствует определенному ABI (Application Binary Interface). ABI — это набор соглашений о вызовах и типов данных, связанных с компилятором, ОС и процессором, на которых была построена библиотека. Например, C-компилятор на Linux/x64 обычно создает библиотеки, которые соответствуют ABI SystemV.

Линкер имеет подробные сведения о соглашениях о вызовах и типах данных, используемых конкретным ABI. Для любой библиотеки, которая соответствует этому ABI, линкер может взаимодействовать между кодом Java, выполняемым в JVM, и внешними функциями в библиотеке. В частности:

  • Линкер позволяет коду Java связываться с внешними функциями через обработчики вызовов внизОГРАНИЧЕННО; и
  • Линкер позволяет внешним функциям вызывать обработчики методов Java через создание заглушек для вызовов вверхОГРАНИЧЕННО.
Линкер предоставляет способ поиска канонических представлений, связанных с типами данных, используемыми ABI. Например, линкер, реализующий C ABI, может выбрать предоставление канонического представления для типа C size_t. На 64-битных платформах это каноническое представление может быть равно ValueLayout.JAVA_LONG. Канонические представления, поддерживаемые линкером, экспонируются через метод canonicalLayouts(), который возвращает отображение имен типов на канонические представления.

Кроме того, линкер предоставляет способ поиска внешних функций в библиотеках, соответствующих ABI. Каждый линкер выбирает набор библиотек, которые обычно используются на комбинации ОС и процессора, связанных с ABI. Например, линкер для Linux/x64 может выбрать две библиотеки: libc и libm. Функции в этих библиотеках экспонируются через поиск символов.

Вызов функций нативных функций

Нативный линкер может использоваться для связи с функциями, определенными в C-библиотеках (нативные функции). Предположим, мы хотим вызвать функцию strlen из стандартной C-библиотеки из Java:
size_t strlen(const char *s);
Обработчик вызова вниз, который экспонирует strlen, получается с помощью нативного линкера следующим образом:
Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("strlen"),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);
Обратите внимание, как нативный линкер также предоставляет доступ через свой поиск по умолчанию к нативным функциям, определенным C-библиотеками, загруженными с Java-средой выполнения. Выше используется поиск по умолчанию для поиска адреса нативной функции strlen. Этот адрес затем передается вместе с платформенно-зависимым описанием сигнатуры функции, выраженным как FunctionDescriptor (подробнее об этом ниже) в метод нативного линкера downcallHandle(MemorySegment, FunctionDescriptor, Option...)ОГРАНИЧЕННО. Полученный обработчик вызова вниз затем вызывается следующим образом:
 try (Arena arena = Arena.ofConfined()) {
     MemorySegment str = arena.allocateFrom("Hello");
     long len = (long) strlen.invokeExact(str);  // 5
 }

Описание сигнатур C

При взаимодействии с нативным линком клиенты должны предоставить платформенно-зависимое описание сигнатуры C-функции, с которой они хотят связаться. Это описание, function descriptor, определяет представления, связанные с типами параметров и типом возвращаемого значения (если таковой имеется) C-функции.

Скалярные типы C, такие как bool, int, моделируются как представления значений подходящего носителя. отображение между скалярным типом и соответствующим каноническим представлением зависит от ABI, реализованного нативным линком (см. ниже).

Составные типы моделируются как представления групп. Более конкретно, тип C struct сопоставляется с представлением структуры, а тип C union сопоставляется с union layout. При определении представления структуры или объединения клиенты должны учитывать ограничения размера и выравнивания соответствующего определения составного типа в C. Например, отступы между двумя полями структуры должны быть смоделированы явно, добавив член представления заполнения достаточного размера в результирующее представление структуры.

Наконец, типы указателей, такие как int** и int(*)(size_t*, size_t*), моделируются как представления адресов. Когда пространственные границы типа указателя известны статически, представление адреса может быть связано с целевым представлением. Например, указатель, который, как известно, указывает на массив C int[2], может быть смоделирован как представление адреса, целевым представлением которого является последовательное представление, количество элементов которого равно 2, а тип элемента — ValueLayout.JAVA_INT.

Все реализации нативного линкера гарантируют предоставление канонических представлений для следующего набора типов:

  • bool
  • char
  • short
  • int
  • long
  • long long
  • float
  • double
  • size_t
  • wchar_t
  • void*
Как отмечалось выше, конкретное каноническое представление, связанное с каждым типом, может меняться в зависимости от модели данных, поддерживаемой заданным ABI. Например, тип C long отображается на константу представления ValueLayout.JAVA_LONG на Linux/x64, но отображается на константу представления ValueLayout.JAVA_INT на Windows/x64. Аналогично, тип C size_t отображается на константу представления ValueLayout.JAVA_LONG на 64-битных платформах, но отображается на константу представления ValueLayout.JAVA_INT на 32-битных платформах.

Нативный линкер обычно не предоставляет канонических представлений для беззнаковых целочисленных типов C. Вместо этого они моделируются с использованием канонических представлений, связанных с соответствующими знаковыми целочисленными типами. Например, тип C unsigned long отображается на константу представления ValueLayout.JAVA_LONG на Linux/x64, но отображается на константу представления ValueLayout.JAVA_INT на Windows/x64.

Следующая таблица показывает некоторые примеры того, как типы C моделируются на Linux/x64 в соответствии с "System V Application Binary Interface" (все примеры, приведенные здесь, предполагают эти платформенно-зависимые отображения):

END_OF_DOCUMENT_MARKER
Сопоставление типов C
Тип C Макет Тип Java
bool ValueLayout.JAVA_BOOLEAN boolean
char
unsigned char
ValueLayout.JAVA_BYTE byte
short
unsigned short
ValueLayout.JAVA_SHORT short
int
unsigned int
ValueLayout.JAVA_INT int
long
unsigned long
ValueLayout.JAVA_LONG long
long long
unsigned long long
ValueLayout.JAVA_LONG long
float ValueLayout.JAVA_FLOAT float
double ValueLayout.JAVA_DOUBLE double
size_t ValueLayout.JAVA_LONG long
char*, int**, struct Point* ValueLayout.ADDRESS MemorySegment
int (*ptr)[10]
 ValueLayout.ADDRESS.withTargetLayout(
     MemoryLayout.sequenceLayout(10,
         ValueLayout.JAVA_INT)
 );
 
MemorySegment
struct Point { int x; long y; };
 MemoryLayout.structLayout(
     ValueLayout.JAVA_INT.withName("x"),
     MemoryLayout.paddingLayout(4),
     ValueLayout.JAVA_LONG.withName("y")
 );
 
MemorySegment
union Choice { float a; int b; }
 MemoryLayout.unionLayout(
     ValueLayout.JAVA_FLOAT.withName("a"),
     ValueLayout.JAVA_INT.withName("b")
 );
 
MemorySegment

Модуль связывания с нативным кодом поддерживает только описатели функций, у которых макеты аргументов/возвращаемых значений являются корректными макетами. Более формально, макет `L` корректен, если:

  • L является макетом значения, и L получен из канонического макета C таким образом, что L.byteAlignment() <= C.byteAlignment()
  • L является макетом последовательности S и все следующие условия выполняются:
    1. L.byteAlignment() равен естественному выравниванию макета последовательности, и
    2. S.elementLayout() является корректным макетом.
  • L является макетом группы G и все следующие условия выполняются:
    1. G.byteAlignment() равен естественному выравниванию макета группы
    2. G.byteSize() является кратным G.byteAlignment()
    3. Каждый макет члена в G.memberLayouts() является либо макетом заполнения, либо корректным макетом
    4. Каждый макет не-заполнения E в G.memberLayouts() следует за необязательным макетом заполнения, размер которого — минимально необходимый для выравнивания E
    5. G содержит необязательный макет заполнения в конце, размер которого — минимально необходимый для удовлетворения (2)

Описатель функции является корректным, если её макеты аргументов и возвращаемых значений корректны и не являются макетами последовательностей. Модуль связывания с нативным кодом гарантирует отклонение некорректных описателей функций. Однако модуль связывания с нативным кодом может отклонить и корректные описатели функций согласно правилам, зависящим от платформы. Например, некоторые модули связывания с нативным кодом могут отклонять упакованные макеты структур — макеты структур, макеты членов которых имеют ослабленные ограничения на выравнивание, чтобы избежать вставки дополнительного заполнения.

Функции-указатели

Иногда полезно передавать код Java как указатель на функцию некоторой нативной функции; это достигается с помощью заглушки вызова вверхОГРАНИЧЕННО. В качестве примера рассмотрим следующую функцию из стандартной библиотеки C:
void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));
Функция qsort может использоваться для сортировки содержимого массива с помощью пользовательской функции сравнения, переданной в качестве указателя на функцию (параметр compar). Для возможности вызова функции qsort из Java необходимо сначала создать дескриптор метода вызова вниз для неё следующим образом:
Linker linker = Linker.nativeLinker();
MethodHandle qsort = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("qsort"),
        FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
);
Как и прежде, мы используем ValueLayout.JAVA_LONG для сопоставления типа C size_t, и ValueLayout.ADDRESS как для первого параметра-указателя (указателя на массив), так и для последнего параметра (указателя на функцию).

Для вызова дескриптора вызова вниз qsort, полученного выше, нам нужен указатель на функцию, который передаётся в качестве последнего параметра. То есть нам нужно создать указатель на функцию из существующего дескриптора метода. Сначала напишем метод Java, который может сравнивать два целочисленных элемента, переданных как указатели (т. е. как сегменты памяти):

class Qsort {
    static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
        return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0));
    }
}
Теперь создадим дескриптор метода для метода сравнения, определённого выше:
FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT,
                                                      ADDRESS.withTargetLayout(JAVA_INT),
                                                      ADDRESS.withTargetLayout(JAVA_INT));
MethodHandle comparHandle = MethodHandles.lookup()
                                         .findStatic(Qsort.class, "qsortCompare",
                                                     comparDesc.toMethodType());
Сначала создадим дескриптор функции для типа указателя на функцию. Поскольку нам известно, что параметры, передаваемые в метод сравнения, будут указателями на элементы массива C int[], мы можем указать ValueLayout.JAVA_INT как целевой макет для макетов адресов обоих параметров. Это позволит методу сравнения получить доступ к содержимому элементов массива, которые необходимо сравнить. Затем мы преобразуем этот дескриптор функции в подходящий тип метода, который затем используем для поиска дескриптора метода сравнения. Теперь мы можем создать заглушку вызова вверх, которая указывает на этот метод, и передать её как указатель на функцию дескриптору вызова вниз qsort, как показано ниже:
try (Arena arena = Arena.ofConfined()) {
    MemorySegment comparFunc = linker.upcallStub(comparHandle, comparDesc, arena);
    MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
    qsort.invokeExact(array, 10L, 4L, comparFunc);
    int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
}
Этот код создаёт массив вне кучи, копирует содержимое массива Java в него и затем передаёт массив в дескриптор метода qsort вместе с функцией сравнения, полученной из модуля связывания с нативным кодом. После вызова содержимое массива вне кучи будет отсортировано в соответствии с нашей функцией сравнения, написанной на Java. Затем мы извлекаем новый массив Java из сегмента, который содержит отсортированные элементы.

Функции, возвращающие указатели

При взаимодействии с нативными функциями часто встречается ситуация, когда эти функции выделяют область памяти и возвращают указатель на эту область. Рассмотрим следующую функцию из стандартной библиотеки C:
void *malloc(size_t size);
Функция malloc выделяет область памяти заданного размера и возвращает указатель на эту область памяти, которая впоследствии освобождается с помощью другой функции из стандартной библиотеки C:
void free(void *ptr);
Функция free принимает указатель на область памяти и освобождает эту область. В этом разделе мы покажем, как взаимодействовать с этими нативными функциями, с целью предоставления безопасного API для выделения памяти (представленный ниже подход, конечно, может быть обобщён на функции выделения памяти, отличные от malloc и free).

Сначала нам нужно создать дескрипторы методов вызова вниз для malloc и free, как показано ниже:

Linker linker = Linker.nativeLinker();

MethodHandle malloc = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("malloc"),
    FunctionDescriptor.of(ADDRESS, JAVA_LONG)
);

MethodHandle free = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("free"),
    FunctionDescriptor.ofVoid(ADDRESS)
);
Когда нативная функция, возвращающая указатель (например, malloc), вызывается с помощью дескриптора метода вызова вниз, Java-среда исполнения не имеет информации о размере или времени жизни возвращаемого указателя. Рассмотрим следующий код:
MemorySegment segment = (MemorySegment)malloc.invokeExact(100);
Размер сегмента, возвращаемого дескриптором метода вызова вниз malloc, равен нулю. Более того, область действия возвращаемого сегмента — глобальная область действия. Для безопасного доступа к сегменту мы должны, небезопасно, изменить размер сегмента до желаемого размера (100 в данном случае). Также может быть желательно привязать сегмент к некоторой существующей арене, чтобы время жизни области памяти, лежащей в основе сегмента, можно было управлять автоматически, как и в случае любого другого нативного сегмента, созданного непосредственно из кода Java. Обе эти операции выполняются с помощью ограниченного метода MemorySegment.reinterpret(long, Arena, Consumer)ОГРАНИЧЕННО, как показано ниже:
MemorySegment allocateMemory(long byteSize, Arena arena) throws Throwable {
    MemorySegment segment = (MemorySegment) malloc.invokeExact(byteSize); // size = 0, scope = always alive
    return segment.reinterpret(byteSize, arena, s -> {
        try {
            free.invokeExact(s);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    });  // size = byteSize, scope = arena.scope()
}
Метод allocateMemory, определённый выше, принимает два параметра: размер и арену. Метод вызывает метод обработки вызова вниз malloc и небезопасно преобразует возвращённый сегмент, присвоив ему новый размер (размер, переданный методу allocateMemory) и новую область видимости (область видимости предоставленной арены). Метод также указывает действие очистки, которое должно быть выполнено при закрытии предоставленной арены. Неудивительно, что действие очистки передает сегмент методу обработки вызова вниз free для освобождения подлежащей области памяти. Мы можем использовать метод allocateMemory следующим образом:
try (Arena arena = Arena.ofConfined()) {
    MemorySegment segment = allocateMemory(100, arena);
} // 'free' called here
Обратите внимание, как сегмент, полученный из allocateMemory, действует как любой другой сегмент, управляемый ограниченной областью. Более конкретно, полученный сегмент имеет желаемый размер, к нему может получить доступ только один поток (поток, создавший ограниченную область), и его срок службы привязан к блоку try-with-resources.

Функции с переменным числом аргументов

Функции с переменным числом аргументов — это C-функции, которые могут принимать переменное число и тип аргументов. Они объявляются с заключительным многоточием (...) в конце списка формальных параметров, например: void foo(int x, ...); Аргументы, передаваемые вместо многоточия, называются аргументами с переменным числом. Функции с переменным числом аргументов, по сути, являются шаблонами, которые могут быть специализированы в несколько функций без переменного числа аргументов путём замены ... списком параметров с переменным числом фиксированного числа и типа.

Следует отметить, что значения, передаваемые как аргументы с переменным числом, подвергаются стандартному преобразованию аргументов в C. Например, применяются следующие преобразования аргументов:

  • _Bool -> unsigned int
  • [signed] char -> [signed] int
  • [signed] short -> [signed] int
  • float -> double
При этом знаковый атрибут исходного типа соответствует знакомому атрибуту преобразованного типа. Полный процесс стандартного преобразования аргументов описан в спецификации C. Фактически, эти преобразования накладывают ограничения на типы, которые могут быть использованы для замены ..., так как параметры с переменным числом специализированной формы функции с переменным числом аргументов всегда будут иметь преобразованный тип.

Собственный компоновщик поддерживает только компоновку специализированной формы функции с переменным числом аргументов. Функцию с переменным числом аргументов в её специализированной форме можно скомпоновать с помощью описателя функции, описывающего специализированную форму. Кроме того, должен быть указан параметр компоновщика Linker.Option.firstVariadicArg(int), чтобы указать первый параметр с переменным числом в списке параметров. Соответствующая компоновка аргументов (если таковая имеется), а также все последующие компоновки аргументов в описателе специализированной функции называются компоновками аргументов с переменным числом.

Собственный компоновщик не выполняет автоматически стандартное преобразование аргументов. Однако, поскольку передача аргумента не преобразованного типа в качестве аргумента с переменным числом не поддерживается в C, собственный компоновщик отклонит попытку компоновки описателя специализированной функции с любыми компоновками значений аргументов с переменным числом, соответствующими типу C без преобразования. Поскольку размер типа C int зависит от платформы, то и набор отклоняемых компоновок будет зависеть от платформы. Например, в Linux/x64 компоновщик отклонит компоновки, соответствующие типам C _Bool, (unsigned) char, (unsigned) short и float (и другим). Метод canonicalLayouts() можно использовать для определения компоновки, соответствующей конкретному типу C.

Хорошо известной функцией с переменным числом аргументов является функция printf, определённая в стандартной библиотеке C:

int printf(const char *format, ...);
Эта функция принимает строку формата и несколько дополнительных аргументов (количество таких аргументов задаётся строкой формата). Рассмотрим следующий вызов с переменным числом аргументов:
printf("%d plus %d equals %d", 2, 2, 4);
Чтобы выполнить эквивалентный вызов с помощью обработчика вызова вниз, необходимо создать описатель функции, который описывает специализированную сигнатуру вызываемой C-функции. Этот описатель должен включать дополнительную компоновку для каждого аргумента с переменным числом, который мы намерены предоставить. В данном случае специализированная сигнатура C-функции — (char*, int, int, int), так как строка формата принимает три целочисленных параметра. Затем нам нужно использовать параметр компоновщика параметр компоновщика, чтобы указать позицию первой компоновки с переменным числом в предоставленном описателе функции (начиная с 0). В данном случае, так как первый параметр — строка формата (аргумент без переменного числа), индекс первого аргумента с переменным числом должен быть установлен в 1, как показано ниже:
Linker linker = Linker.nativeLinker();
MethodHandle printf = linker.downcallHandle(
    linker.defaultLookup().findOrThrow("printf"),
        FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT),
        Linker.Option.firstVariadicArg(1) // first int is variadic
);
Затем мы можем вызвать специализированный обработчик вызова вниз обычным способом:
 try (Arena arena = Arena.ofConfined()) {
     //prints "2 plus 2 equals 4"
     int res = (int)printf.invokeExact(arena.allocateFrom("%d plus %d equals %d"), 2, 2, 4);
 }

Соображения безопасности

Создание обработчика вызова вниз изначально небезопасно. Символ в сторонней библиотеке, как правило, не содержит достаточной информации о сигнатуре (например, арности и типах параметров внешней функции). Вследствие этого, среда выполнения компоновщика не может проверить запросы компоновки. При взаимодействии клиента с обработчиком вызова вниз, полученным посредством некорректного запроса компоновки (например, указанием описателя функции с избыточным количеством компоновок аргументов), результат такого взаимодействия не определён и может привести к сбоям JVM.

При передаче обработчика вызова вверх сторонней функции может произойти сбой JVM, если сторонний код преобразует указатель функции, связанный с обработчиком вызова вверх, в тип, несовместимый с типом обработчика вызова вверх, а затем попытается вызвать функцию через полученный указатель функции. Более того, если обработчик метода, связанный с обработчиком вызова вверх, возвращает сегмент памяти, клиенты должны гарантировать, что этот адрес не станет недействительным после завершения вызова вверх. Это может привести к неопределённому поведению и даже к сбоям JVM, поскольку вызов вверх, как правило, выполняется в контексте вызова обработчика метода вызова вниз.

Требования к реализации:
Реализации этого интерфейса неизменяемы, потокобезопасны и базируются на значениях.
С:
22

Краткое описание вложенных классов

Модификатор и тип Интерфейс Описание
static interface  Linker.Option
Параметр компоновщика используется для предоставления дополнительных параметров запросу компоновки.

Краткое описание методов

Модификатор и тип Метод Описание
Map<String, MemoryLayout> canonicalLayouts()
Возвращает неизменяемое отображение между именами типов данных, используемых ABI, реализованным этим компоновщиком, и их каноническими компоновками.
SymbolLookup defaultLookup()
Возвращает поиск символов для символов в наборе часто используемых библиотек.
MethodHandle downcallHandle(FunctionDescriptor function, Linker.Option... options)
Ограниченный доступ.
Создаёт обработчик метода для вызова внешней функции с заданной сигнатурой.
MethodHandle downcallHandle(MemorySegment address, FunctionDescriptor function, Linker.Option... options)
Ограниченный доступ.
Создаёт обработчик метода для вызова внешней функции с заданной сигнатурой и адресом.
static Linker nativeLinker()
Возвращает компоновщик для ABI, связанного с основной платформой.
MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options)
Ограниченный доступ.
Создаёт обработчик вызова вверх, который можно передать другим внешним функциям в качестве указателя функции, связанного с заданной областью.

Подробное описание методов

nativeLinker

static Linker nativeLinker()
Возвращает линкер для ABI, связанного с основной платформой нативного кода.

Основная платформа нативного кода — это сочетание ОС и процессора, где в настоящее время выполняется Java-среда выполнения.

Примечание API:
В настоящее время невозможно получить линкер для другой комбинации ОС и процессора.
Требования к реализации:
Реализация нативного линкера гарантирует предоставление канонических представлений для базовых типов C.
Примечание по реализации:
Библиотеки, доступные по по умолчанию, связанные с возвращённым линкером, — это библиотеки нативного кода, загруженные в процессе, где в настоящее время выполняется Java-среда выполнения. Например, в Linux эти библиотеки обычно включают libc, libm и libdl.
Возвращает:
линкер для ABI, связанного с основной платформой нативного кода

downcallHandle

MethodHandle downcallHandle(MemorySegment address, FunctionDescriptor function, Linker.Option... options)
downcallHandle — это ограниченный метод платформы Java.
Программы могут использовать только downcallHandle при включённом доступе к ограниченным методам.
Ограниченные методы небезопасны и, при неправильном использовании, могут привести к аварийному завершению JVM или к повреждению памяти.
Создаёт обработчик метода для вызова внешней функции с заданной сигнатурой и адресом.

Вызов этого метода эквивалентен следующему коду:

linker.downcallHandle(function, options).bindTo(address);
Параметры:
address — сегмент памяти нативного кода, чьим базовым адресом является адрес целевой внешней функции
function — описатель функции целевой внешней функции
options — параметры линкера, связанные с этим запросом на связывание
Возвращает:
обработчик метода вызова
Исключение:
IllegalArgumentException — если предоставленный описатель функции не поддерживается этим линком
IllegalArgumentException — если !address.isNative(), или если address.equals(MemorySegment.NULL)
IllegalArgumentException — если задана некорректная комбинация параметров линкера
IllegalCallerException — если вызывающий элемент находится в модуле, в котором доступ к нативному коду не включён
См. также:
  • SymbolLookup

downcallHandle

MethodHandle downcallHandle(FunctionDescriptor function, Linker.Option... options)
downcallHandle — это ограниченный метод платформы Java.
Программы могут использовать только downcallHandle при включённом доступе к ограниченным методам.
Ограниченные методы небезопасны и, при неправильном использовании, могут привести к аварийному завершению JVM или к повреждению памяти.
Создаёт обработчик метода для вызова внешней функции с заданной сигнатурой.

Тип метода Java метода, связанный с возвращённым обработчиком метода, выводится из представлений аргументов и возвращаемого значения в описателе функции, но включает дополнительный ведущий параметр типа MemorySegment, из которого извлекается адрес целевой внешней функции. Кроме того, если представление возвращаемого значения в описателе функции является групповым представлением, возвращаемый обработчик метода принимает дополнительный ведущий параметр типа SegmentAllocator, который используется временем выполнения линкера для выделения области памяти, связанной со структурой, возвращённой обработчиком метода вызова.

При вызове обработчика метода вызова линкер обеспечивает следующие гарантии для любого аргумента A типа MemorySegment, чьё соответствующее представление — представление адреса:

  • A.scope().isAlive() == true. В противном случае вызов бросает IllegalStateException;
  • Вызов происходит в потоке T, таком что A.isAccessibleBy(T) == true. В противном случае вызов бросает WrongThreadException; и
  • A сохраняется во время вызова. Например, если A был получен с использованием совместной области, любая попытка закрыть область во время выполнения метода вызова приведёт к IllegalStateException.

Кроме того, если представление возвращаемого значения в предоставленном описателе функции — представление адреса, вызов возвращённого обработчика метода вернёт сегмент нативного кода, связанный со глобальной областью. В обычных условиях размер возвращённого сегмента — 0. Однако, если представление возвращаемого значения в описателе функции имеет целевое представление T, то размер возвращённого сегмента устанавливается в T.byteSize().

Возвращаемый обработчик метода бросит IllegalArgumentException, если MemorySegment, представляющий целевой адрес внешней функции, — это MemorySegment.NULL адрес. Если аргумент является MemorySegment, чьё соответствующее представление — групповое представление, линкер может попытаться получить доступ к содержимому сегмента. В связи с этим, может быть брошено одно из исключений, определённых методами MemorySegment.get(ValueLayout.OfByte, long) или MemorySegment.copy(MemorySegment, long, MemorySegment, long, long). Если аргумент является MemorySegment с представлением адреса, линкер бросит IllegalArgumentException, если сегмент — сегмент кучи, если сегменты кучи не разрешены явно параметром линкера Linker.Option.critical(boolean). Возвращаемый обработчик метода также бросит NullPointerException, если какой-либо аргумент, переданный ему, — null.

Параметры:
function — описатель функции целевой внешней функции
options — параметры линкера, связанные с этим запросом на связывание
Возвращает:
обработчик метода вызова
Исключение:
IllegalArgumentException — если предоставленный описатель функции не поддерживается этим линком
IllegalArgumentException — если задана некорректная комбинация параметров линкера
IllegalCallerException — если вызывающий элемент находится в модуле, в котором доступ к нативному коду не включён

upcallStub

MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options)
upcallStub — это ограниченный метод платформы Java.
Программы могут использовать upcallStub только при включенном доступе к ограниченным методам.
Ограниченные методы небезопасны и, при неправильном использовании, могут привести к аварийному завершению JVM или к повреждению памяти.
Создаёт заглушку вызова, которая может передаваться другим внешним функциям в качестве указателя на функцию, связанную с заданным ареной. Вызов такого указателя на функцию из внешнего кода приведёт к выполнению предоставленной обрабатывающей метод.

Адрес возвращаемого сегмента памяти указывает на только что созданную заглушку вызова и связан с предоставленной ареной. Таким образом, срок жизни возвращаемого сегмента заглушки вызова контролируется предоставленной ареной. Например, если предоставленная арена является ограниченной ареной, возвращаемый сегмент заглушки вызова будет освобождён, когда предоставленная ограниченная арена будет закрыта.

Аргумент заглушки вызова, чья соответствующая компоновка представляет собой разметку адреса, представляет собой локальный сегмент, связанный со глобальной областью видимости. В нормальных условиях размер этого аргумента сегмента равен 0. Однако, если у разметки адреса есть целевая разметка T, то размер аргумента сегмента устанавливается в T.byteSize().

Обрабатываемый метод не должен генерировать исключения. Если обрабатываемый метод генерирует исключение, JVM аварийно завершится. Чтобы этого избежать, клиенты должны обернуть код в обрабатываемом методе в блок try/catch для перехвата любых непредвиденных исключений. Это можно сделать, используя комбинирующий метод обработки MethodHandles.catchException(MethodHandle, Class, MethodHandle), и обрабатывать исключения по своему усмотрению в соответствующем блоке catch.

Параметры:
target — обрабатываемый метод
function — описатель функции заглушки вызова
arena — арена, связанная с возвращаемым сегментом заглушки вызова
options — параметры линкера, связанные с этим запросом связи
Возвращает:
сегмент нулевой длины, адрес которого является адресом заглушки вызова
Исключение:
IllegalArgumentException — если предоставленный описатель функции не поддерживается этим линковщиком
IllegalArgumentException — если тип target несовместим с типом, полученным из function
IllegalArgumentException — если выявлено, что обрабатываемый метод может генерировать исключение
IllegalStateException — если arena.scope().isAlive() == false
WrongThreadException — если arena является ограниченной ареной, и этот метод вызывается из потока T, отличного от потока-владельца арены
IllegalCallerException — если вызывающий находится в модуле, в котором не включен доступ к нативным функциям

defaultLookup

SymbolLookup defaultLookup()
Возвращает поиск символов для символов в наборе часто используемых библиотек.

Каждый Linker отвечает за выбор библиотек, которые широко признаны полезными на сочетании ОС и процессора, поддерживаемом Linker. Соответственно, точный набор символов, экспонируемых поиском символов, не определён; он варьируется от одного Linker к другому.

Примечание реализации:
Сильно рекомендуется, чтобы результат defaultLookup() экспонировал набор символов, устойчивый во времени. Клиенты defaultLookup() могут потерпеть неудачу, если символ, ранее экспонированный поиском символов, больше не экспонируется.

Если реализатор предоставляет реализации Linker для нескольких комбинаций ОС и процессоров, то настоятельно рекомендуется, чтобы результат defaultLookup() экспонировал, насколько это возможно, согласованный набор символов для всех комбинаций ОС и процессоров.

Возвращает:
поиск символов для символов в наборе часто используемых библиотек

canonicalLayouts

Map<String, MemoryLayout> canonicalLayouts()
Возвращает неизменяемое отображение между именами типов данных, используемых ABI, реализованной этим линковщиком, и их каноническими компоновками.

Каждый Linker отвечает за выбор типов данных, которые широко признаны полезными на сочетании ОС и процессора, поддерживаемом Linker. Соответственно, точный набор имён типов данных и канонических компоновок, экспонируемых линковщиком, не определён; они варьируются от одного Linker к другому.

Примечание реализации:
Сильно рекомендуется, чтобы результат canonicalLayouts() экспонировал набор символов, устойчивый во времени. Клиенты canonicalLayouts() могут потерпеть неудачу, если тип данных, ранее экспонированный линковщиком, больше не экспонируется или если его каноническая компоновка обновлена.

Если реализатор предоставляет реализации Linker для нескольких комбинаций ОС и процессоров, то настоятельно рекомендуется, чтобы результат canonicalLayouts() экспонировал, насколько это возможно, согласованный набор символов для всех комбинаций ОС и процессоров.

Возвращает:
неизменяемое отображение между именами типов данных, используемых ABI, реализованной этим линковщиком, и их каноническими компоновками

© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
https://download.java.net/java/early_access/jdk24/docs/api/java.base/java/lang/foreign/Linker.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API