Spec-Zone.ru › OpenJDK 21

Интерфейс Linker

public sealed interface Linker
Linker — это предварительный API платформы Java.
Программы могут использовать Linker только при включенных предварительных функциях.
Предварительные функции могут быть удалены в будущих выпусках или доработаны до постоянных функций платформы Java.
Линкер предоставляет доступ к внешним функциям из кода Java и к коду Java из внешних функций.

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

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

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

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

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

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

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

Скалярные типы C, такие как bool, int, моделируются как макеты значенийПРЕВЬЮ подходящего носителя. Сопоставление между скалярным типом и соответствующим макетом зависит от 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 struct сопоставляется с структурным макетомПРЕВЬЮ, а тип C union сопоставляется с union layoutПРЕВЬЮ. При определении макета структуры или объединения клиенты должны учитывать ограничения размера и выравнивания соответствующего определения составного типа в C. Например, отступы между двумя полями структуры должны быть явно смоделированы, путем добавления члена макета заполненияПРЕВЬЮ соответствующего размера в результирующий макет структуры.

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

В следующей таблице приведены примеры моделирования типов C на Linux/x64:

Сопоставление типов C
Тип C Макет Тип Java
bool ValueLayout.JAVA_BOOLEANПРЕВЬЮ boolean
char ValueLayout.JAVA_BYTEПРЕВЬЮ byte
short ValueLayout.JAVA_SHORTПРЕВЬЮ short
int ValueLayout.JAVA_INTПРЕВЬЮ int
long ValueLayout.JAVA_LONGПРЕВЬЮ long
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(32),
     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 поддерживается нативным линком NL если:

  • L — это макет значения V и V.withoutName() равенравенПРЕВЬЮ одному из следующих констант макетов:
    • ValueLayout.JAVA_BOOLEANПРЕВЬЮ
    • ValueLayout.JAVA_BYTEПРЕВЬЮ
    • ValueLayout.JAVA_CHARПРЕВЬЮ
    • ValueLayout.JAVA_SHORTПРЕВЬЮ
    • ValueLayout.JAVA_INTПРЕВЬЮ
    • ValueLayout.JAVA_LONGПРЕВЬЮ
    • ValueLayout.JAVA_FLOATПРЕВЬЮ
    • ValueLayout.JAVA_DOUBLEПРЕВЬЮ
  • L — это макет адреса A и A.withoutTargetLayout().withoutName() равенравенПРЕВЬЮ ValueLayout.ADDRESSПРЕВЬЮ
  • L — это макет последовательности S и выполняются все следующие условия:
    1. ограничение выравнивания S установлено в его естественное выравнивание, и
    2. S.elementLayout() — это макет, поддерживаемый NL.
  • L — это макет группы G и выполняются все следующие условия:
    1. ограничение выравнивания G установлено в его естественное выравнивание;
    2. размер G кратен его ограничению выравнивания;
    3. каждый макет элемента в G.memberLayouts() — это либо макет заполнения, либо макет, поддерживаемый NL, и
    4. 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().find("qsort").orElseThrow(),
        FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
);
Как и прежде, мы используем ValueLayout.JAVA_LONGПРЕВЬЮ для сопоставления типа C size_t, а ValueLayout.ADDRESSПРЕВЬЮ — для первого параметра-указателя (указателя на массив) и последнего параметра (указателя на функцию).

Чтобы вызвать полученный выше обработчик вызова qsort, нам нужно передать указатель на функцию в качестве последнего параметра. Для этого нужно создать указатель на функцию из существующего обработчика метода. Сначала напишем метод Java, который может сравнивать два элемента int, переданных как указатели (т.е. как сегменты памятиПРЕВЬЮ):

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.allocateArray(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().find("malloc").orElseThrow(),
    FunctionDescriptor.of(ADDRESS, JAVA_LONG)
);

MethodHandle free = linker.downcallHandle(
    linker.defaultLookup().find("free").orElseThrow(),
    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, которые могут принимать переменное количество и тип аргументов. Они объявляются:
  1. С заключительным многоточием (...) в конце списка формальных параметров, например: void foo(int x, ...);
  2. Со списком формальных параметров, называемым функцией без прототипа, например: void foo();
Аргументы, передаваемые вместо многоточия или аргументы, переданные функции без прототипа, называются аргументами с переменным числом параметров. Функции с переменным числом параметров, по сути, являются шаблонами, которые могут быть специализированы в несколько не-вариабельных функций путём замены многоточия или пустого списка формальных параметров списком параметров с переменным числом параметров фиксированного количества и типа.

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

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

Собственный линковщик поддерживает только специализированную форму вариадной функции. Вариадную функцию в специализированной форме можно связать с помощью дескриптора функции, описывающего специализированную форму. Кроме того, необходимо указать параметр линковщика Linker.Option.firstVariadicArg(int)ПРОСМОТР для указания первого вариадного параметра в списке параметров. Соответствующая структура аргументов (если она есть), а также все последующие структуры аргументов в дескрипторе специализированной функции называются структурами вариадных аргументов. Для функции без прототипа индекс, переданный Linker.Option.firstVariadicArg(int)ПРОСМОТР, должен всегда быть 0.

Собственный линковщик отклонит попытку связать дескриптор специализированной функции с любыми структурами вариадных аргументов, соответствующими типу C, который подвергнется стандартному продвижению аргументов (как описано выше). Точно, какие структуры будут отклонены, зависит от платформы, но, например, на Linux/x64 будут отклонены структуры ValueLayout.JAVA_BOOLEANПРОСМОТР, ValueLayout.JAVA_BYTEПРОСМОТР, ValueLayout.JAVA_CHARПРОСМОТР, ValueLayout.JAVA_SHORTПРОСМОТР и ValueLayout.JAVA_FLOATПРОСМОТР.

Известной вариадной функцией является функция 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().find("printf").orElseThrow(),
        FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT),
        Linker.Option.firstVariadicArg(1) // first int is variadic
);
Затем мы можем вызвать обработчик специализированного вызова вниз обычным способом:
try (Arena arena = Arena.ofConfined()) {
    int res = (int)printf.invokeExact(arena.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
}

Вопросы безопасности

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

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

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

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

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

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

Модификатор и тип Метод Описание
SymbolLookupPREVIEW defaultLookup()
Возвращает поиск символов для символов в наборе часто используемых библиотек.
MethodHandle downcallHandle(FunctionDescriptorPREVIEW function, Linker.OptionPREVIEW... options)
Создаёт обработчик метода, который используется для вызова внешней функции с заданной сигнатурой.
MethodHandle downcallHandle(MemorySegmentPREVIEW address, FunctionDescriptorPREVIEW function, Linker.OptionPREVIEW... options)
Создаёт обработчик метода, который используется для вызова внешней функции с заданной сигнатурой и адресом.
static LinkerPREVIEW nativeLinker()
Возвращает линковщик для ABI, связанного с базовой платформой.
MemorySegmentPREVIEW upcallStub(MethodHandle target, FunctionDescriptorPREVIEW function, ArenaPREVIEW arena, Linker.OptionPREVIEW... options)
Создаёт заглушку вызова вверх, которая может передаваться другим внешним функциям в качестве указателя функции, связанной с заданным ареной.

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

nativeLinker

static LinkerPREVIEW nativeLinker()
Возвращает линковщик для ABI, связанного с базовой платформой нативного кода. Базовая платформа нативного кода — это сочетание ОС и процессора, на котором в данный момент выполняется Java-среда выполнения.
Примечание API:
В настоящее время невозможно получить линковщик для другого сочетания ОС и процессора.
Примечание реализации:
Библиотеки, экспонируемые с помощью поиска по умолчанию, связанного с возвращенным линковщиком, — это библиотеки нативного кода, загруженные в процессе, где в данный момент выполняется Java-среда выполнения. Например, в Linux эти библиотеки обычно включают libc, libm и libdl.
Возвращает:
линковщик для ABI, связанного с базовой платформой нативного кода
Имеет исключения:
UnsupportedOperationException — если базовая платформа нативного кода не поддерживается.

downcallHandle

MethodHandle downcallHandle(MemorySegmentPREVIEW address, FunctionDescriptorPREVIEW function, Linker.OptionPREVIEW... options)
Создаёт обработчик вызова, который используется для вызова внешней функции с заданной сигнатурой и адресом.

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

linker.downcallHandle(function).bindTo(symbol);

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

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

downcallHandle

MethodHandle downcallHandle(FunctionDescriptorPREVIEW function, Linker.OptionPREVIEW... options)
Создаёт обработчик вызова, который используется для вызова внешней функции с заданной сигнатурой.

Тип метода 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ПРЕДПРОСМОТР. Возвращаемый обработчик вызовов дополнительно выбросит NullPointerException, если какой-либо переданный в него аргумент null.

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

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

upcallStub

MemorySegmentPREVIEW upcallStub(MethodHandle target, FunctionDescriptorPREVIEW function, ArenaPREVIEW arena, Linker.OptionPREVIEW... options)
Создаёт заглушку вызова, которая может передаваться другим внешним функциям в качестве указателя на функцию, связанную с заданной областью. Вызов такого указателя на функцию из внешнего кода приведёт к выполнению предоставленного обработчика метода.

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

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

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

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

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

defaultLookup

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

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

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

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

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

© 1993, 2023, 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://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/foreign/Linker.html

Spec-Zone.ru

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