Hi. I'm having some trouble getting c interop to w...
# multiplatform
k
Hi. I'm having some trouble getting c interop to work with my KMM library.
I have the following c header:
Copy code
#ifndef __MYWRAPPER_H
#define __MYWRAPPER_H

#ifdef __cplusplus
extern "C" {
#endif

typedef struct MyClass MyClass;

MyClass* newMyClass();

void MyClass_int_set(MyClass* v, int i);

int MyClass_int_get(MyClass* v);

void deleteMyClass(MyClass* v);

#ifdef __cplusplus
}
#endif
#endif
And the following def file
Copy code
package = testlib

headers = MyWrapper.h
headerFilter = MyWrapper.h

staticLibraries = libtest.a
libraryPaths = src/nativeInterop/cinterop/libtest/
compilerOpts = -Isrc/nativeInterop/cinterop/libtest
And here is my gradle
Copy code
kotlin {
    android()

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }

        it.apply {
            compilations.getByName("main") {
                val libtest by cinterops.creating {
                    defFile(project.file("src/nativeInterop/cinterop/libtest.def"))
                    packageName("testlib")
                }
            }
        }
    }

    sourceSets {
        val commonMain by getting
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val androidMain by getting
        val androidTest by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }
        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }
    }
}
Everything seems to build fine, but I can't access testlib from Kotlin (Android, iOS, or common)
l
I think the methods need to be static.
Is src in the root of your project, or in a module’s folder?
k
It's the module folder
l
You should be able to remove the defFile from your gradle and just use
val libtest by cinterops.creating
. It will automatically check src/nativeInterop/cinterop/libtest.def
k
I have tried that too, I just added it while I was testing the packageName. It's the same behavior with this too
Copy code
it.apply {
    compilations.getByName("main") {
        val libtest by cinterops.creating
    }
}
l
Are there any warnings in the console?
k
Oh! Yes there is!
Copy code
[WARNING] The project is using Kotlin Multiplatform with hierarchical structure and disabled 'cinterop commonization'
    See: <https://kotlinlang.org/docs/mpp-share-on-platforms.html#use-native-libraries-in-the-hierarchical-structure>

    'cinterop commonization' can be enabled in your 'gradle.properties'
    kotlin.mpp.enableCInteropCommonization=true
    
    To hide this message, add to your 'gradle.properties'
    kotlin.mpp.enableCInteropCommonization.nowarn=true 

    The following source sets are affected: 
    [iosMain]
    
    The following cinterops are affected: 
    [cinterop:compilation/compileKotlinIosArm64/libtest, cinterop:compilation/compileKotlinIosSimulatorArm64/libtest, cinterop:compilation/compileKotlinIosX64/libtest]
Setting that doesn't seem to help though 😞
l
Any new warnings?
k
No
Copy code
> Configure project :shared
Kotlin Multiplatform Projects are an Alpha feature. See: <https://kotlinlang.org/docs/reference/evolution/components-stability.html>. To hide this message, add 'kotlin.mpp.stability.nowarn=true' to the Gradle properties.


The following Kotlin source sets were configured but not added to any Kotlin compilation:
 * androidAndroidTestRelease
 * androidTestFixtures
 * androidTestFixturesDebug
 * androidTestFixturesRelease
You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'.
See <https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#connecting-source-sets>

> Task :commonizeNativeDistribution
> Task :commonize
> Task :runCommonizer
> Task :shared:cinteropLibtestIosArm64 UP-TO-DATE
> Task :shared:cinteropLibtestIosSimulatorArm64 UP-TO-DATE
> Task :shared:cinteropLibtestIosX64 UP-TO-DATE
> Task :shared:commonizeCInterop UP-TO-DATE
> Task :shared:copyCommonizeCInteropForIde UP-TO-DATE
> Task :shared:commonize
> Task :shared:transformIosMainCInteropDependenciesMetadataForIde UP-TO-DATE
> Task :shared:transformIosTestCInteropDependenciesMetadataForIde UP-TO-DATE
> Task :shared:runCommonizer
> Task :prepareKotlinBuildScriptModel UP-TO-DATE

BUILD SUCCESSFUL in 1s
8 actionable tasks: 1 executed, 7 up-to-date
l
Looks like it’s trying to cinterop. You don’t see MyClass in the module?
k
Inside the aar?
l
In the Kotlin/Native source set you want to use it in. Android uses JVM, so cinterop won’t be available without a JNI wrapper.
k
Is there anywhere I should look sorry? I can't use
testlib
or
MyClass
inside commonMain and I can't see anything under External Libraries in the project window
l
What are your targets? Is it Android and iOS?
k
Yeah
l
Android uses JVM, so you can’t directly use anything from C. For now, you’ll have to stick with your C library in the iOS source set. On Android, you’ll have to have a JVM implementation.
k
Okay. That's a shame about Android. But it makes sense as to why I can't add cinterops to the android() section of my gradle
l
For Android, this means you have 2 options: 1. Write an equivalent of testlib in Kotlin 2. If you can’t do that, use the androidNative* source sets to create a JNI wrapper. You can look at https://github.com/LandryNorris/JniUtils/sample for an example of how to do this.
k
Oh! It's available in iOS module now! I must have been that warning! Thanks 🙂
l
Is testlib something you control or something distributed by some company? If a company distributes an iOS binary, they almost certainly have an Android version using JVM.
k
The library we want to use is our internal lib. It's currently a c++ library with a jni wrapper for Android. We're looking to share it with iOS so was hoping KMM would allow us to do this easily
But it sounds like it's going to have to be something like the following? iOS: C++ with a C wrapper and cinterop Android: C++ with jni
l
In this case, you can still use your JNI wrapper on Android. You can create a wrapper in commonMain with expect, then add actual implementations in your androidMain and iosMain
If you already have C++ code for Android, you can use the JNI normally, then provide the JVM class as an actual for your common expect.
k
Great. I've already hooked that up for the Android side. Glad to know my instincts were correct there then
Thanks for all the help, I really appriciate it. It's been driving me crazy for the last few hours!
🍺
207 Views