Stuart Schechter
Stuart Schechter

Reputation: 584

What comes after $ and Android JNI naming?

When I try to call a JNI function from Android, I'm getting an error due to an unexpected suffix that appears in the function signature.

The Kotlin class is org.dicekeys.crypto.seeded.PublicKey and I'm calling a static method constructFromJsonJNI, which should be calling a JNI method named Java_org_dicekeys_crypto_seeded_PublicKey_constructJNI. It is instead look for Java_org_dicekeys_crypto_seeded_PublicKey_constructJNI_00024seeded_1debug (the module is named "seeded" and the IML implies that Andrdoid Studio has assigned the name "seeded_debug". I have no idea where the 00024 originates. Perhaps a hash?)

The mismatch yields this exception, which is a common exception when you don't load the module (it's loaded), don't use the name JNI expects (this matches with the checker in the latest tools), or when you forget to load the library (this worked just a day or two ago before something got corrupted. - it loads):

java.lang.UnsatisfiedLinkError: No implementation found for long org.dicekeys.crypto.seeded.PublicKey.constructJNI$seeded_debug(java.lang.String) (tried Java_org_dicekeys_crypto_seeded_PublicKey_constructJNI_00024seeded_1debug and Java_org_dicekeys_crypto_seeded_PublicKey_constructJNI_00024seeded_1debug__Ljava_lang_String_2)

The libraries are indeed being compiled, the latest Kotlin language tools report a correct link between my extern functions and my JNI implementations, and this all worked until a day ago.

I've already tried deleting all the builds, .iml files, etc. and rebuilding from scratch without luck.

The relevant gradle sections are:

android {

    ndkVersion '21.0.6113669'

    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
            debuggable = false
        }
        debug {
            debuggable true
            initWith debug
            jniDebuggable false
            signingConfig signingConfigs.debug
        }
    }

...
    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'

        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }
    }
...
    externalNativeBuild {
        cmake {
            version "3.15.0+"
            path file('src/main/cpp/CMakeLists.txt')
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }


    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}
...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    testImplementation 'junit:junit:4.13'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation 'com.google.zxing:core:3.4.0'
}

Upvotes: 0

Views: 423

Answers (1)

Stuart Schechter
Stuart Schechter

Reputation: 584

For those who suffer the lost hours I did due to mappings of Kotlin companion object @JvmStatic functions to C function names in JNI.

I had marked my JNI functions internal (which creates the beautiful pair internal external). Apparently, adding internal changes the required JNI function by adding $, which becomes 00024. The current Android Studio tools (3.6.2 from March 19, 2020) didn't factor that in, generated the wrong function names, or flag the problem with the naming.

I fixed by making the functions private external instead of internal external since my use case allowed that. If I needed internal so that other classes in my module could access it, I'd likely just add a wrapper function next to the private extern one that could expose the private function to other code in my module.

Upvotes: 2

Related Questions