Отображение типов struct и union из C – руководство
Рассмотрим, какие объявления struct и union из C доступны в Kotlin, а также изучим расширенные сценарии взаимодействия Kotlin/Native с C и сборки Gradle для мультиплатформенных проектов.
В этом руководстве вы узнаете:
Отображение типов struct и union из C
Чтобы понять, как Kotlin отображает типы struct и union, объявим их в C и посмотрим, как они представлены в Kotlin.
В предыдущем руководстве вы уже создали библиотеку C с необходимыми файлами. На этом шаге обновите объявления в файле interop.def после разделителя ---:
---
typedef struct {
int a;
double b;
} MyStruct;
void struct_by_value(MyStruct s) {}
void struct_by_pointer(MyStruct* s) {}
typedef union {
int a;
MyStruct b;
float c;
} MyUnion;
void union_by_value(MyUnion u) {}
void union_by_pointer(MyUnion* u) {}
Файл interop.def содержит всё необходимое для компиляции и запуска приложения, а также для его открытия в IDE.
Изучение сгенерированных API Kotlin для библиотеки C
Посмотрим, как типы struct и union из C отображаются в Kotlin/Native, и обновим проект:
-
В
src/nativeMain/kotlinобновите файлhello.ktиз предыдущего руководства, заменив его содержимое на следующее:import interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") struct_by_value(/* fix me*/) struct_by_pointer(/* fix me*/) union_by_value(/* fix me*/) union_by_pointer(/* fix me*/) } -
Чтобы избежать ошибок компилятора, добавьте поддержку взаимодействия с C в процесс сборки. Для этого обновите файл сборки
build.gradle(.kts)следующим содержимым:kotlin { macosArm64() // macOS on Apple Silicon // linuxArm64() // Linux on ARM64 platforms // linuxX64() // Linux on x86_64 platforms // mingwX64() // on Windows targets.withType<KotlinNativeTarget>().configureEach { val main by compilations.getting val interop by main.cinterops.creating { definitionFile.set(project.file("src/nativeInterop/cinterop/interop.def")) } binaries { executable() } } }kotlin { macosArm64() // Apple Silicon macOS // linuxArm64() // Linux on ARM64 platforms // linuxX64() // Linux on x86_64 platforms // mingwX64() // Windows targets.withType(KotlinNativeTarget).configureEach { compilations.main.cinterops { interop { definitionFile = project.file('src/nativeInterop/cinterop/interop.def') } } binaries { executable() } } } -
Воспользуйтесь командой IntelliJ IDEA Перейти к объявлению (Cmd + B/Ctrl + B), чтобы перейти к следующему сгенерированному API для функций, struct и union из C:
fun struct_by_value(s: kotlinx.cinterop.CValue<interop.MyStruct>) fun struct_by_pointer(s: kotlinx.cinterop.CValuesRef<interop.MyStruct>?) fun union_by_value(u: kotlinx.cinterop.CValue<interop.MyUnion>) fun union_by_pointer(u: kotlinx.cinterop.CValuesRef<interop.MyUnion>?)
С технической точки зрения типы struct и union в Kotlin ничем не отличаются. Инструмент cinterop генерирует типы Kotlin для объявлений struct и union в C.
Сгенерированный API включает полные имена пакетов для CValue<T> и CValuesRef<T>, указывающие на их расположение в kotlinx.cinterop. CValue<T> представляет параметр-структуру, передаваемую по значению, а CValuesRef<T>? используется для передачи указателя на структуру или объединение.
Использование типов struct и union из Kotlin
Использовать типы struct и union из C в Kotlin просто благодаря сгенерированному API. Единственный вопрос — как создавать экземпляры этих типов.
Рассмотрим сгенерированные функции, принимающие MyStruct и MyUnion в качестве параметров. Параметры, передаваемые по значению, представлены как kotlinx.cinterop.CValue<T>, а параметры-указатели используют kotlinx.cinterop.CValuesRef<T>?.
В Kotlin есть удобный API для создания этих типов и работы с ними. Рассмотрим, как использовать его на практике.
Создание CValue<T>
Тип CValue<T> используется для передачи параметров по значению при вызове функции C. Используйте функцию cValue, чтобы создать экземпляр CValue<T>. Для инициализации базового типа C на месте функция принимает лямбда-функцию с получателем. Объявление функции выглядит так:
fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T>
Вот как использовать cValue и передавать параметры по значению:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cValue
@OptIn(ExperimentalForeignApi::class)
fun callValue() {
val cStruct = cValue<MyStruct> {
a = 42
b = 3.14
}
struct_by_value(cStruct)
val cUnion = cValue<MyUnion> {
b.a = 5
b.b = 2.7182
}
union_by_value(cUnion)
}
Создание struct и union как CValuesRef<T>
Тип CValuesRef<T> используется в Kotlin для передачи параметра-указателя функции C. Чтобы выделить MyStruct и MyUnion в нативной памяти, используйте следующую функцию-расширение для типа kotlinx.cinterop.NativePlacement:
fun <reified T : kotlinx.cinterop.CVariable> alloc(): T
NativePlacement представляет нативную память и предоставляет функции, похожие на malloc и free. Существует несколько реализаций NativePlacement:
Глобальная реализация — это
kotlinx.cinterop.nativeHeap, однако после использования необходимо вызватьnativeHeap.free(), чтобы освободить память.-
Более безопасный вариант —
memScoped(): он создаёт кратковременную область памяти, в которой все выделенные участки автоматически освобождаются в конце блока:fun <R> memScoped(block: kotlinx.cinterop.MemScope.() -> R): R
С memScoped() код вызова функций с указателями может выглядеть так:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ptr
@OptIn(ExperimentalForeignApi::class)
fun callRef() {
memScoped {
val cStruct = alloc<MyStruct>()
cStruct.a = 42
cStruct.b = 3.14
struct_by_pointer(cStruct.ptr)
val cUnion = alloc<MyUnion>()
cUnion.b.a = 5
cUnion.b.b = 2.7182
union_by_pointer(cUnion.ptr)
}
}
Здесь свойство-расширение ptr, доступное внутри блока memScoped {}, преобразует экземпляры MyStruct и MyUnion в нативные указатели.
Поскольку память управляется внутри блока memScoped {}, в конце блока она автоматически освобождается. Не используйте указатели за пределами этой области, чтобы не обращаться к освобождённой памяти. Если вам нужно выделить память на более длительный срок (например, для кэширования в библиотеке C), рассмотрите возможность использования Arena() или nativeHeap.
Преобразование между CValue<T> и CValuesRef<T>
Иногда структуру нужно передать по значению при одном вызове функции, а затем передать ту же структуру по ссылке при другом.
Для этого понадобится NativePlacement. Но сначала посмотрим, как CValue<T> преобразуется в указатель:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cValue
import kotlinx.cinterop.memScoped
@OptIn(ExperimentalForeignApi::class)
fun callMix_ref() {
val cStruct = cValue<MyStruct> {
a = 42
b = 3.14
}
memScoped {
struct_by_pointer(cStruct.ptr)
}
}
И здесь свойство-расширение ptr из memScoped {} преобразует экземпляры MyStruct в нативные указатели. Эти указатели действительны только внутри блока memScoped {}.
Чтобы преобразовать указатель обратно в переменную, передаваемую по значению, вызовите функцию-расширение .readValue():
import interop.*
import kotlinx.cinterop.alloc
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.readValue
@OptIn(ExperimentalForeignApi::class)
fun callMix_value() {
memScoped {
val cStruct = alloc<MyStruct>()
cStruct.a = 42
cStruct.b = 3.14
struct_by_value(cStruct.readValue())
}
}
Обновление кода Kotlin
Теперь, когда вы узнали, как использовать объявления C в коде Kotlin, попробуйте применить их в своём проекте. Итоговый код в файле hello.kt может выглядеть так:
import interop.*
import kotlinx.cinterop.alloc
import kotlinx.cinterop.cValue
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.readValue
import kotlinx.cinterop.ExperimentalForeignApi
@OptIn(ExperimentalForeignApi::class)
fun main() {
println("Hello Kotlin/Native!")
val cUnion = cValue<MyUnion> {
b.a = 5
b.b = 2.7182
}
memScoped {
union_by_value(cUnion)
union_by_pointer(cUnion.ptr)
}
memScoped {
val cStruct = alloc<MyStruct> {
a = 42
b = 3.14
}
struct_by_value(cStruct.readValue())
struct_by_pointer(cStruct.ptr)
}
}
Чтобы убедиться, что всё работает как ожидается, запустите задачу Gradle runDebugExecutable<YourTargetName> в IDE или выполните в терминале команду консоли, как в этом примере:
./gradlew runDebugExecutableMacosArm64
Следующий шаг
В следующей части серии вы узнаете, как указатели на функции отображаются между Kotlin и C:
См. также
Подробнее см. в документации Взаимодействие с C, где описаны более сложные сценарии.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/mapping-struct-union-types-from-c.html