Kotlin Help

Mapping primitive data types from C – tutorial

In this tutorial, you will learn what C data types are visible in Kotlin/Native and vice versa. You will:

Types in C language

What types are there in the C language? Let's take the C data types article from Wikipedia as a basis. There are following types in the C programming language:

  • basic types char, int, float, double with modifiers signed, unsigned, short, long

  • structures, unions, arrays

  • pointers

  • function pointers

There are also more specific types:

  • boolean type (from C99)

  • size_t and ptrdiff_t (also ssize_t)

  • fixed width integer types, such as int32_t or uint64_t (from C99)

There are also the following type qualifiers in the C language: const, volatile, restrict, atomic.

The best way to see what C data types are visible in Kotlin is to try it.

Example C library

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 this example, but may be necessary if you use C++ and overloaded functions. The C++ compatibility thread on Stackoverflow contains more details on this.

For every set of .h files, you will be using the cinterop tool 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 you do not need to create a lib.c file, unless you want to compile and run the example. More details on this are covered in the C interop page. It is enough for the tutorial to create the lib.def file with the following content:

headers = lib.h

You 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. Use that feature to have a runnable example without a need for a C compiler. To implement that, you need to add implementations to the C functions from the lib.h file, and place these functions into a .def file. You 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.

Inspect generated Kotlin APIs for a C library

While it is possible to use the command line, either directly or by combining it with a script file (such as .sh or .bat file), this approach doesn't 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, 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.

Use the following build.gradle(.kts) Gradle build file:

plugins { kotlin("multiplatform") version "2.0.21" } 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 = "8.5" distributionType = Wrapper.DistributionType.BIN }
plugins { id 'org.jetbrains.kotlin.multiplatform' version '2.0.21' } 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 = '8.5' distributionType = 'BIN' }

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, you may want to import the whole package in our .kt files. Check out the Multiplatform Gradle DSL reference to learn about all the different ways you could configure it.

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 you are ready to open the project in IntelliJ IDEA and to see how to fix the example project. While doing that, see how C primitive types are mapped into Kotlin/Native.

Primitive types in kotlin

With the help of IntelliJ IDEA's Go to | Declaration or compiler errors, you see the following generated API for the 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

Fix the code

You've seen all definitions and it is the time to fix the code. Run the runDebugExecutableNative Gradle task in IDE or use the following command to run the code:

./gradlew 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) }

Next steps

Continue to explore more complicated C language types and their representation in Kotlin/Native in the next tutorials:

The C interop documentation covers more advanced scenarios of the interop.

Last modified: 25 September 2024