- Automatic binding generation from IDL file (no JNI / JNA / cinterop boilerplate)
- JVM interop via Foreign Function & Memory API (Project Panama)
- Low-overhead critical native calls
- Static native linking for Kotlin/Native
- JavaScript target support via Emscripten
- CMake-based native builds with existing toolchains
- No structs, arrays, or callbacks yet
- Kotlin 2.0+
⚠️ This project is under active development.
Apply plugin in your build.gradle.kts:
plugins {
id("com.huskerdev.native-kt") version "1.0.1"
}Then declare a native project in the native {} block:
native {
create("mylib") {
// ...
}
}By default, all native projects are stored in src/nativeInterop/[name].
Location can be changed using property projectDir.
You can run ./gradlew :cmakeInit[Name] to generate a minimal CMake project.
This task is optional but recommended for getting started.
For an example, you can look at the test-glfw module.
- macOS:
Xcode Command Line Tools - Windows:
mingw64(MSYS2)
It is important to setmsys64/clang64/bindirectory to PATH. - Linux: It is required to have
clang. Please follow your own distribution instructions.
CMake is required to configure your projects.
It is important to have Android SDK installed, as well as NDK with specified version.
ANDROID_HOME is required to locate the SDK.
You can simply install Android Studio and set up NDK in UI.
Emscripten is required to be installed.
EMSDK is required to locate the SDK.
The WebIDL format is used to describe the interface of C functions in api.idl.
It should be located in the root of the native project.
By default: src/nativeInterop/[name]/api.idl
All functions must be declared inside the global namespace.
Other namespaces will be ignored.
// Example
namespace global {
void helloWorld();
}
⚠️ Currently, you can only specify functions with primitive types.
Structures and arrays support will be coming later.
| IDL | Kotlin | C |
|---|---|---|
long |
Int | int32_t |
long long |
Long | int64_t |
float |
Float | float |
double |
Double | double |
byte |
Byte | int8_t |
symbol |
Char | int16_t |
bool |
Boolean | bool |
DOMString |
String | const char* |
void |
Unit | void |
When Gradle project is loaded, it generates header api.h based on api.idl.
This C-header should be included and must be implemented in your code.
For C++, make sure the functions are exported with
extern "C".
Example:
// api.h
// ...
void helloWorld();
// ...// myLib.c
#include "api.h"
#include <stdio.h>
void helloWorld() {
printf("Hello, World!\n");
fflush(stdout);
}There is no API for manual memory management, but it is important to understand the memory lifecycle.
All pointer variables from function arguments (strings, arrays, ...) , must not be freed or stored outside this function.
Before function execution, actual Kotlin String/Array data is copied to the pointers, and freed after completion.
In other words, if you want to store the string in native code, you must copy it to your own allocated memory.
This behavior may be modified in the future.
When you return a pointer, it will not be used "as is" in Kotlin variables.
Data will be copied to actual Kotlin objects, but pointer will not be freed.
However, you can add [Dealloc] annotation to the function in the api.idl file - it will free the pointer after execution.
It's also important to consider string literals.
If you have a statement like return "my literal", you must not use [Dealloc] annotation.
When Gradle project is loaded, it generates functions based on your api.idl.
By default, API is generated in natives.[name] classpath.
It can be changed when declaring a module in build.gradle using the classPath option.
If you have restarted a Gradle, but IntelliJ IDEA does not see the functions,
then right-click at thebuilddirectory and selectReload from Disk
Before you can call your native function, you must load the library.
You can do it synchronously or asynchronously.
⚠️ Note that synchronous initialization will not work inKotlin/JS.
// Sync init (won't work in Kotlin/JS)
loadLibMyLibSync()
// Async init with callback
loadLibMyLib { /* ... */ }
// Async init (suspend function)
loadLibMyLib()After initialization, you can freely use native functions
suspend fun main() {
loadLibMyLib() // Initialize
helloWorld() // Call native function
}JVM and Android have a critical way for calling native functions.
This means that there will be minimal overhead costs.
These functions should be fast, use only primitive types, and must not perform blocking operations or callbacks.
To declare a critical function, add the [Critical] annotation in api.idl before declaration:
namespace global {
[Critical]
long fastAdd(long a, long b);
}Currently, this is only implemented for the
Foreign Function & Memory APIin JVM.
Support for JNI and Android will be coming in the future.
The previous guide assumed you wanted to use one shared module and several child modules on different platforms.
There's also a way to use just one sourceSet (for example, jvmMain).
// build.gradle.kts
native {
create("mylib", SingleModule::class) {
targetSourceSet = TARGET_JVM
}
}Now your module will be available only in jvmMain source set.
This mode is useful when native code is required only for a specific platform.