Поиск и вызов внешних функций

Последнее обновление: 21.12.2025

Для поиска библиотечных функций языка Си и их подготовке для вызова в API FFM применяется класс java.lang.foreign.Linker. Для создания линкера, связанного с текущей нативной платформой ОС применяется статический метод Linker.nativeLinker():

Linker linker = Linker.nativeLinker();

Фактически, часть "поиска" делегируется экземпляру SymbolLookup, который определяется с помощью метода defaultLookup() линкера:

Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();

Библиотеки, о которых знает поиск по умолчанию, зависят от платформы. В Linux это libc и libm. Этот поиск достаточен для таких известных функций, как printf и malloc.

Для использования со специализированной библиотекой (или своей) в метод SymbolLookup.libraryLookup() передается путь к файлу библиотеки (в виде строки или объекта Path) и арена:

SymbolLookup somelib = SymbolLookup.libraryLookup("/lib/x86_64-linux-gnu/libsxxx.so", arena);

Метод libraryLookup загружает библиотеку. А когда арена закрывается, библиотека выгружается.

Поиск функций

Получив объект SymbolLookup, мы можем перейти к поиску функций. Для этого SymbolLookup предоставляет два метода:

  • Optional<MemorySegment> find(String name): возвращает адрес символа с заданным именем

  • MemorySegment findOrThrow(String name): возвращает адрес символа с заданным именем или генерирует исключение, если функция не найдена

В обоих случаях указатель на полученную функцию возвращается через объект MemorySegment, работа с которым была рассмотрена в предыдущеихз статьях. Например, поиск функции printf:

Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MemorySegment printf_addr = stdlib.findOrThrow("printf");

Дескриптор функции/метода

Получив адрес вызываемой функции, можн создать дескриптор метода для ее вызова. Дескриптор метода - это объект, через который можно передавать аргументы, вызывать метод и получать возвращаемое значение.

Дескриптор метода представляет тип MethodHandle. Для его создания линкер - объект Linker предоставляет метода downcallHandle()

  • downcallHandle(FunctionDescriptor function, Linker.Option... options)

    Создает дескриптор метода, используемый для вызова внешней функции с заданной сигнатурой

  • downcallHandle(MemorySegment address, FunctionDescriptor function, Linker.Option... options)

    Создает дескриптор метода, используемый для вызова внешней функции с заданной сигнатурой и адресом

Этот метод принимает до трех параметров:

  • address: сегмент памяти, базовый адрес которого совпадает с адресом вызываемой внешней функции

  • function: дескриптор вызываемой внешней функции

  • options: параметры линкера, связанные с этим запросом на компоновку

Ключевой здесь параметр - это десткриптор функции, представленный интерфейсом FunctionDescriptor. Для определения этого объекта обычно применяется статический метод FunctionDescriptor.of

FunctionDescriptor.of(MemoryLayout resLayout, MemoryLayout... argLayouts)

Его первый параметр представляет компоновку памяти типа результата функции, а второй параметр - компоновки памяти для параметров функции.

Например, получим десткриптор метода для функции printf:

import java.lang.foreign.*;
import java.lang.invoke.*;

public class Program {

    public static void main(String[] args) {

        // получаем линкер/компоновщик 
        Linker linker = Linker.nativeLinker();
        // получаем стандартную библиотеку
        SymbolLookup stdlib = linker.defaultLookup();
        // получаем адрес функции printf
        MemorySegment printf_addr = stdlib.findOrThrow("printf");
        // дескриптор функции printf
        FunctionDescriptor printf_desc = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS);
        // дескриптор метода для функции printf
        MethodHandle printf = linker.downcallHandle(printf_addr, printf_desc);
        System.out.println(printf);     // MethodHandle(MemorySegment)int
    }
}

При передаче указателя в функцию C или получении результата указателя используется ValueLayout.ADDRESS. Тип блока памяти не имеет значения. Кроме того, необходимо точно указать тип возвращаемого значения функции и типы параметров. Если предоставить неверную информацию, виртуальная машина может завершиться с ошибкой.

Так, функция printf в C возвращает целое число int, а в качестве параметра принимает строку (по сути указатель на символы), поэтому ее дескриптор имеет форму

FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)

Вызов функции

Получив дескриптор метода, наконец мы можем вызвать функцию. Для этого у типа MethodHandle применяется метод invoke()

public final Object invoke(Object... args) throws Throwable

В качестве аргументов в вызов метода передаются значения для параметров внешней функции. ПРи этом параметры должны соответствовать типам языка Си, а не Java. Например, мы не можем написать так:

printf.invoke("Hello");

Хотя, казалось бы функция printf() принимает строку. Однако это должна быть строка языка Си. Соответственно нам надо сконвертировать строку Java в строку Си (UTF-8). Пример такого преобразования и вызова функции printf:

import java.lang.foreign.*;
import java.lang.invoke.*;

public class Program {

    public static void main(String[] args) throws Throwable{

        // получаем линкер/компоновщик 
        Linker linker = Linker.nativeLinker();
        // получаем стандартную библиотеку
        SymbolLookup stdlib = linker.defaultLookup();

        // получаем адрес функции printf
        MemorySegment printf_addr = stdlib.findOrThrow("printf");
        // дескриптор функции printf
        FunctionDescriptor printf_desc = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS);
        // дескриптор метода для функции printf
        MethodHandle printf = linker.downcallHandle(printf_addr, printf_desc);

        // создаем арену
        try (Arena arena = Arena.ofConfined()) {
            
            // строка для вывода
            String str = "Hello METANIT.COM\n";
            // преобразуем строку Java в строку UTF-8
            MemorySegment str_seg = arena.allocateFrom(str);

            // вызываем функцию printf и получаем результат
            int char_count = (int) printf.invoke(str_seg);  // Hello METANIT.COM
            
            System.out.println("Длина строки: " + char_count + " символов");  // Длина строки: 18 символов
        }
    }
}

Возвращаемый тип void

Отдельно стоит сказать про функции, который возвращают void. В этом случае для определения дескриптора функции применяется метод FunctionDescriptor.ofVoid

FunctionDescriptor ofVoid(MemoryLayout... argLayouts)

В качестве параметров методу также передаются значения, которые передаются вызываемой внешней функции. Указатель на void (void*), как и все указатели, представляют тип ValueLayout.ADDRESS. Например, получим дескрипторы методов для функций malloc (выделение памяти) и free (освобождение памяти)

import java.lang.foreign.*;
import java.lang.invoke.*;

public class Program {

    public static void main(String[] args) throws Throwable{

        // получаем линкер/компоновщик 
        Linker linker = Linker.nativeLinker();
        // получаем стандартную библиотеку
        SymbolLookup stdlib = linker.defaultLookup();

        // получаем адрес функции malloc
        MemorySegment mallocAddr = stdlib.findOrThrow("malloc");
        // void* malloc(size_t)
        MethodHandle mallocHandle = linker.downcallHandle(mallocAddr, FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));
        System.out.println(mallocHandle);   // MethodHandle(long)MemorySegment


        // получаем адрес функции free
        MemorySegment freeAddr = stdlib.findOrThrow("free");
        // void free(void *ptr)
        MethodHandle freeHandle = linker.downcallHandle(freeAddr, FunctionDescriptor.ofVoid(ValueLayout.ADDRESS));
        System.out.println(freeHandle);     // MethodHandle(MemorySegment)void

    }
}
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850