Last Updated | 15 April 2019 |
In this tutorial, we learn what C data types are visible in Kotlin/Native and vice versa. We will:
What types do we have in the C language? Let's first list all of them. I have used the C data types article from Wikipedia as a basis. There are following types in the C programming language:
char, int, float, double
with modifiers signed, unsigned, short, long
There are also more specific types:
size_t
and ptrdiff_t
(also ssize_t
)int32_t
or uint64_t
(from C99)There are also the following type qualifiers in the C language: const
, volatile
, restruct
, atomic
.
The best way to see what C data types are visible in Kotlin is to try it
We create a lib.h
file to see how C functions are mapped into Kotlin:
#ifndef LIB2_H_INCLUDED #define LIB2_H_INCLUDED void ints(char c, short d, int e, long f); void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f); void doubles(float a, double b); #endif
The file is missing the extern "C"
block, which is not needed for our example, but may be necessary if we use C++ and overloaded functions. The C++ compatibility thread contains more details on this.
For every set of .h
files, we will be using the cinterop
C Libraries from Kotlin/Native to generate a Kotlin/Native library, or .klib
. The generated library will bridge calls from Kotlin/Native to C. It includes respective Kotlin declarations for the definitions form the .h
files. It is only necessary to have a .h
file to run the cinterop
tool. And we do not need to create a lib.c
file, unless we want to compile and run the example. More details on this are covered in the C Libraries page. It is enough for the tutorial to create the lib.def
file with the following content:
headers = lib.h
We may include all declarations directly into the .def
file after a ---
separator. It can be helpful to include macros or other C defines into the code generated by the cinterop
tool. Method bodies are compiled and fully included into the binary too. Let's use that feature to have a runnable example without a need for a C compiler. To implement that, we need to add implementations to the C functions from the lib.h
file, and place these functions into a .def
file. We will have the following interop.def
result:
--- void ints(char c, short d, int e, long f) { } void uints(unsigned char c, unsigned short d, unsigned int e, unsigned long f) { } void doubles(float a, double b) { }
The interop.def
file is enough to compile and run the application or open it in an IDE. Now it is time to create project files, open the project in IntelliJ IDEA and run it.
While it is possible to use the command line, either directly or by combining it with a script file (i.e., sh or bat file), we should notice, that it does not scale well for big projects that have hundreds of files and libraries. It is then better to use the Kotlin/Native compiler with a build system, as it helps to download and cache the Kotlin/Native compiler binaries and libraries with transitive dependencies and run the compiler and tests. Kotlin/Native can use the Gradle build system through the kotlin-multiplatform plugin.
We covered the basics of setting up an IDE compatible project with Gradle in the A Basic Kotlin/Native Application tutorial. Please check it out if you are looking for detailed first steps and instructions on how to start a new Kotlin/Native project and open it in IntelliJ IDEA. In this tutorial, we'll look at the advanced C interop related usages of Kotlin/Native and multiplatform builds with Gradle.
First, let's create a project folder. All the paths in this tutorial will be relative to this folder. Sometimes the missing directories will have to be created before any new files can be added.
We'll use the following build.gradle
build.gradle.kts
Gradle build file with the following contents:
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 }
The prepared project sources can be downloaded directly from GitHub. GitHub. GitHub. GitHub. GitHub. GitHub.
The project file configures the C interop as an additional step of the build. Let's move the interop.def
file to the src/nativeInterop/cinterop
directory. Gradle recommends using conventions instead of configurations, for example, the source files are expected to be in the src/nativeMain/kotlin
folder. By default, all the symbols from C are imported to the interop
package, we may want to import the whole package in our .kt
files. Check out the kotlin-multiplatform plugin documentation to learn about all the different ways you could configure it.
Let's create a src/nativeMain/kotlin/hello.kt
stub file with the following content to see how C primitive type declarations are visible from Kotlin:
import interop.* fun main() { println("Hello Kotlin/Native!") ints(/* fix me*/) uints(/* fix me*/) doubles(/* fix me*/) }
Now we are ready to open the project in IntelliJ IDEA and to see how to fix the example project. While doing that, we'll examine how C primitive types are mapped into Kotlin/Native.
With the help of IntelliJ IDEA's Goto Declaration or compiler errors we see the following generated API for our C functions:
fun ints(c: Byte, d: Short, e: Int, f: Long) fun uints(c: UByte, d: UShort, e: UInt, f: ULong) fun doubles(a: Float, b: Double)
C types are mapped in the way we would expect, note that char
type is mapped to kotlin.Byte
as it is usually an 8-bit signed value.
C | Kotlin |
---|---|
char | kotlin.Byte |
unsigned char | kotlin.UByte |
short | kotlin.Short |
unsigned short | kotlin.UShort |
int | kotlin.Int |
unsigned int | kotlin.UInt |
long long | kotlin.Long |
unsigned long long | kotlin.ULong |
float | kotlin.Float |
double | kotlin.Double |
We've seen all definitions and it is the time to fix the code. Let's run the runDebugExecutableNative
Gradle task in IDE or use the following command to run the code:
./gradlew runDebugExecutableNative
./gradlew runDebugExecutableNative
gradlew.bat runDebugExecutableNative
The final code in the hello.kt
file may look like that:
import interop.* fun main() { println("Hello Kotlin/Native!") ints(1, 2, 3, 4) uints(5, 6, 7, 8) doubles(9.0f, 10.0) }
We will continue to explore more complicated C language types and their representation in Kotlin/Native in the next tutorials:
The C Interop documentation documentation covers more advanced scenarios of the interop.
© 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-primitive-data-types-from-c.html