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