Kotlin/Native в виде динамической библиотеки — руководство
Вы можете создавать динамические библиотеки, чтобы использовать код на Kotlin в существующих программах. Это позволяет совместно использовать код на разных платформах и в разных языках, включая JVM, Python, Android и другие.
Вы можете использовать код Kotlin/Native в существующих нативных приложениях или библиотеках. Для этого нужно скомпилировать код на Kotlin в динамическую библиотеку в формате .so, .dylib или .dll.
В этом руководстве вы узнаете, как:
Для создания библиотеки Kotlin можно использовать командную строку напрямую или с помощью файла скрипта (например, файла .sh или .bat). Однако этот подход плохо подходит для крупных проектов с сотнями файлов и библиотек. Система сборки упрощает этот процесс: она скачивает и кэширует двоичные файлы компилятора Kotlin/Native и библиотеки с транзитивными зависимостями, а также запускает компилятор и тесты. Kotlin/Native поддерживает систему сборки Gradle с помощью плагина Kotlin Multiplatform.
Рассмотрим расширенные варианты использования Kotlin/Native для взаимодействия с C и сборки проектов Kotlin Multiplatform с помощью Gradle.
Создание библиотеки Kotlin
Компилятор Kotlin/Native может создать динамическую библиотеку из кода на Kotlin. Динамическая библиотека часто сопровождается заголовочным файлом .h, который используется для вызова скомпилированного кода из C.
Создадим библиотеку Kotlin и используем её из программы на C.
-
Перейдите в каталог
src/nativeMain/kotlinи создайте файлlib.ktсо следующим содержимым библиотеки:package example object Object { val field = "A" } class Clazz { fun memberFunction(p: Int): ULong = 42UL } fun forIntegers(b: Byte, s: Short, i: UInt, l: Long) { } fun forFloats(f: Float, d: Double) { } fun strings(str: String) : String? { return "That is '$str' from C" } val globalString = "A global String" -
Обновите файл сборки Gradle
build.gradle(.kts)следующим образом:import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget plugins { kotlin("multiplatform") version "2.4.20" } repositories { mavenCentral() } kotlin { macosArm64() // macOS on Apple Silicon // linuxArm64() // Linux on ARM64 platforms // linuxX64() // Linux on x86_64 platforms // mingwX64() // on Windows targets.withType<KotlinNativeTarget>().configureEach { binaries { sharedLib { baseName = "native" // macOS // baseName = "native" // Linux // baseName = "libnative" // Windows } } } } tasks.wrapper { gradleVersion = "9.7.0" distributionType = Wrapper.DistributionType.ALL }import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget plugins { id 'org.jetbrains.kotlin.multiplatform' version '2.4.20' } repositories { mavenCentral() } kotlin { macosArm64() // Apple Silicon macOS // linuxArm64() // Linux on ARM64 platforms // linuxX64() // Linux on x86_64 platforms // mingwX64() // Windows targets.withType(KotlinNativeTarget).configureEach { binaries { sharedLib { baseName = "native" // macOS // baseName = "native" // Linux // baseName = "libnative" // Windows } } } } wrapper { gradleVersion = "9.7.0" distributionType = "ALL" }Блок
binaries {}настраивает проект для создания динамической или разделяемой библиотеки.libnativeиспользуется в качестве имени библиотеки и префикса имени сгенерированного заголовочного файла. Кроме того, он служит префиксом для всех объявлений в заголовочном файле.
-
Чтобы собрать библиотеку, запустите задачу Gradle
linkDebugShared<YourTargetName>в IDE или выполните в терминале команду, как в этом примере:./gradlew linkDebugSharedMacosArm64
В результате сборки библиотека будет создана в каталоге build/bin/<yourTargetName>/debugShared вместе со следующими файлами:
macOS:
libnative_api.hиlibnative.dylibLinux:
libnative_api.hиlibnative.soWindows:
libnative_api.h,libnative.defиlibnative.dll
Компилятор Kotlin/Native использует одинаковые правила для создания файла .h на всех платформах. Рассмотрим API библиотеки Kotlin для C.
Сгенерированный заголовочный файл
Рассмотрим, как объявления Kotlin преобразуются в функции C.
Откройте заголовочный файл libnative_api.h в каталоге build/bin/<yourTargetName>/debugShared. В самом начале находится стандартная часть заголовка и футера C/C++:
#ifndef KONAN_LIBNATIVE_H
#define KONAN_LIBNATIVE_H
#ifdef __cplusplus
extern "C" {
#endif
/// The rest of the generated code
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* KONAN_LIBNATIVE_H */
Далее в файле libnative_api.h находится блок с общими определениями типов:
#ifdef __cplusplus typedef bool libnative_KBoolean; #else typedef _Bool libnative_KBoolean; #endif typedef unsigned short libnative_KChar; typedef signed char libnative_KByte; typedef short libnative_KShort; typedef int libnative_KInt; typedef long long libnative_KLong; typedef unsigned char libnative_KUByte; typedef unsigned short libnative_KUShort; typedef unsigned int libnative_KUInt; typedef unsigned long long libnative_KULong; typedef float libnative_KFloat; typedef double libnative_KDouble; typedef float __attribute__ ((__vector_size__ (16))) libnative_KVector128; typedef void* libnative_KNativePtr;
Kotlin использует префикс libnative_ для всех объявлений в созданном файле libnative_api.h. Ниже приведён полный список соответствий типов:
Определение Kotlin |
Тип C |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Раздел определений файла libnative_api.h показывает, как примитивные типы Kotlin соответствуют примитивным типам C. Компилятор Kotlin/Native автоматически генерирует эти записи для каждой библиотеки. Обратное преобразование описано в руководстве Сопоставление примитивных типов данных из C.
После автоматически сгенерированных определений типов вы найдёте отдельные определения типов, используемых в вашей библиотеке:
struct libnative_KType;
typedef struct libnative_KType libnative_KType;
/// Automatically generated type definitions
typedef struct {
libnative_KNativePtr pinned;
} libnative_kref_example_Object;
typedef struct {
libnative_KNativePtr pinned;
} libnative_kref_example_Clazz;
В C структура объявляется с помощью синтаксиса typedef struct { ... } TYPE_NAME.
Как видно из этих определений, типы Kotlin сопоставляются по одному и тому же шаблону: Object сопоставляется с libnative_kref_example_Object, а Clazz — с libnative_kref_example_Clazz. Все структуры содержат только поле pinned с указателем. Тип поля libnative_KNativePtr ранее в файле определён как void*.
Поскольку C не поддерживает пространства имён, компилятор Kotlin/Native генерирует длинные имена, чтобы избежать возможных конфликтов с другими символами в существующем нативном проекте.
Служебные функции среды выполнения
Структура libnative_ExportedSymbols определяет все функции, предоставляемые Kotlin/Native и вашей библиотекой. Для имитации пакетов в ней активно используются вложенные анонимные структуры. Префикс libnative_ образован от имени библиотеки.
libnative_ExportedSymbols содержит в заголовочном файле несколько вспомогательных функций:
typedef struct {
/* Service functions. */
void (*DisposeStablePointer)(libnative_KNativePtr ptr);
void (*DisposeString)(const char* string);
Эти функции работают с объектами Kotlin/Native. Функция DisposeStablePointer вызывается для освобождения ссылки на объект Kotlin, а функция DisposeString — для освобождения строки Kotlin, которая в C имеет тип char*.
Следующая часть файла libnative_api.h содержит объявления структур для функций среды выполнения:
libnative_KBoolean (*IsInstance)(libnative_KNativePtr ref, const libnative_KType* type); libnative_KBoolean (*IsInstance)(libnative_KNativePtr ref, const libnative_KType* type); libnative_kref_kotlin_Byte (*createNullableByte)(libnative_KByte); libnative_KByte (*getNonNullValueOfByte)(libnative_kref_kotlin_Byte); libnative_kref_kotlin_Short (*createNullableShort)(libnative_KShort); libnative_KShort (*getNonNullValueOfShort)(libnative_kref_kotlin_Short); libnative_kref_kotlin_Int (*createNullableInt)(libnative_KInt); libnative_KInt (*getNonNullValueOfInt)(libnative_kref_kotlin_Int); libnative_kref_kotlin_Long (*createNullableLong)(libnative_KLong); libnative_KLong (*getNonNullValueOfLong)(libnative_kref_kotlin_Long); libnative_kref_kotlin_Float (*createNullableFloat)(libnative_KFloat); libnative_KFloat (*getNonNullValueOfFloat)(libnative_kref_kotlin_Float); libnative_kref_kotlin_Double (*createNullableDouble)(libnative_KDouble); libnative_KDouble (*getNonNullValueOfDouble)(libnative_kref_kotlin_Double); libnative_kref_kotlin_Char (*createNullableChar)(libnative_KChar); libnative_KChar (*getNonNullValueOfChar)(libnative_kref_kotlin_Char); libnative_kref_kotlin_Boolean (*createNullableBoolean)(libnative_KBoolean); libnative_KBoolean (*getNonNullValueOfBoolean)(libnative_kref_kotlin_Boolean); libnative_kref_kotlin_Unit (*createNullableUnit)(void); libnative_kref_kotlin_UByte (*createNullableUByte)(libnative_KUByte); libnative_KUByte (*getNonNullValueOfUByte)(libnative_kref_kotlin_UByte); libnative_kref_kotlin_UShort (*createNullableUShort)(libnative_KUShort); libnative_KUShort (*getNonNullValueOfUShort)(libnative_kref_kotlin_UShort); libnative_kref_kotlin_UInt (*createNullableUInt)(libnative_KUInt); libnative_KUInt (*getNonNullValueOfUInt)(libnative_kref_kotlin_UInt); libnative_kref_kotlin_ULong (*createNullableULong)(libnative_KULong); libnative_KULong (*getNonNullValueOfULong)(libnative_kref_kotlin_ULong);
Функцию IsInstance можно использовать, чтобы проверить, является ли объект Kotlin (на который ссылается указатель .pinned) экземпляром типа. Фактический набор сгенерированных операций зависит от того, как используются типы.
Функции вашей библиотеки
Рассмотрим отдельные объявления структур, используемые в вашей библиотеке. Поле libnative_kref_example имитирует структуру пакетов в вашем коде Kotlin с префиксом libnative_kref.:
typedef struct {
/* User functions. */
struct {
struct {
struct {
struct {
libnative_KType* (*_type)(void);
libnative_kref_example_Object (*_instance)();
const char* (*get_field)(libnative_kref_example_Object thiz);
} Object;
struct {
libnative_KType* (*_type)(void);
libnative_kref_example_Clazz (*Clazz)();
libnative_KULong (*memberFunction)(libnative_kref_example_Clazz thiz, libnative_KInt p);
} Clazz;
const char* (*get_globalString)();
void (*forFloats)(libnative_KFloat f, libnative_KDouble d);
void (*forIntegers)(libnative_KByte b, libnative_KShort s, libnative_KUInt i, libnative_KLong l);
const char* (*strings)(const char* str);
} example;
} root;
} kotlin;
} libnative_ExportedSymbols;
В коде используются объявления анонимных структур. Здесь struct { ... } foo объявляет поле во внешней структуре анонимного типа, у которого нет имени.
Поскольку C также не поддерживает объекты, для имитации семантики объектов используются указатели на функции. Указатель на функцию объявляется как RETURN_TYPE (* FIELD_NAME)(PARAMETERS).
Поле libnative_kref_example_Clazz представляет Clazz из Kotlin. Доступ к libnative_KULong осуществляется через поле memberFunction. Единственное отличие состоит в том, что memberFunction принимает ссылку thiz в качестве первого параметра. Поскольку C не поддерживает объекты, указатель thiz передаётся явно.
В поле Clazz (то есть libnative_kref_example_Clazz_Clazz) находится конструктор, который служит функцией-конструктором для создания экземпляра Clazz.
Доступ к объекту Kotlin object Object осуществляется как к libnative_kref_example_Object. Функция _instance возвращает единственный экземпляр объекта.
Свойства преобразуются в функции. Префиксы get_ и set_ используются в именах функций-геттеров и функций-сеттеров соответственно. Например, доступное только для чтения свойство globalString из Kotlin преобразуется в функцию get_globalString в C.
Глобальные функции forFloats, forIntegers и strings преобразуются в указатели на функции в анонимной структуре libnative_kref_example.
Точка входа
Теперь, когда вы знаете, как создаётся API, можно перейти к начальной точке — инициализации структуры libnative_ExportedSymbols. Рассмотрим заключительную часть файла libnative_api.h:
extern libnative_ExportedSymbols* libnative_symbols(void);
Функция libnative_symbols позволяет открыть доступ из нативного кода к библиотеке Kotlin/Native. Это точка входа для доступа к библиотеке. Имя библиотеки используется как префикс имени функции.
Использование сгенерированных заголовков из C
Использовать сгенерированные заголовки из C просто. Создайте в каталоге библиотеки файл main.c со следующим кодом:
#include "libnative_api.h"
#include "stdio.h"
int main(int argc, char** argv) {
// Obtain reference for calling Kotlin/Native functions
libnative_ExportedSymbols* lib = libnative_symbols();
lib->kotlin.root.example.forIntegers(1, 2, 3, 4);
lib->kotlin.root.example.forFloats(1.0f, 2.0);
// Use C and Kotlin/Native strings
const char* str = "Hello from Native!";
const char* response = lib->kotlin.root.example.strings(str);
printf("in: %s\nout:%s\n", str, response);
lib->DisposeString(response);
// Create Kotlin object instance
libnative_kref_example_Clazz newInstance = lib->kotlin.root.example.Clazz.Clazz();
long x = lib->kotlin.root.example.Clazz.memberFunction(newInstance, 42);
lib->DisposeStablePointer(newInstance.pinned);
printf("DemoClazz returned %ld\n", x);
return 0;
}
Сборка и запуск проекта
macOS
Чтобы скомпилировать код C и связать его с динамической библиотекой, перейдите в каталог библиотеки и выполните следующую команду:
clang main.c libnative.dylib
Компилятор создаст исполняемый файл с именем a.out. Запустите его, чтобы выполнить код Kotlin из библиотеки C.
Linux
Чтобы скомпилировать код C и связать его с динамической библиотекой, перейдите в каталог библиотеки и выполните следующую команду:
gcc main.c libnative.so
Компилятор создаст исполняемый файл с именем a.out. Запустите его, чтобы выполнить код Kotlin из библиотеки C. В Linux необходимо добавить . в LD_LIBRARY_PATH, чтобы приложение загружало библиотеку libnative.so из текущей папки.
Windows
Сначала необходимо установить компилятор Microsoft Visual C++, поддерживающий целевую платформу x64_64.
Проще всего установить Microsoft Visual Studio на компьютере с Windows. Во время установки выберите необходимые компоненты для работы с C++, например Разработка классических приложений на C++.
В Windows динамические библиотеки можно подключить, сгенерировав статическую библиотеку-обёртку, или вручную с помощью функции LoadLibrary либо аналогичных функций Win32API.
Воспользуемся первым вариантом и создадим статическую библиотеку-обёртку для libnative.dll:
-
Вызовите
lib.exeиз набора инструментов, чтобы создать статическую библиотеку-обёрткуlibnative.lib, которая автоматизирует использование DLL в коде:lib /def:libnative.def /out:libnative.lib
-
Скомпилируйте
main.cв исполняемый файл. Добавьте сгенерированный файлlibnative.libв команду сборки и запустите её:cl.exe main.c libnative.lib
Команда создаст файл
main.exe, который можно запустить.
Что дальше
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/native-dynamic-libraries.html