Функциональные указатели из C
| Последнее обновление | 15 апреля 2019 |
Это третья публикация в серии. Первый туториал — Преобразование примитивных типов данных из C. Также есть туториалы Преобразование структур и объединений из C и Преобразование строк из C.
В этом туториале мы узнаем, как:
Преобразование типов указателей на функции из C
Лучший способ понять сопоставление между Kotlin и C — это попробовать небольшой пример. Мы объявляем функцию, которая принимает указатель на функцию в качестве параметра, и другую функцию, которая возвращает указатель на функцию.
Kotlin/Native поставляется с инструментом cinterop; этот инструмент генерирует связи между языком C и Kotlin. Он использует файл .def для указания библиотеки C, которую нужно импортировать. Более подробные сведения об этом содержатся в туториале Взаимодействие с библиотеками C.
Самый быстрый способ попробовать сопоставление C API — разместить все объявления C в файле interop.def, не создавая вообще каких-либо файлов .h или .c. Затем поместите объявления C в файл .def после специальной разделительной строки ---:
---
int myFun(int i) {
return i+1;
}
typedef int (*MyFun)(int);
void accept_fun(MyFun f) {
f(42);
}
MyFun supply_fun() {
return myFun;
}
Файл interop.def достаточно для компиляции и запуска приложения или открытия его в IDE. Теперь пришло время создать файлы проекта, открыть проект в IntelliJ IDEA и запустить его.
Просмотр сгенерированных Kotlin API для библиотеки C
Хотя можно использовать командную строку, непосредственно или комбинируя ее со скриптовым файлом (например, sh или bat), следует отметить, что это не масштабируется для больших проектов с сотнями файлов и библиотек. В таком случае лучше использовать компилятор Kotlin/Native с системой сборки, поскольку это поможет загрузить и кэшировать двоичные файлы и библиотеки компилятора Kotlin/Native с транзитивными зависимостями и выполнить компиляцию и тесты. Kotlin/Native может использовать систему сборки Gradle через плагин Gradle kotlin-multiplatform.
Основы настройки совместимого с IDE проекта с Gradle мы рассмотрели в туториале Базовое приложение Kotlin/Native. Пожалуйста, ознакомьтесь с ним, если вы хотите получить подробные начальные шаги и инструкции по запуску нового проекта Kotlin/Native и открытию его в IntelliJ IDEA. В этом туториале мы рассмотрим расширенные аспекты взаимодействия с C, связанные с Kotlin/Native и многоплатформенными сборками с Gradle.
Сначала создадим папку проекта. Все пути в этом туториале будут относительными к этой папке. Иногда необходимо создать отсутствующие директории перед добавлением новых файлов.
Мы будем использовать следующий файл сборки Gradle build.gradle build.gradle.kts с содержимым:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}
repositories {
mavenCentral()
}
kotlin {
macosX64("native") {
compilations.main.cinterops {
interop
}
binaries {
executable()
}
}
}
wrapper {
gradleVersion = "5.3.1"
distributionType = "ALL"
}
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}
repositories {
mavenCentral()
}
kotlin {
linuxX64("native") {
compilations.main.cinterops {
interop
}
binaries {
executable()
}
}
}
wrapper {
gradleVersion = "5.3.1"
distributionType = "ALL"
}
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.21'
}
repositories {
mavenCentral()
}
kotlin {
mingwX64("native") {
compilations.main.cinterops {
interop
}
binaries {
executable()
}
}
}
wrapper {
gradleVersion = "5.3.1"
distributionType = "ALL"
}
plugins {
kotlin("multiplatform") version "1.3.21"
}
repositories {
mavenCentral()
}
kotlin {
macosX64("native") {
val main by compilations.getting
val interop by main.cinterops.creating
binaries {
executable()
}
}
}
tasks.withType<Wrapper> {
gradleVersion = "5.3.1"
distributionType = Wrapper.DistributionType.ALL
}
plugins {
kotlin("multiplatform") version "1.3.21"
}
repositories {
mavenCentral()
}
kotlin {
linuxX64("native") {
val main by compilations.getting
val interop by main.cinterops.creating
binaries {
executable()
}
}
}
tasks.withType<Wrapper> {
gradleVersion = "5.3.1"
distributionType = Wrapper.DistributionType.ALL
}
plugins {
kotlin("multiplatform") version "1.3.21"
}
repositories {
mavenCentral()
}
kotlin {
mingwX64("native") {
val main by compilations.getting
val interop by main.cinterops.creating
binaries {
executable()
}
}
}
tasks.withType<Wrapper> {
gradleVersion = "5.3.1"
distributionType = Wrapper.DistributionType.ALL
}
Подготовленные исходные файлы проекта можно загрузить напрямую с GitHub. GitHub. GitHub. GitHub. GitHub. GitHub.
Файл проекта настраивает взаимодействие с 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!")
accept_fun(/*fix me */)
val useMe = supply_fun()
}
Теперь мы готовы открыть проект в IntelliJ IDEA и увидеть, как исправить пример проекта. При этом мы рассмотрим, как функции C отображаются в объявлениях Kotlin/Native.
Указатели на функции C в Kotlin
С помощью функции Перейти к определению в IntelliJ IDEA или ошибок компилятора мы видим следующие объявления для наших функций C:
fun accept_fun(f: MyFun? /* = CPointer<CFunction<(Int) -> Int>>? */) fun supply_fun(): MyFun? /* = CPointer<CFunction<(Int) -> Int>>? */ fun myFun(i: kotlin.Int): kotlin.Int typealias MyFun = kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlin.Int) -> kotlin.Int>> typealias MyFunVar = kotlinx.cinterop.CPointerVarOf<lib.MyFun>
Мы видим, что наш тип функции из C был преобразован в Kotlin typealias. Он использует тип CPointer<..> для представления параметров-указателей и CFunction<(Int)->Int> для представления сигнатуры функции. Доступна функция-расширение invoke для всех типов CPointer<CFunction<..>, поэтому ее можно вызывать так же, как и любую другую функцию в Kotlin.
Передача функции Kotlin как указателя на функцию C
Пришло время попробовать использовать функции C из нашей программы Kotlin. Давайте вызовем функцию accept_fun и передадим указатель на функцию C в Kotlin лямбда-выражение:
fun myFun() {
accept_fun(staticCFunction<Int, Int> { it + 1 })
}
Мы используем вспомогательную функцию staticCFunction{..} из Kotlin/Native для обертывания лямбда-функции Kotlin в указатель на функцию C. Она позволяет использовать только свободные и не захватывающие лямбда-функции. Например, она не может использовать локальную переменную из функции. Мы можем использовать только глобальные объявления. Бросание исключений из staticCFunction{..} приведет к непредсказуемым побочным эффектам. Важно убедиться, что мы не бросаем какие-либо внезапные исключения из нее.
Использование указателя на функцию C из Kotlin
Следующий шаг — вызов указателя на функцию C из C-указателя, который мы получили из вызова supply_fun():
fun myFun2() {
val functionFromC = supply_fun() ?: error("No function is returned")
functionFromC(42)
}
Kotlin преобразует возвращаемый тип указателя на функцию в объект CPointer<CFunction<..>, который может быть null. Необходимо явно проверить null в первую очередь. Для этого мы используем оператор Elvis. Инструмент cinterop помогает преобразовать указатель на функцию C в легко вызываемый объект в Kotlin. Именно это мы сделали в последней строке.
Исправление кода
Мы рассмотрели все определения, и теперь пришло время исправить и запустить код. Давайте запустим задачу runDebugExecutableNative Gradle в IDE или используйте следующую команду для запуска кода:
./gradlew runDebugExecutableNative
./gradlew runDebugExecutableNative
gradlew.bat runDebugExecutableNative
Код в файле hello.kt может выглядеть следующим образом:
import interop.*
import kotlinx.cinterop.*
fun main() {
println("Hello Kotlin/Native!")
val cFunctionPointer = staticCFunction<Int, Int> { it + 1 }
accept_fun(cFunctionPointer)
val funFromC = supply_fun() ?: error("No function is returned")
funFromC(42)
}
Следующие шаги
В следующих туториалах мы продолжим изучение других типов языка C и их представления в Kotlin/Native:
- Преобразование примитивных типов данных из C
- Преобразование структур и объединений из C
- Преобразование строк из C
Документация по взаимодействию с C C Interop documentation охватывает более сложные сценарии взаимодействия.
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/tutorials/native/mapping-function-pointers-from-c.html