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:
See what Data types are in C language.
Create a tiny C Library that uses those types in exports.
Find how Primitive types in Kotlin are mapped to C.
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 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
.
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:
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:
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:
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 Get started with Kotlin/Native 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:
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:
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:
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:
The final code in the hello.kt
file may look like that:
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.