Mapping primitive data types from C – tutorial
Let's explore which C data types are visible in Kotlin/Native and vice versa and examine advanced C interop-related use cases of Kotlin/Native and multiplatform Gradle builds.
In this tutorial, you'll:
You can use the command line to generate a Kotlin library, either directly or with a script file (such as .sh
or .bat
file). However, this approach doesn't scale well for larger projects that have hundreds of files and libraries. Using a build system simplifies the process by downloading and caching the Kotlin/Native compiler binaries and libraries with transitive dependencies, as well as by running the compiler and tests. Kotlin/Native can use the Gradle build system through the Kotlin Multiplatform plugin.
Types in C language
The C programming language has the following data types:
Basic types:
char, int, float, double
with modifierssigned, unsigned, short, long
Structures, unions, arrays
Pointers
Function pointers
There are also more specific types:
Boolean type (from C99)
size_t
andptrdiff_t
(alsossize_t
)Fixed width integer types, such as
int32_t
oruint64_t
(from C99)
There are also the following type qualifiers in the C language: const
, volatile
, restrict
, atomic
.
Let's see which C data types are visible in Kotlin.
Create a C library
In this tutorial, you won't create a lib.c
source file, which is only necessary if you want to compile and run your C library. For this setup, you'll only need a .h
header file that is required for running the cinterop tool.
The cinterop tool generates a Kotlin/Native library (a .klib
file) for each set of .h
files. The generated library helps bridge calls from Kotlin/Native to C. It includes Kotlin declarations that correspond to the definitions from the .h
files.
To create a C library:
Create an empty folder for your future project.
Inside, create a
lib.h
file with the following content 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); #endifThe file doesn't have the
extern "C"
block, which is not needed for this example but may be necessary if you use C++ and overloaded functions. See this Stackoverflow thread for more details.Create the
lib.def
definition file with the following content:headers = lib.hIt can be helpful to include macros or other C definitions in the code generated by the cinterop tool. This way, method bodies are also compiled and fully included in the binary. With this feature, you can create a runnable example without needing a C compiler.
To do that, add implementations to the C functions from the
lib.h
file to a newinterop.def
file after the---
separator:--- 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 provides everything necessary to compile, run, or open the application in an IDE.
Create a Kotlin/Native project
To create project files:
In your project folder, create a
build.gradle(.kts)
Gradle build file with the following content:plugins { kotlin("multiplatform") version "2.1.20" } repositories { mavenCentral() } kotlin { macosArm64("native") { // macOS on Apple Silicon // macosX64("native") { // macOS on x86_64 platforms // linuxArm64("native") { // Linux on ARM64 platforms // linuxX64("native") { // Linux on x86_64 platforms // mingwX64("native") { // on Windows val main by compilations.getting val interop by main.cinterops.creating binaries { executable() } } } tasks.wrapper { gradleVersion = "8.10" distributionType = Wrapper.DistributionType.BIN }plugins { id 'org.jetbrains.kotlin.multiplatform' version '2.1.20' } repositories { mavenCentral() } kotlin { macosArm64("native") { // Apple Silicon macOS // macosX64("native") { // macOS on x86_64 platforms // linuxArm64("native") { // Linux on ARM64 platforms // linuxX64("native") { // Linux on x86_64 platforms // mingwX64("native") { // Windows compilations.main.cinterops { interop } binaries { executable() } } } wrapper { gradleVersion = '8.10' distributionType = 'BIN' }The project file configures the C interop as an additional build step. Check out the Multiplatform Gradle DSL reference to learn about different ways you can configure it.
Move your
interop.def
,lib.h
, andlib.def
files to thesrc/nativeInterop/cinterop
directory.Create a
src/nativeMain/kotlin
directory. This is where you should place all the source files, following Gradle's recommendations on using conventions instead of configurations.By default, all the symbols from C are imported to the
interop
package.In
src/nativeMain/kotlin
, create ahello.kt
stub file with the following content:import interop.* import kotlinx.cinterop.ExperimentalForeignApi @OptIn(ExperimentalForeignApi::class) fun main() { println("Hello Kotlin/Native!") ints(/* fix me*/) uints(/* fix me*/) doubles(/* fix me*/) }
You'll complete the code later as you learn how C primitive type declarations look from the Kotlin side.
Inspect generated Kotlin APIs for a C library
Let's see how C primitive types are mapped into Kotlin/Native and update the example project accordingly.
Use IntelliJ IDEA's Go to declaration command (Cmd + B/Ctrl + B) to navigate to the following generated API for C functions:
C types are mapped directly, except for the char
type, which is mapped to kotlin.Byte
as it's 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 |
Update Kotlin code
Now that you've seen the C definitions, you can update your Kotlin code. The final code in the hello.kt
file may look like this:
To verify that everything works as expected, run the runDebugExecutableNative
Gradle task in your IDE or use the following command to run the code:
Next step
In the next part of the series, you'll learn how struct and union types are mapped between Kotlin and C:
See also
Learn more in the Interoperability with C documentation that covers more advanced scenarios.