Spec-Zone.ru › Kotlin 2

Отображение типов struct и union из C – руководство

Импорт библиотек C находится в бета-версии. Все объявления Kotlin, сгенерированные инструментом cinterop из библиотек C, должны иметь аннотацию @ExperimentalForeignApi.

Для библиотек нативной платформы, поставляемых с Kotlin/Native (например, Foundation, UIKit и POSIX), явно разрешать использование нужно только для некоторых API.

Рассмотрим, какие объявления struct и union из C доступны в Kotlin, а также изучим расширенные сценарии взаимодействия Kotlin/Native с C и сборки Gradle для мультиплатформенных проектов.

В этом руководстве вы узнаете:

  • Как отображаются типы struct и union

  • Как использовать типы struct и union из Kotlin

Отображение типов 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, и обновим проект:

  1. В 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*/)
    }
    
  2. Чтобы избежать ошибок компилятора, добавьте поддержку взаимодействия с 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()
            }
        }
    }
    
  3. Воспользуйтесь командой 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, где описаны более сложные сценарии.

1 сентября 2026 г.
Отображение примитивных типов данных из 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API