This tutorial demonstrates how to use C/C++ code in your KMM shared module. You'll learn how to embed a simple SHA-256 implementation on C-language in the KMM library and use it for iOS and Android target platforms. Kotlin Multiplatform Mobile (KMM) is an SDK designed to simplify the development of cross-platform mobile applications. You can share common code between iOS and Android apps and write platform-specific code only where it's necessary You can use C and C++ code both with Android and iOS . KMM utilizes this functionality and allows you to use C and C++ code in your shared module. (Xcode supports C, C++, and out-of-the-box, and Android has ) Android NDK It can be useful in app development; there are a lot of libraries that have already been written in C/C++. A great example of this technique is the which embeds the engine in the KMM code. Zipline project QuickJs In this article, we won’t talk about how it works under the hood and will instead concentrate on writing the code and setting up the Gradle build. The code for this article could be found in the repository Start Building a Library for iOS and Android We won’t talk about how to create a KMM library as it is very well explained in the official Kotlin tutorial. Let’s take the SHA-256 code written on C as an example, and copy it into our folder. The sample code for the SHA-256 implementation was taken from this . native/sha256 repository Because Android and iOS applications support C-code differently, we need to use the . expected/actual approach First of all, let’s set up our common part: /** * Encoder interface which can encode byte arrays to Sha256 format. */ interface Sha256Encoder { fun encode(src: ByteArray): ByteArray fun encodeToString(src: ByteArray): String { val encoded = encode(src) return buildString(encoded.size) { encoded.forEach { append(it.toUByte().toString(16).padStart(2, '0')) } } } } expect object Sha256Factory { /** * Creates a new instance of [Sha256Encoder] */ fun createEncoder(): Sha256Encoder } Then, continue to platform-specific implementations. Embed C-code for iOS Target Platforms For iOS target platforms, we will set up the Gradle plugin, which will build our C-code as LLVM bitcode. C Klib C Klib is an experimental library with limited support from the authors. All we need to do is provide a path to the source folder with C-code; in our example, it will be : native/sha256 cklib { config.kotlinVersion = libs.versions.kotlin.get() create("sha256") { language = C srcDirs = project.files(file("native/sha256")) } } Then we need to create a file for the KMM tool which generates Kotlin bindings from a C-header file. .def cinterop To learn more about , you could read .def the official cinterop tutorial In our case, we just need to create our in the folder (default searching place for tool) and name package where Kotlin bindings will be: sha256.def nativeInterop/cinterop cinterop package = com.ttypic.clibs.sha256 Finally, we need to add the cinterop step for our target platforms and specify the header file: kotlin { // ... listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { it.compilations { val main by getting { cinterops { create("sha256") { header(file("native/sha256/sha256.h")) } } } } } // ... } After resyncing our Gradle build, we will see Kotlin bindings in the package, and we are ready to invoke native code. Our implementation will look like this: com.ttypic.clibs.sha256 actual actual object Sha256Factory { actual fun createEncoder(): Sha256Encoder = NativeSha256Encoder } object NativeSha256Encoder : Sha256Encoder { @OptIn(ExperimentalUnsignedTypes::class) override fun encode(src: ByteArray): ByteArray = memScoped { val ctx = alloc<SHA256_CTX>() sha256_init(ctx.ptr) val srcPointer = src.toUByteArray().toCValues().ptr sha256_update(ctx.ptr, srcPointer, src.size.toULong()) UByteArray(32).apply { usePinned { sha256_final(ctx.ptr, it.addressOf(0)) } }.toByteArray() } } That’s it; now we can run tests to check if everything is OK: class NativeSha256Test { @Test fun `should pass library sha256 checks`() { checkEncodeToString("Kotlin", "c78f6c97923e81a2f04f09c5e87b69e085c1e47066a1136b5f590bfde696e2eb") checkEncodeToString("abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") checkEncodeToString("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1") checkEncodeToString("aaaaaaaaaa", "bf2cb58a68f684d95a3b78ef8f661c9a4e5b09e82cc8f9cc88cce90528caeb27") } private fun checkEncodeToString(input: String, expectedOutput: String) { assertEquals(expectedOutput, Sha256Factory.createEncoder().encodeToString(input.asciiToByteArray())) } } private fun String.asciiToByteArray() = ByteArray(length) { get(it).code.toByte() } Embed C-code for Android Target Platforms For Android, we will be using . Android NDK uses under the hood; that’s why we need to provide the build file: Android NDK CMake CMakeList.txt cmake_minimum_required(VERSION 3.4.1) # setup C standard we used set(CMAKE_C_STANDARD 99) # source code file mask file(GLOB_RECURSE sources "../../native/*.c") # build sources as dynamic library add_library(sha256 SHARED ${sources}) # link library target_link_libraries(sha256) Then we have to specify this file in the Android plugin and setup the target platforms: android { // ... defaultConfig { // ... ndk { // target platforms abiFilters += listOf("x86", "x86_64", "armeabi-v7a", "arm64-v8a") } } // ... externalNativeBuild { cmake { path = file("src/androidMain/CMakeLists.txt") } } } To invoke our native code, we need to use to map native code to JVM. Android Studio has great JNI support. Let’s create file, then include JNI and our library header files: JNI sha256-jni.c #include "jni.h" #include "sha256/sha256.h" Now, we are ready to write our code: actual actual object Sha256Factory { actual fun createEncoder(): Sha256Encoder = AndroidSha256Encoder } object AndroidSha256Encoder : Sha256Encoder { init { System.loadLibrary("sha256") } external override fun encode(src: ByteArray): ByteArray } It is very straightforward. First, we load our dynamic library, and next, we use to invoke native code. Now, all we need is to write JNI-bindings. You can do it yourself using the fully qualified class name or invoke the Android Studio context menu and choose JNI create JNI funtion JNI function in sha256-jni.c JNIEXPORT jbyteArray JNICALL Java_com_ttypic_clibs_AndroidSha256Encoder_encode(JNIEnv *env, jclass _, jbyteArray src) { BYTE hash[SHA256_BLOCK_SIZE]; SHA256_CTX ctx; size_t len = (size_t) ((*env)->GetArrayLength(env, src)); jboolean copied; jbyte* bytes = (*env)->GetByteArrayElements(env, src, &copied); sha256_init(&ctx); sha256_update(&ctx, (const BYTE *) bytes, len); sha256_final(&ctx, hash); (*env)->ReleaseByteArrayElements(env, src, bytes, JNI_ABORT); jbyteArray result = (*env)->NewByteArray(env, SHA256_BLOCK_SIZE); (*env)->SetByteArrayRegion(env, result, 0, SHA256_BLOCK_SIZE, (const jbyte *) hash); return result; } And finally, we are ready to write tests: We need to write the instrumental test on the simulator; regular Unit-test won’t load our native library. class AndroidSha256Test { @Test fun should_pass_library_sha256_checks() { checkEncodeToString("Kotlin", "c78f6c97923e81a2f04f09c5e87b69e085c1e47066a1136b5f590bfde696e2eb") checkEncodeToString("abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") checkEncodeToString("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1") checkEncodeToString("aaaaaaaaaa", "bf2cb58a68f684d95a3b78ef8f661c9a4e5b09e82cc8f9cc88cce90528caeb27") } private fun checkEncodeToString(input: String, expectedOutput: String) { assertEquals( expectedOutput, Sha256Factory.createEncoder().encodeToString(input.asciiToByteArray()) ) } } private fun String.asciiToByteArray() = ByteArray(length) { get(it).code.toByte() } We successfully embedded C-code in our Kotlin Multiplatform Library, which can be used in iOS and Android target platforms. Useful Links Github repository with final code Get started KMM Official KMM library tutorial Zipline project Native dependencies in KMM