Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
173: Interop with C Libraries in Kotlin/Native
Most of the time, Kotlin/Native gives us everything we need. But eventually, you'll run into a situation where you need a high-performance C library, a legacy system API, or some hardware-specific driver that only provides a C interface. When that happens, you don't rewrite the library in Kotlin; you build a bridge.
I've found that the hardest part of C interop isn't the Kotlin code—it's the configuration. To show you how this works, we're going to build a tiny C library called libtinymath that handles a specific calculation (the hypotenuse of a triangle) and call it from Kotlin. It's simple, but it covers the actual plumbing you'll use in production.
Writing the C side of the bridge
First, we need the actual C code. I'm keeping this minimal so we can focus on the interop. Here is my tinymath.h header file:
// tinymath.h
#ifndef TINYMATH_H
#define TINYMATH_H
double calculate_hypotenuse(double a, double b);
#endif
And the implementation in tinymath.c:
// tinymath.c
#include "tinymath.h"
#include
double calculate_hypotenuse(double a, double b) {
return sqrt((a * a) + (b * b));
}
At this point, I'd compile this into a static library using gcc or clang. In a real project, your build system (like CMake) would handle this, but for now, just imagine we have libtinymath.a sitting in our project folder.
Mapping C to Kotlin with a .def file
Kotlin/Native doesn't just "guess" what's in your C library. You have to provide a definition file (a .def file) that tells the cinterop tool which headers to read and how to link the binary. I'll create one called tinymath.def:
headers = tinymath.h
package = com.example.tinymath
linkerOpts = -L. -ltinymath
The package line is important—this is where the generated Kotlin bindings will live. The linkerOpts tell the linker to look in the current directory (-L.) for a library named libtinymath (-ltinymath).
The "Where is my symbol?" moment
Here is where I usually trip up. I wrote my Kotlin code, ran the cinterop tool, and tried to compile. I got a nasty linker error: undefined reference to 'calculate_hypotenuse'.
I spent five minutes staring at my Kotlin code thinking I'd called the function wrong. Then I realized the mistake: I had the .def file pointing to the library, but I hadn't actually compiled the C code into a .a file in the directory the linker was searching. The cinterop tool generates the declarations (so the IDE is happy), but it doesn't compile your C source files for you. It only links against existing binaries.
I fixed it by running gcc -c tinymath.c -o tinymath.o and then ar rcs libtinymath.a tinymath.o. Once the actual binary existed, the error vanished.
Calling C from Kotlin
Now for the fun part. Once the cinterop tool runs, Kotlin treats the C functions as if they were regular Kotlin functions. Because I defined the package as com.example.tinymath, I can just import it.
import com.example.tinymath.*
fun main() {
val a = 3.0
val b = 4.0
// This calls the C function directly
val result = calculate_hypotenuse(a, b)
println("The hypotenuse of $a and $b is $result")
}
Notice how seamless that is? The double in C maps directly to Double in Kotlin. If I were dealing with strings, I'd have to deal with CPointer and pinned memory, but for primitive types, Kotlin/Native does the heavy lifting for us.
📋 Practical Task
Exercise: Building a C-Powered String Reverser
To practice C interop, you are going to create a small utility that reverses a string using a C function. This will force you to handle C strings (pointers), which are more complex than doubles.
- The C Part: Create a
reverser.handreverser.c. Implement a functionvoid reverse_string(char* str)that reverses a string in-place. - The Bridge: Create a
reverser.deffile that maps the header and links to the compiledlibreverser.a. - The Kotlin Part: Write a Kotlin program that:
- Takes a Kotlin
String. - Converts it to a C-string using
memScoped { ... }andallocArrayOforcstr. - Calls
reverse_string. - Converts the resulting C-string back into a Kotlin
StringusingtoKString(). - Prints the result to the console.
- Takes a Kotlin
Goal: Successfully pass a string from Kotlin to C, mutate it in C, and bring the mutated version back into Kotlin.
There are no comments for now.