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 *(Xcode supports C, C++, and out-of-the-box, and Android has [Android NDK](https://developer.android.com/ndk/guides))*. KMM utilizes this functionality and allows you to use C and C++ code in your shared module. \ 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 [Zipline project](https://github.com/cashapp/zipline) which embeds the [QuickJs](https://bellard.org/quickjs/) engine in the KMM code. \ 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. :::tip The code for this article could be found in [the repository](https://github.com/ttypic/kmm-embedded-c) ::: ## 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.](https://kotlinlang.org/docs/multiplatform-library.html#create-a-project) \ Let’s take the SHA-256 code written on C as an example, and copy it into our `native/sha256` folder. The sample code for the SHA-256 implementation was taken from this [repository](https://github.com/B-Con/crypto-algorithms/blob/master/sha256.h). \ Because Android and iOS applications support C-code differently, we need to use the [expected/actual approach](https://kotlinlang.org/docs/multiplatform-connect-to-apis.html). \ First of all, let’s set up our common part: \ ```java /** * 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 [C Klib](https://github.com/touchlab/cklib) plugin, which will build our C-code as LLVM bitcode. \ :::info 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`: \ ```java cklib { config.kotlinVersion = libs.versions.kotlin.get() create("sha256") { language = C srcDirs = project.files(file("native/sha256")) } } ``` \ Then we need to create a `.def` file for the KMM `cinterop` tool which generates Kotlin bindings from a C-header file. \ :::tip To learn more about `.def`, you could read [the official cinterop tutorial](https://kotlinlang.org/docs/native-app-with-c-and-libcurl.html#create-a-definition-file) ::: \ In our case, we just need to create our `sha256.def` in the `nativeInterop/cinterop` folder (default searching place for `cinterop` tool) and name package where Kotlin bindings will be: \ ```yaml package = com.ttypic.clibs.sha256 ``` \ Finally, we need to add the cinterop step for our target platforms and specify the header file: \ ```java 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 `com.ttypic.clibs.sha256` package, and we are ready to invoke native code. Our `actual` implementation will look like this: \ ```java 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: \ ```javascript 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](https://developer.android.com/ndk/guides). Android NDK uses [CMake](https://cmake.org) under the hood; that’s why we need to provide the `CMakeList.txt` build file: \ ```bash 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: \ ```javascript 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 [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html) to map native code to JVM. Android Studio has great JNI support. Let’s create `sha256-jni.c` file, then include JNI and our library header files: \ ```clike #include "jni.h" #include "sha256/sha256.h" ``` \ Now, we are ready to write our `actual` code: \ ```javascript 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 [JNI](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/intro.html) 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 `create JNI funtion` \  JNI function in `sha256-jni.c` \ ```clike 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: \ :::info We need to write the instrumental test on the simulator; regular Unit-test won’t load our native library. ::: \ ```javascript 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](https://github.com/ttypic/kmm-embedded-c) * [Get started KMM](https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html) * [Official KMM library tutorial](https://kotlinlang.org/docs/multiplatform-library.html#create-a-project) * [Zipline project](https://github.com/cashapp/zipline) * [Native dependencies in KMM](https://medium.com/kodein-koders/native-dependency-in-kotlin-multiplatform-part-1-architecture-7fb9ded56bf1) \ \