Kotlin/Native в качестве динамической библиотеки – учебник
Узнайте, как использовать код Kotlin/Native из существующих приложений или библиотек нативных языках. Для этого необходимо скомпилировать код Kotlin в динамическую библиотеку, .so, .dylib, и .dll.
Kotlin/Native также имеет тесную интеграцию с технологиями Apple. В учебнике Kotlin/Native как фреймворк Apple объясняется, как скомпилировать код 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-interop, связанные с Kotlin/Native и многоплатформенными сборками с Gradle.
Сначала создайте папку проекта. Все пути в этом руководстве будут относительными к этой папке. Иногда недостающие каталоги нужно создать, прежде чем добавлять новые файлы.
Используйте следующий build.gradle(.kts) файл Gradle:
plugins {
kotlin("multiplatform") version "1.9.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.6"
distributionType = Wrapper.DistributionType.ALL
}
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.9.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.6"
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