Сопоставление строк из C — руководство
В заключительной части серии рассмотрим, как работать со строками C в Kotlin/Native.
В этом руководстве вы узнаете, как:
Работа со строками C
В C нет специального типа строк. Сигнатуры методов или документация помогут определить, представляет ли данный char * строку C в конкретном контексте.
Строки в языке C завершаются нулевым символом, поэтому в конец последовательности байтов добавляется завершающий ноль \0, обозначающий конец строки. Обычно используются строки в кодировке UTF-8. В кодировке UTF-8 используются символы переменной ширины, и она обратно совместима с ASCII. По умолчанию Kotlin/Native использует кодировку UTF-8.
Чтобы понять, как строки сопоставляются между Kotlin и C, сначала создайте заголовки библиотеки. В первой части серии вы уже создали библиотеку C с необходимыми файлами. Для этого шага:
-
Обновите файл
lib.h, добавив следующие объявления функций для работы со строками C:#ifndef LIB2_H_INCLUDED #define LIB2_H_INCLUDED void pass_string(char* str); char* return_string(); int copy_string(char* str, int size); #endif
В этом примере показаны распространённые способы передачи строки в языке C или её получения. Аккуратно обработайте возвращаемое значение функции
return_string(). Используйте правильную функциюfree()для освобождения возвращённогоchar*. -
Обновите объявления в файле
interop.defпосле разделителя---:--- void pass_string(char* str) { } char* return_string() { return "C string"; } int copy_string(char* str, int size) { *str++ = 'C'; *str++ = ' '; *str++ = 'K'; *str++ = '/'; *str++ = 'N'; *str++ = 0; return 0; }
Файл interop.def содержит всё необходимое для компиляции и запуска приложения или его открытия в IDE.
Изучение сгенерированных API Kotlin для библиотеки C
Посмотрим, как объявления строк C сопоставляются с Kotlin/Native:
-
В
src/nativeMain/kotlinобновите файлhello.ktиз предыдущего руководства, добавив следующее содержимое:import interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") pass_string(/*fix me*/) val useMe = return_string() val useMe2 = copy_string(/*fix me*/) } -
Используйте команду IntelliJ IDEA «Перейти к объявлению» (Cmd + B/Ctrl + B), чтобы перейти к следующему сгенерированному API для функций C:
fun pass_string(str: kotlinx.cinterop.CValuesRef<kotlinx.cinterop.ByteVarOf<kotlin.Byte> /* from: kotlinx.cinterop.ByteVar */>?) fun return_string(): kotlinx.cinterop.CPointer<kotlinx.cinterop.ByteVarOf<kotlin.Byte> /* from: kotlinx.cinterop.ByteVar */>? fun copy_string(str: kotlinx.cinterop.CValuesRef<kotlinx.cinterop.ByteVarOf<kotlin.Byte> /* from: kotlinx.cinterop.ByteVar */>?, size: kotlin.Int): kotlin.Int
Эти объявления просты. В Kotlin указатели C char * сопоставляются с str: CValuesRef<ByteVarOf>? для параметров и с CPointer<ByteVarOf>? для типов возвращаемых значений. Тип char в Kotlin представлен как kotlin.Byte, поскольку обычно это 8-битовое значение со знаком.
В сгенерированных объявлениях Kotlin str определён как CValuesRef<ByteVarOf<Byte>>?. Поскольку этот тип допускает значение null, в качестве аргумента можно передать null.
Передача строк Kotlin в C
Попробуем использовать API из Kotlin. Сначала вызовите функцию pass_string():
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.cstr
@OptIn(ExperimentalForeignApi::class)
fun passStringToC() {
val str = "This is a Kotlin string"
pass_string(str.cstr)
}
Передать строку Kotlin в C просто благодаря String.cstr свойству-расширению. Для случаев с символами UTF-16 также есть свойство String.wcstr.
Чтение строк C в Kotlin
Теперь получите возвращаемое значение char * из функции return_string() и преобразуйте его в строку Kotlin:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString
@OptIn(ExperimentalForeignApi::class)
fun passStringToC() {
val stringFromC = return_string()?.toKString()
println("Returned from C: $stringFromC")
}
Здесь функция-расширение .toKString() преобразует строку C, возвращённую функцией return_string(), в строку Kotlin.
В Kotlin предусмотрено несколько функций-расширений для преобразования строк C char * в строки Kotlin — в зависимости от кодировки:
fun CPointer<ByteVarOf<Byte>>.toKString(): String // Standard function for UTF-8 strings fun CPointer<ByteVarOf<Byte>>.toKStringFromUtf8(): String // Explicitly converts UTF-8 strings fun CPointer<ShortVarOf<Short>>.toKStringFromUtf16(): String // Converts UTF-16 encoded strings fun CPointer<IntVarOf<Int>>.toKStringFromUtf32(): String // Converts UTF-32 encoded strings
Получение байтов строки C из Kotlin
На этот раз используйте функцию C copy_string(), чтобы записать строку C в заданный буфер. Она принимает два аргумента: указатель на область памяти, куда следует записать строку, и допустимый размер буфера.
Функция также должна возвращать значение, указывающее, успешно ли она выполнилась. Предположим, что 0 означает успех и что переданный буфер достаточно велик:
import interop.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
@OptIn(ExperimentalForeignApi::class)
fun sendString() {
val buf = ByteArray(255)
buf.usePinned { pinned ->
if (copy_string(pinned.addressOf(0), buf.size - 1) != 0) {
throw Error("Failed to read string from C")
}
}
val copiedStringFromC = buf.decodeToString()
println("Message from C: $copiedStringFromC")
}
Сначала в функцию C передаётся нативный указатель. Функция-расширение .usePinned() временно закрепляет нативный адрес в памяти байтового массива. Функция C заполняет байтовый массив данными. Другая функция-расширение, ByteArray.decodeToString(), преобразует байтовый массив в строку Kotlin, предполагая кодировку UTF-8.
Обновление кода Kotlin
Теперь, когда вы знаете, как использовать объявления C в коде Kotlin, попробуйте применить их в своём проекте. Код в итоговом файле hello.kt может выглядеть так:
import interop.*
import kotlinx.cinterop.*
@OptIn(ExperimentalForeignApi::class)
fun main() {
println("Hello Kotlin/Native!")
val str = "This is a Kotlin string"
pass_string(str.cstr)
val useMe = return_string()?.toKString() ?: error("null pointer returned")
println(useMe)
val copyFromC = ByteArray(255).usePinned { pinned ->
val useMe2 = copy_string(pinned.addressOf(0), pinned.get().size - 1)
if (useMe2 != 0) throw Error("Failed to read a string from C")
pinned.get().decodeToString()
}
println(copyFromC)
}
Чтобы убедиться, что всё работает как ожидается, запустите задачу Gradle runDebugExecutable<YourTargetName> в IDE или выполните в терминале консольную команду, например:
./gradlew runDebugExecutableMacosArm64
Что дальше
Подробнее см. в документации «Взаимодействие с 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-strings-from-c.html