首页 > 解决方案 > 从多平台 kotlin 项目访问 C/C++ 库

问题描述

我第一次使用 Android Studio 构建一个多平台项目。我创建了一个在 Android 上使用多平台库的 Android 应用程序模块。我还使用 XCode 构建了一个在 iOS 上使用多平台库的 iOS 应用程序。一切正常,我可以使用expect fun由不同actual fun的 Android 和 iOS 实现的。

我还在 C++ 中创建了一个库,它公开了一个 C 接口。

#ifndef PINGLIB_LIBRARY_H
#define PINGLIB_LIBRARY_H

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    long long elapsed;
} PingInfo;

typedef void (*PingCallback)(PingInfo pingInfo);
typedef struct
{
    PingCallback pingUpdate;
} PingObserver;

void* ping(const char * url, const PingCallback *pingCallback);
void subscribe(void* pingOperation);
void unsubscribe(void* pingOperation);

#ifdef __cplusplus
}
#endif

#endif //PINGLIB_LIBRARY_H

我使用 CLion 来构建 C++ 代码。我创建了一个.def文件,用于使用cinterop.

package = com.exercise.pinglib
headers = PingLibrary.h
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu
compilerOpts = -std=c99 -I/Users/username/myproject/ping/ping/header
staticLibraries = libping.a
libraryPaths = /opt/local/lib /Users/username/myproject/ping/cmake-build-debug

libping.a是为构建 C++ 代码而创建的库。它是在文件夹中创建的/Users/username/myproject/ping/cmake-build-debug

当我运行命令cinterop -def ping.def -o ping时,它会创建 klib 文件和包含文件的文件夹、包含manifest.properties文件的natives子文件夹和包含文件的子cstubs.bc文件kotlin.kt

ping.klib
-ping-build
    manifest.properties
    -natives
        cstubs.bc
    -kotlin
        -com
            -exercise
                -pinglib
                    pinglib.kt

如何使用cinterop在我的 kotlin-multiplatform 项目中创建的库?

我找到了不同的方法来导入它,但我没有找到任何关于如何做到这一点的完整描述。 在这里他们说我可以使用类似的东西:

    implementation files("ping.klib")

我是为 iOS 项目做的,但我仍然不知道如何在 Android 和 iOS 上访问 kotlin 类。

这是我的build.gradle

apply plugin: 'com.android.library'
apply plugin: 'kotlin-multiplatform'

android {
    compileSdkVersion 28
    defaultConfig {
        minSdkVersion 15
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
kotlin {
    targets {
        final def iOSTarget = System.getenv('SDK_NAME')?.startsWith('iphoneos') ? presets.iosArm64 : presets.iosX64
        fromPreset(iOSTarget, 'ios') {
            binaries {
                framework('shared')
            }
        }
        fromPreset(presets.android, 'android')
    }
    sourceSets {
        // for common code
        commonMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib-common'
        }
        androidMain.dependencies {
            api 'org.jetbrains.kotlin:kotlin-stdlib'
        }
        iosMain.dependencies {
            implementation files("ping.klib")
        }
    }
}
configurations {
    compileClasspath
}

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
    final def framework = kotlin.targets.ios.binaries.getFramework("shared", mode)
    inputs.property "mode", mode
    dependsOn framework.linkTask
    from { framework.outputFile.parentFile }
    into frameworkDir
    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}
tasks.build.dependsOn packForXCode

编辑 我改变了这个问题,因为最初,我认为这cinterop不是创建 klib 库,但这只是一个错误:我正在查看ping-build文件夹,但文件在该文件夹之外。所以我解决了一半的问题。

EDIT2 我添加了build.script

标签: androidc++kotlinkotlin-multiplatformkotlin-native

解决方案


I'm glad to see that everything is fine with the KLIB, now about the library use.
First of all, I have to mention that this library can be utilized only by Kotlin/Native compiler, meaning it will be available for some targets(see list here). Then, if you're going to include C library use into an MPP project, it is always better to produce bindings via the Gradle script. It can be done inside of a target, see this doc for example. For your iOS target it should be like:

kotlin {
    iosX64 {  // Replace with a target you need.
        compilations.getByName("main") {
            val ping by cinterops.creating {
                defFile(project.file("ping.def"))
                packageName("c.ping")
            }
        }
    }
}

This snippet will add cinterop task to your Gradle, and provide module to include like import c.ping.* inside of the corresponding Kotlin files.


推荐阅读