Spec-Zone.ru › OpenJDK 25

Интерфейс Linker

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

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

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

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

Кроме того, компоновщик предоставляет способ поиска внешних функций в библиотеках, соответствующих 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().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» (во всех приведённых здесь примерах предполагается использование этих зависящих от платформы соответствий):

Соответствие типов 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, который может сравнивать два элемента типа 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.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, реализованным этим компоновщиком, с их каноническими структурами данных

Сообщить об ошибке или предложить улучшение
Дополнительную справочную информацию по API и документацию для разработчиков см. в разделе Документация Java SE, содержащем более подробные описания для разработчиков, обзоры концепций, определения терминов, обходные решения и работающие примеры кода. Другие версии.
Java является товарным знаком или зарегистрированным товарным знаком Oracle и/или ее аффилированных лиц в США и других странах.
Авторские права © 1993, 2025, Oracle и/или ее аффилированные лица, 500 Oracle Parkway, Redwood Shores, CA 94065 USA.
Все права защищены. Использование регулируется условиями лицензии и политикой распространения документации.

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

Spec-Zone.ru

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