Spec-Zone.ru › Kotlin 1.8

Преобразование строк из C – учебник

Это последний учебник в серии. Первый учебник в серии – Преобразование примитивных типов данных из C. Также есть учебники Преобразование структур и объединений из C и Преобразование указателей на функции из C.

В этом учебнике вы узнаете, как работать со строками C в Kotlin/Native. Вы научитесь:

  • Передавать Kotlin-строку в C

  • Читать C-строку в Kotlin

  • Получать байты C-строки в Kotlin-строку

Работа со строками C

В языке C нет отдельного типа для строк. Разработчик узнает из сигнатуры метода или документации, является ли данная char * C-строкой в контексте. Строки в языке C являются нуль-терминированными, заключительный нулевой символ \0 добавляется в конец последовательности байтов для обозначения окончания строки. Обычно используются строки, закодированные в UTF-8. Кодировка UTF-8 использует символы переменной длины и обратно совместима с ASCII. Kotlin/Native по умолчанию использует кодировку UTF-8.

Лучший способ понять сопоставление между C и Kotlin — это попробовать это на небольшом примере. Создайте небольшие заголовки библиотеки для этого. Сначала создайте файл 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. В общем случае лучше убедиться, что вы используете правильную функцию для освобождения возвращенного char* с помощью правильного вызова функции free(..).

Kotlin/Native поставляется с инструментом cinterop; инструмент генерирует привязки между языком C и Kotlin. Он использует файл .def для указания C-библиотеки для импорта. Более подробные сведения об этом находятся в учебнике Взаимодействие с C-библиотеками. Самый быстрый способ попробовать сопоставление C API — это разместить все объявления C в файле interop.def без создания каких-либо файлов .h или .c. Затем поместите объявления C в файл interop.def после специальной разделительной строки ---:

headers = lib.h
---

void pass_string(char* str) {
}

char* return_string() {
  return "C stirng";
}

int copy_string(char* str, int size) {
  *str++ = 'C';
  *str++ = ' ';
  *str++ = 'K';
  *str++ = '/';
  *str++ = 'N';
  *str++ = 0;
  return 0;
}

Файла interop.def достаточно для компиляции и запуска приложения или его открытия в IDE. Теперь пришло время создать файлы проекта, открыть проект в IntelliJ IDEA и запустить его.

Просмотр сгенерированных Kotlin API для C-библиотеки

Хотя можно использовать командную строку, либо напрямую, либо комбинируя её со скриптовым файлом (например, файлом .sh или .bat), такой подход не масштабируется для крупных проектов с сотнями файлов и библиотек. В таком случае лучше использовать Kotlin/Native-компилятор со средствами сборки, поскольку это помогает скачивать и кэшировать двоичные файлы и библиотеки Kotlin/Native-компилятора с транзитивными зависимостями и запускать компилятор и тесты. Kotlin/Native может использовать систему сборки Gradle через плагин kotlin-multiplatform.

Мы рассмотрели основы настройки проекта, совместимого с IDE, с Gradle в учебнике Базовое Kotlin/Native-приложение. Пожалуйста, ознакомьтесь с ним, если вы хотите получить подробные начальные шаги и инструкции по началу нового Kotlin/Native-проекта и его открытию в IntelliJ IDEA. В этом учебнике мы рассмотрим расширенное использование C-взаимодействия в Kotlin/Native и сборки multiplatform с Gradle.

Сначала создайте папку проекта. Все пути в этом учебнике будут относительными по отношению к этой папке. Иногда необходимо предварительно создать отсутствующие каталоги, прежде чем можно будет добавить новые файлы.

Используйте следующий файл сборки Gradle build.gradle(.kts):

plugins {
    kotlin("multiplatform") version "1.8.0"
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64("native") { // on Linux
  // macosX64("native") { // on x86_64 macOS
  // macosArm64("native") { // on Apple Silicon macOS
  // mingwX64("native") { // on Windows
    val main by compilations.getting
    val interop by main.cinterops.creating
    
    binaries {
      executable()
    }
  }
}

tasks.wrapper {
  gradleVersion = "7.3"
  distributionType = Wrapper.DistributionType.BIN
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.8.0'
}

repositories {
    mavenCentral()
}

kotlin {
  linuxX64('native') { // on Linux
  // macosX64("native") { // on x86_64 macOS
  // macosArm64("native") { // on Apple Silicon macOS
  // mingwX64('native') { // on Windows
    compilations.main.cinterops {
      interop 
    }
    
    binaries {
      executable()
    }
  }
}

wrapper {
  gradleVersion = '7.3'
  distributionType = 'BIN'
}

Файл проекта настраивает C-взаимодействие как дополнительный шаг сборки. Давайте переместим файл interop.def в каталог src/nativeInterop/cinterop. Gradle рекомендует использовать соглашения вместо конфигураций, например, ожидается, что исходные файлы будут находиться в папке src/nativeMain/kotlin. По умолчанию все символы из C импортируются в пакет interop, вы можете импортировать весь пакет в файлы .kt. Ознакомьтесь с документацией плагина kotlin-multiplatform, чтобы узнать обо всех различных способах его настройки.

Давайте создадим файл-заглушку src/nativeMain/kotlin/hello.kt со следующим содержимым, чтобы увидеть, как объявления C-строк видны из Kotlin:

import interop.*

fun main() {
  println("Hello Kotlin/Native!")
  
  pass_string(/*fix me*/)
  val useMe = return_string()
  val useMe2 = copy_string(/*fix me*/)
}

Теперь вы готовы открыть проект в IntelliJ IDEA и увидеть, как исправить пример проекта. Делая это, посмотрите, как C-строки сопоставляются в Kotlin/Native.

Строки в Kotlin

С помощью IntelliJ IDEA «Перейти к объявлению» или сообщений компилятора вы видите следующий сгенерированный API для C-функций:

fun pass_string(str: CValuesRef<ByteVar /* = ByteVarOf<Byte> */>?)
fun return_string(): CPointer<ByteVar /* = ByteVarOf<Byte> */>?
fun copy_string(str: CValuesRef<ByteVar /* = ByteVarOf<Byte> */>?, size: Int): Int

Эти объявления выглядят понятно. Все указатели char * преобразуются в str: CValuesRef<ByteVar>? для параметров и в CPointer<ByteVar>? в возвращаемых типах. Kotlin преобразует тип char в тип kotlin.Byte, так как это обычно значение с 8-битовым знаком.

В сгенерированных Kotlin-объявлениях вы видите, что str представлен как CValuesRef<ByteVar/>?. Тип является необязательным, и вы можете просто передать Kotlin null в качестве значения параметра.

Передача Kotlin-строки в C

Давайте попробуем использовать API из Kotlin. Сначала вызовите pass_string:

fun passStringToC() {
  val str = "this is a Kotlin String"
  pass_string(str.cstr)
}

Передача Kotlin-строки в C проста благодаря тому, что существует String.cstr расширяющее свойство в Kotlin для этого. Также есть String.wcstr для случаев, когда вам нужны UTF-16 широкие символы.

Чтение C-строк в Kotlin

В этот раз вы получите возвращаемую char * от функции return_string и преобразуете её в Kotlin-строку. Для этого сделайте следующее в Kotlin:

fun passStringToC() {
  val stringFromC = return_string()?.toKString()
  
  println("Returned from C: $stringFromC")
}

Этот код использует расширяющую функцию toKString() выше. Не пропускайте функцию toString(). У toKString() есть две перегруженные расширяющие функции в Kotlin:

fun CPointer<ByteVar>.toKString(): String
fun CPointer<ShortVar>.toKString(): String

Первая расширяющая функция принимает char * как UTF-8 строку и преобразует её в String. Вторая функция делает то же самое, но для широких UTF-16 строк.

Получение байтов C-строки из Kotlin

На этот раз мы попросим C-функцию записать нам C-строку в заданный буфер. Функция называется copy_string. Она принимает указатель на место записи символов и допустимый размер буфера. Функция возвращает что-то, чтобы указать, удалось ли это или нет. Предположим, что 0 означает успех, и предоставленный буфер был достаточно большим:

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 String, предполагая кодировку UTF-8.

Исправление кода

Теперь вы видели все определения, и пришло время исправить код. Запустите задачу Gradle runDebugExecutableNative в IDE или используйте следующую команду для запуска кода:

./gradlew runDebugExecutableNative

Код в конечном файле hello.kt может выглядеть так:

import interop.*
import kotlinx.cinterop.*

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 string from C")
    pinned.get().decodeToString()
  }

  println(copyFromC)
}

Дальнейшие шаги

Продолжайте изучать другие типы языка C и их представление в Kotlin/Native в наших других учебниках:

  • Преобразование примитивных типов данных из C

  • Преобразование структур и объединений из C

  • Преобразование указателей на функции из C

Документация C Interop охватывает более сложные сценарии взаимодействия.

Последнее изменение: 10 января 2023
Преобразование указателей на функции из C – учебник Создание приложения с использованием C Interop и libcurl – учебник

© 2010–2023 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

Spec-Zone.ru

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