skip to content

C / Objective-C / Swift Interop (cinterop)

cinterop generates Kotlin bindings from C or Objective-C headers described in a .def file, giving you CPointer, memScoped, and friends. It is how Kotlin/Native talks to platform frameworks, and how you leak memory if you skip the scoping helpers.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is the cinterop tool in Kotlin/Native, and what role does a .def file play when binding to a C library?

level: juniorimportance: must knowfreq 55%

answer

  1. cinterop = C headers in, Kotlin .klib bindings out
  2. .def = package + headers + compilerOpts/linkerOpts
  3. Configured via cinterops { } in the native compilation
  4. Generated klib lands on the source set classpath
  5. Kotlin/Native only (not JVM/JS)

basics

~10 s

cinterop is a Kotlin/Native tool that reads C header files and produces Kotlin code so you can call the C library from Kotlin. A .def file tells it which headers and library to use.

solid answer

~30 s

cinterop is the Kotlin/Native command-line tool (driven through Gradle's cinterops block in the kotlin { } native target) that parses C/Objective-C headers and generates a Kotlin bindings library (a .klib). A .def file is the configuration: it names the package, the headers to include (headers = ...), and how to link (e.g. headerFilter, compilerOpts, linkerOpts, staticLibraries). Each C function/struct/enum becomes a Kotlin declaration. You don't write the bindings by hand; you point cinterop at the .def and Gradle wires the generated klib onto the source set's classpath so you can import and call it.

code

kotlin · 19 lines
kotlin
// libpng.def
// headers = png.h
// package = org.example.libpng
// linkerOpts = -lpng

// build.gradle.kts
kotlin {
    linuxX64 {
        compilations.getByName("main").cinterops {
            val libpng by creating {
                defFile("src/nativeInterop/cinterop/libpng.def")
            }
        }
    }
}

// usage
import org.example.libpng.png_access_version_number
fun main() = println(png_access_version_number())

go deeper

for a junior

Knows cinterop turns C headers into Kotlin bindings and that a .def names headers and the library.

for a middle

Can write a minimal .def with package/headers/linkerOpts and wire the cinterops block in Gradle.

for a senior

Explains headerFilter, compilerOpts vs linkerOpts, static vs dynamic linking, and the klib-on-classpath flow.

for a principal

Reasons about multi-target binding strategy, build reproducibility, and packaging native deps across CI/architectures.

## What cinterop is **cinterop** is a tool shipped with **Kotlin/Native** (the LLVM-backed Kotlin compiler that produces native binaries). Its job is **automatic binding generation**: you give it C (or Objective-C) headers, and it emits a **Kotlin library** (a `.klib`) full of Kotlin declarations that map onto the C API. You then `import` those declarations and call them like ordinary Kotlin. ## The .def file The **`.def` file** (definition file) is cinterop's configuration. It is a key/value text file. Common keys: - `headers` — the C header files to parse (e.g. `headers = png.h`). - `package` — the Kotlin package the generated declarations land in. - `headerFilter` — limits which transitively-included headers contribute declarations (avoids dragging in the whole system). - `compilerOpts` — flags passed to the C compiler front-end (e.g. `-I/path/to/include`). - `linkerOpts` — flags for the linker (e.g. `-lpng`, `-L/path/to/lib`). - `staticLibraries` / `libraryPaths` — bundle a static lib into the klib. ## Wiring it in Gradle You declare a cinterop in the native target: ```kotlin kotlin { macosArm64 { compilations.getByName("main") { cinterops { val libpng by creating { defFile(project.file("src/nativeInterop/cinterop/libpng.def")) packageName("org.example.libpng") } } } } } ``` Gradle runs cinterop, produces the klib, and adds it to the source set so `import org.example.libpng.*` works. ## What gets generated - C **functions** → Kotlin functions. - C **structs/unions** → Kotlin classes accessed through pointers (`CPointer<T>`). - C **enums** → Kotlin enums or constants. - C **typedefs**, macros (simple ones), and global variables → corresponding Kotlin entities. Because the C world is unmanaged, pointers, memory scopes (`memScoped`), and value types (`CValue`) appear in the generated API — but the starting point is always: **point cinterop at headers via a .def, get Kotlin bindings.**

  • Can cinterop be used in a Kotlin/JVM module?
    No. cinterop and the generated klib are Kotlin/Native-only; on JVM you'd use JNI/JNA/the FFM API instead.
  • Where do .def files conventionally live?
    Under src/nativeInterop/cinterop/ in the module, referenced by defFile(...) in the cinterops block.

A .def file is like an order form for a translator: you list which documents (headers) to translate and where to find the originals (lib paths); cinterop hands back the translated bindings.

saying these in an interview costs you the question

  • Thinking cinterop works on the JVM target
  • Claiming you write the Kotlin bindings by hand
  • Confusing .def with a Kotlin source file
  • Not knowing linkerOpts/compilerOpts go in the .def or Gradle config
  • Saying cinterop runs at runtime rather than at build time

context

open as a page

Explain memScoped, CPointer, and how you pass a C struct or an out-parameter to a C function from Kotlin/Native.

level: middleimportance: must knowfreq 50%

basics

~20 s

memScoped gives you a temporary native memory area that is freed when the block ends. Inside it you allocate C structs and get CPointers to them, which you can pass to C functions, including as out-parameters the C code fills in.

open as a page

How do you consume an Apple Objective-C framework (like Foundation/UIKit) from Kotlin/Native, and how do Objective-C types and nullability map into Kotlin?

level: middleimportance: should knowfreq 42%

basics

~10 s

Apple frameworks ship with prebuilt cinterop bindings, so you just import them (e.g. platform.Foundation.*) and call them. Objective-C classes become Kotlin classes, methods become Kotlin functions, and nullable Obj-C types become Kotlin nullable types.

open as a page

How is a Kotlin/Native module exposed to Swift, and what limitations of the Kotlin→Objective-C/Swift bridge should you design around?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Kotlin/Native compiles your code into an Objective-C-compatible framework that Swift can import. Swift sees Kotlin classes and functions, but some Kotlin features (like generics, sealed types, default arguments) don't survive the bridge cleanly, so you design the public API around that.

open as a page

Contrast CValue<T> with CPointer<T>: when do you use cValue { } and useContents, and why does the distinction matter for C functions that take or return structs by value?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

CPointer<T> is a reference to native memory you can mutate; CValue<T> is an immutable copy of a C struct passed by value. You build one with cValue { } and read its fields with useContents { }. You use CValue when the C function takes or returns the struct itself, not a pointer.

open as a page