Kotlin/Native в качестве динамической библиотеки – учебник
Узнайте, как использовать код Kotlin/Native из существующих приложений или библиотек нативных языках. Для этого необходимо скомпилировать код Kotlin в динамическую библиотеку, .so, .dylib, и .dll.
Kotlin/Native также имеет тесную интеграцию с технологиями Apple. Учебник Kotlin/Native как Apple Framework объясняет, как скомпилировать код Kotlin в фреймворк для Swift и Objective-C.
В этом руководстве вы:
Скомпилировать и запустить пример на Linux и macOS и Windows
Создание библиотеки Kotlin
Компилятор Kotlin/Native может создать динамическую библиотеку из кода Kotlin. Динамическая библиотека часто поставляется с заголовочным файлом, файлом .h, который вы будете использовать для вызова скомпилированного кода из C.
Лучший способ понять эти техники — попробовать их на практике. Сначала создайте простую библиотеку Kotlin и используйте её из программы на C.
Начните с создания файла библиотеки на Kotlin и сохраните его как hello.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"
Хотя можно использовать командную строку, как напрямую, так и в сочетании со скрипт-файлом (например, с файлом .sh или .bat), такой подход не масштабируется для крупных проектов с сотнями файлов и библиотек. В этом случае лучше использовать компилятор Kotlin/Native с системой сборки, так как она помогает загружать и кэшировать исполняемые файлы и библиотеки Kotlin/Native с транзитивными зависимостями, а также запускать компилятор и тесты. Kotlin/Native может использовать систему сборки Gradle через плагин kotlin-multiplatform.
Основы настройки проекта, совместимого с IDE и Gradle, мы рассмотрели в руководстве Простое приложение Kotlin/Native. Пожалуйста, ознакомьтесь с ним, если вам нужны подробные начальные шаги и инструкции по созданию нового проекта Kotlin/Native и открытию его в IntelliJ IDEA. В этом руководстве мы рассмотрим расширенные аспекты взаимодействия с C, связанные с Kotlin/Native и многоплатформенными сборками с помощью Gradle.
Сначала создайте папку проекта. Все пути в этом руководстве будут относительными к этой папке. Иногда нужно создать отсутствующие каталоги перед добавлением новых файлов.
Используйте следующий файл сборки Gradle:
plugins {
kotlin("multiplatform") version "1.8.0"
}
repositories {
mavenCentral()
}
kotlin {
linuxX64("native") { // on Linux
// macosX64("native") { // on x86_64 macOS
// macosArm64("native") { // on Apple Silicon macOS
// mingwX64("native") { // on Windows
binaries {
sharedLib {
baseName = "native" // on Linux and macOS
// baseName = "libnative" // on Windows
}
}
}
}
tasks.wrapper {
gradleVersion = "7.3"
distributionType = Wrapper.DistributionType.ALL
}
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.8.0'
}
repositories {
mavenCentral()
}
kotlin {
linuxX64("native") { // on Linux
// macosX64("native") { // on x86_64 macOS
// macosArm64("native") { // on Apple Silicon macOS
// mingwX64("native") { // on Windows
binaries {
sharedLib {
baseName = "native" // on Linux and macOS
// baseName = "libnative" // on Windows
}
}
}
}
wrapper {
gradleVersion = "7.3"
distributionType = "ALL"
}
Переместите файлы исходного кода в папку src/nativeMain/kotlin в проекте. Это стандартный путь к исходному коду, когда используется плагин kotlin-multiplatform. Используйте следующий блок, чтобы настроить проект на генерацию динамической или общей библиотеки:
binaries {
sharedLib {
baseName = "native" // on Linux and macOS
// baseName = "libnative" // on Windows
}
}
libnative используется как имя библиотеки, префикс имени сгенерированного заголовочного файла. Также он служит префиксом для всех объявлений в заголовочном файле.
Теперь вы можете открыть проект в IntelliJ IDEA, чтобы увидеть, как исправить пример проекта. При этом мы рассмотрим, как C-функции отображаются в объявлениях Kotlin/Native.
Запустите задачу сборки linkNative Gradle в IDE или вызовите следующую команду в консоли:
./gradlew linkNative
Сборка генерирует следующие файлы в папке build/bin/native/debugShared, в зависимости от ОС:
macOS:
libnative_api.hиlibnative.dylibLinux:
libnative_api.hиlibnative.soWindows:
libnative_api.h,libnative_symbols.defиlibnative.dll
Те же правила использует компилятор Kotlin/Native для генерации файла .h для всех платформ.
Давайте посмотрим на C API нашей библиотеки Kotlin.
Файл сгенерированных заголовков
В libnative_api.h, вы найдете следующий код. Давайте обсудим код по частям, чтобы его было легче понять.
Первая часть содержит стандартный заголовок и подвал C/C++:
#ifndef KONAN_DEMO_H
#define KONAN_DEMO_H
#ifdef __cplusplus
extern "C" {
#endif
/// THE REST OF THE GENERATED CODE GOES HERE
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* KONAN_DEMO_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 void* libnative_KNativePtr;
Kotlin использует префикс libnative_ для всех объявлений в созданном файле libnative_api.h. Давайте представим отображение типов более понятным образом:
Kotlin определение |
Тип C |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Часть определений демонстрирует, как примитивные типы Kotlin отображаются в примитивные типы C. Обратное отображение описано в руководстве Преобразование примитивных типов данных из C.
Следующая часть файла libnative_api.h содержит определения типов, используемых в библиотеке:
struct libnative_KType;
typedef struct libnative_KType libnative_KType;
typedef struct {
libnative_KNativePtr pinned;
} libnative_kref_example_Object;
typedef struct {
libnative_KNativePtr pinned;
} libnative_kref_example_Clazz;
Синтаксис typedef struct { .. } TYPE_NAME используется в языке C для объявления структуры. Эта тема на Stackoverflow предоставляет больше объяснений этого шаблона.
Как видно из этих определений, объект Kotlin Object отображается в libnative_kref_example_Object, а Clazz отображается в libnative_kref_example_Clazz. Обе структуры содержат только поле pinned со указателем, тип поля libnative_KNativePtr определен как void* выше.
В C нет поддержки пространств имен, поэтому компилятор Kotlin/Native генерирует длинные имена, чтобы избежать возможных конфликтов с другими символами в существующем нативном проекте.
Значительная часть определений находится в файле libnative_api.h. Он включает определение мира нашей библиотеки Kotlin/Native:
typedef struct {
/* Service functions. */
void (*DisposeStablePointer)(libnative_KNativePtr ptr);
void (*DisposeString)(const char* string);
libnative_KBoolean (*IsInstance)(libnative_KNativePtr ref, const libnative_KType* type);
/* User functions. */
struct {
struct {
struct {
void (*forIntegers)(libnative_KByte b, libnative_KShort s, libnative_KUInt i, libnative_KLong l);
void (*forFloats)(libnative_KFloat f, libnative_KDouble d);
const char* (*strings)(const char* str);
const char* (*get_globalString)();
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;
} example;
} root;
} kotlin;
} libnative_ExportedSymbols;
Код использует анонимные объявления структур. Код struct { .. } foo объявляет поле во внешней структуре того анонимного типа структуры, типа без имени.
C также не поддерживает объекты. Для имитации семантики объектов используются указатели на функции. Указатель на функцию объявляется следующим образом RETURN_TYPE (* FIELD_NAME)(PARAMETERS). Его сложно читать, но мы должны увидеть поля указателей на функции в структурах выше.
Функции времени выполнения
Код читается следующим образом. У вас есть структура libnative_ExportedSymbols, которая определяет все функции, предоставляемые Kotlin/Native и нашей библиотекой. Она активно использует вложенные анонимные структуры для имитации пакетов. Префикс libnative_ происходит от имени библиотеки.
Структура libnative_ExportedSymbols содержит несколько вспомогательных функций:
void (*DisposeStablePointer)(libnative_KNativePtr ptr); void (*DisposeString)(const char* string); libnative_KBoolean (*IsInstance)(libnative_KNativePtr ref, const libnative_KType* type);
Эти функции работают с объектами Kotlin/Native. Вызовите DisposeStablePointer для освобождения объекта Kotlin и DisposeString для освобождения строки Kotlin, которая имеет тип char* в C. Можно использовать функцию IsInstance для проверки, является ли тип Kotlin или libnative_KNativePtr экземпляром другого типа. Фактический набор операций зависит от фактического использования.
Kotlin/Native имеет сборку мусора, но она не помогает нам работать с объектами Kotlin из языка C. Kotlin/Native имеет взаимодействие с Objective-C и Swift и интегрируется с их счетчиками ссылок. Статья документации Взаимодействие с Objective-C содержит более подробную информацию об этом. Также есть руководство Kotlin/Native как фреймворк Apple.
Функции вашей библиотеки
Давайте посмотрим на поле kotlin.root.example, оно имитирует структуру пакетов нашего кода Kotlin с префиксом kotlin.root..
Есть поле kotlin.root.example.Clazz, которое представляет Clazz из Kotlin. К Clazz#memberFunction можно получить доступ с помощью поля memberFunction. Единственное отличие в том, что memberFunction принимает ссылку на this в качестве первого параметра. Язык C не поддерживает объекты, и это причина, по которой необходимо явно передать указатель на this.
В поле Clazz (также известном как kotlin.root.example.Clazz.Clazz) есть конструктор, который является функцией-конструктором для создания экземпляра Clazz.
Kotlin object Object доступен как kotlin.root.example.Object. Существует функция _instance для получения единственного экземпляра объекта.
Свойства переводятся в функции. Префикс get_ используется для именования функций-геттеров, а префикс set_ - для функций-сеттеров. Например, только для чтения свойство globalString из Kotlin преобразуется в функцию get_globalString в C.
Глобальные функции forInts, forFloats, или strings преобразуются в указатели на функции в анонимной структуре kotlin.root.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;
}
Компиляция и запуск примера на Linux и macOS
На macOS 10.13 с Xcode скомпилируйте код C и свяжите его с динамической библиотекой с помощью следующей команды:
clang main.c libnative.dylib
На Linux вызовите аналогичную команду:
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.
В этом примере вы будете использовать консоль x64 Native Tools Command Prompt <VERSION>. Вы найдете ярлык для открытия консоли в меню Пуск. Она поставляется с пакетом Microsoft Visual Studio.
На Windows динамические библиотеки включаются либо через сгенерированную статическую библиотеку-обертку, либо с помощью ручного кода, который работает с функциями LoadLibrary или аналогичными функциями Win32API. Следуйте первому варианту и сгенерируйте статическую библиотеку-обертку для libnative.dll как описано ниже.
Вызовите lib.exe из инструментальной цепочки для генерации статической библиотеки-обертки libnative.lib, которая автоматизирует использование DLL из кода:
lib /def:libnative_symbols.def /out:libnative.lib
Теперь вы готовы скомпилировать наш main.c в исполняемый файл. Включите сгенерированную libnative.lib в команду сборки и запустите:
cl.exe main.c libnative.lib
Команда создает файл main.exe, который вы можете запустить.
Следующие шаги
Динамические библиотеки — основной способ использования кода Kotlin в существующих программах. Вы можете использовать их для совместного использования кода с многими платформами или языками, включая JVM, Python, iOS, Android и другие.
Kotlin/Native также имеет тесную интеграцию с Objective-C и Swift. Она описана в руководстве Kotlin/Native как фреймворк Apple.
© 2010–2023 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