Spec-Zone.ru › Kotlin 1.6

Создание приложения с использованием C Interop и libcurl – учебник

В этом руководстве показано, как использовать IntelliJ IDEA для создания командной строки приложения. Вы узнаете, как создать простой HTTP-клиент, который может работать в нативных приложениях на указанных платформах с использованием Kotlin/Native и библиотеки libcurl.

Вы получите исполняемый файл командной строки, который можно запустить на macOS и Linux и выполнять простые HTTP-GET запросы.

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

Для начала установите последнюю версию IntelliJ IDEA. Этот учебник подходит как для IntelliJ IDEA Community Edition, так и для IntelliJ IDEA Ultimate.

Создание проекта Kotlin/Native

  1. В IntelliJ IDEA выберите Файл | Создать | Проект.

  2. В левой панели выберите Kotlin | Приложение Native.

  3. Укажите имя и выберите папку, куда будет сохранен ваш проект.

    New project. Native application in IntelliJ IDEA
  4. Нажмите Далее, а затем Готово.

IntelliJ IDEA создаст новый проект с необходимыми файлами и папками. Важно понимать, что приложение, написанное на Kotlin/Native, может быть ориентировано на разные платформы, если код не содержит платформно-специфических требований. Ваш код помещен в папку NativeMain с соответствующими NativeTest. Для этого руководства сохраните структуру папок как есть.

Native application project structure

Вместе с новым проектом генерируется файл build.gradle(.kts). Обратите особое внимание на следующее в файле сборки:

kotlin {
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }

    nativeTarget.apply {
        binaries {
            executable {
                entryPoint = "main"
            }
        }
    }
}

kotlin {
    def hostOs = System.getProperty("os.name")
    def isMingwX64 = hostOs.startsWith("Windows")
    def nativeTarget
        if (hostOs == "Mac OS X") nativeTarget = macosX64('native')
        else if (hostOs == "Linux") nativeTarget = linuxX64("native")
        else if (isMingwX64) nativeTarget = mingwX64("native")
        else throw new FileNotFoundException("Host OS is not supported in Kotlin/Native.")

    nativeTarget.with {
        binaries {
            executable {
                entryPoint = 'main'
            }
        }
    }
}
  • Целевые платформы определены с использованием macOSX64, linuxX64, и mingwX64 для macOS, Linux и Windows. Полный список поддерживаемых платформ см. в обзоре Kotlin Native.

  • Сам entry определяет набор свойств, указывающих на способ генерации двоичного файла и точку входа приложения. Эти значения можно оставить по умолчанию.

  • Взаимодействие с C настроено как дополнительный шаг в процессе сборки. По умолчанию все символы из C импортируются в пакет interop. Возможно, потребуется импортировать весь пакет в файлы .kt. Узнайте больше о настройке этого процесса.

Создание файла определения

При написании нативных приложений вам часто требуется доступ к определенным функциональным возможностям, которые не включены в стандартную библиотеку Kotlin, такие как выполнение HTTP-запросов, чтение и запись на диск и т. д.

Kotlin/Native помогает использовать стандартные C-библиотеки, открывая целый экосистему возможностей, которые существуют практически для всего, что вам может понадобиться. Kotlin/Native уже поставляется с набором предварительно скомпилированных платформенных библиотек, которые предоставляют дополнительную общую функциональность для стандартной библиотеки.

Идеальный сценарий для взаимодействия – вызов функций C как функций Kotlin, следуя тому же сигнатуре и соглашениям. Именно для этого полезен инструмент cinterop. Он принимает C-библиотеку и генерирует соответствующие Kotlin-связки, так что библиотеку можно использовать как Kotlin-код.

Для генерации этих связей создайте файл определения .def библиотеки, содержащий информацию о необходимых заголовочных файлах. В этом приложении вам понадобится библиотека libcurl для выполнения HTTP-запросов. Для создания файла определения:

  1. Выберите папку src и создайте новую папку с помощью Файл | Создать | Папку.

  2. Назовите новую папку nativeInterop/cinterop. Это стандартное соглашение для расположения заголовочных файлов, хотя его можно переопределить в файле build.gradle при использовании другого расположения.

  3. Выберите эту новую подпапку и создайте новый файл libcurl.def с помощью Файл | Создать | Файл.

  4. Обновите файл следующим кодом:

    headers = curl/curl.h
    headerFilter = curl/*
    
    compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu
    linkerOpts.osx = -L/opt/local/lib -L/usr/local/opt/curl/lib -lcurl
    linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl
    
    • headers – список заголовочных файлов для генерации Kotlin-заглушек. Вы можете добавить несколько файлов в эту запись, разделяя их \ в новой строке. В данном случае это только curl.h. Ссылаемые файлы должны быть относительными к папке, где находится файл определения, или быть доступными в системном пути (в данном случае это /usr/include/curl).

    • headerFilter показывает, что именно включается. В C все заголовочные файлы также включаются, когда один файл ссылается на другой с помощью директивы #include. Иногда это не требуется, и вы можете использовать параметр с использованием шаблонов glob для точной настройки.

      headerFilter – необязательный аргумент, который используется в основном, когда библиотека установлена как системная библиотека. Не нужно загружать внешние зависимости (например, системные заголовочные файлы stdint.h в библиотеку взаимодействия). Это может быть важно для оптимизации размера библиотеки и предотвращения конфликтов между системной библиотекой и средой компиляции Kotlin/Native.

    • Следующие строки относятся к предоставлению параметров компоновщика и компилятора, которые могут различаться в зависимости от различных целевых платформ. В данном случае это macOS (суффикс .osx) и Linux (суффикс .linux). Параметры без суффикса также возможны (например, linkerOpts=) и применяются ко всем платформам.

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

Для работы примера на вашем компьютере должны быть бинарные файлы библиотеки curl. На macOS и Linux они обычно включены. На Windows вы можете скомпилировать их из исходных кодов источников (вам понадобится Visual Studio или командные инструменты Windows SDK). Более подробную информацию см. в соответствующей статье блога. В качестве альтернативы вы можете рассмотреть бинарный файл MinGW/MSYS2 curl.

Добавление взаимодействия в процесс сборки

Для использования заголовочных файлов убедитесь, что они генерируются в процессе сборки. Для этого добавьте следующую запись в файл build.gradle(.kts):

nativeTarget.apply {
    compilations.getByName("main") {    // NL
        cinterops {                     // NL
            val libcurl by creating     // NL
        }                               // NL
    }                                   // NL
    binaries {
        executable {
            entryPoint = "main"
        }
    }
}
nativeTarget.with {
    compilations.main { // NL
        cinterops {     // NL
            libcurl     // NL
        }               // NL
    }                   // NL
    binaries {
        executable {
            entryPoint = 'main'
        }
    }
}

Новые строки помечены // NL. Сначала добавляется cinterops, а затем запись для каждого файла def. По умолчанию используется имя файла. Можно переопределить это с помощью дополнительных параметров:

val libcurl by creating {
    defFile(project.file("src/nativeInterop/cinterop/libcurl.def"))
    packageName("com.jetbrains.handson.http")
    compilerOpts("-I/path")
    includeDirs.allHeaders("path")
}
libcurl {
    defFile project.file("src/nativeInterop/cinterop/libcurl.def")
    packageName 'com.jetbrains.handson.http'
    compilerOpts '-I/path'
    includeDirs.allHeaders("path")
}

См. раздел Взаимодействие с C для получения дополнительной информации о доступных параметрах.

END_OF_DOCUMENT_MARKER ```

Написание кода приложения

Теперь у вас есть библиотека и соответствующие Kotlin-заглушки, и вы можете использовать их в своём приложении. Для этого урока преобразуйте пример simple.c в Kotlin.

В папке src/nativeMain/kotlin/ обновите файл Main.kt следующим кодом:

import kotlinx.cinterop.*
import libcurl.*

fun main(args: Array<String>) {
    val curl = curl_easy_init()
    if (curl != null) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://example.com")
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)
        val res = curl_easy_perform(curl)
        if (res != CURLE_OK) {
            println("curl_easy_perform() failed ${curl_easy_strerror(res)?.toKString()}")
        }
        curl_easy_cleanup(curl)
    }
}

Как видите, явные объявления переменных устранены в Kotlin-версии, но всё остальное практически такое же, как и в C-версии. Все ожидаемые вызовы из библиотеки libcurl доступны в её Kotlin-эквиваленте.

Это буквальный перевод строка за строкой. Вы также можете написать это более idioматичным для Kotlin способом.

Компиляция и запуск приложения

  1. Скомпилируйте приложение. Для этого выполните следующую команду в терминале:

    ./gradlew runDebugExecutableNative
    

    В этом случае сгенерированная часть cinterop подразумевается включённой в сборку.

  2. Если ошибок при компиляции не возникло, нажмите зелёную иконку Запуск в строке слева от метода main() или используйте сочетание клавиш Alt+Enter для вызова меню запуска в IntelliJ IDEA.

    IntelliJ IDEA откроет вкладку Запуск и отобразит вывод — содержимое https://example.com.

    Application output with HTML-code

Вы можете увидеть фактический вывод, так как вызов curl_easy_perform выводит результат в стандартный вывод. Вы можете скрыть его с помощью curl_easy_setopt.

Полный код можно найти здесь.

Что дальше?

Для полного примера использования libcurl, обратитесь к примеру libcurl в проекте Kotlin/Native, который демонстрирует абстрагирование кода в Kotlin-классы и вывод заголовков. Также там показано, как упростить шаги, объединив их в скрипт командной строки или в build-скрипт Gradle.

Последнее изменение: 07 апреля 2022
Преобразование строк из C – руководство Взаимодействие со Swift/Objective-C

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/native-app-with-c-and-libcurl.html

Spec-Zone.ru

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