使用预编译的C共享库与JNI / NDK

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用预编译的C共享库与JNI / NDK相关的知识,希望对你有一定的参考价值。

我目前正在开发一个具有C ++集成的小型android演示应用程序。我正在使用Android Studio 3.1.4,我的项目有NDK支持(开发环境是Windows 10)。

该应用程序的目的是从存储(sdcard)加载共享库(.so)文件,然后动态链接库。

以下是我创建此应用程序的步骤:

1.在Cygwin上使用GCC生成共享库

这些是我用来编译库的文件

“sumLib.h”:

#define _MYLIB_H_
#define MAX_INPUT 25
extern double sum(double x, double y);

“sumLib.c”:

#include <stdlib.h>
#include "sumLib.h"
double sum(double x, double y) {
    return x + y;
}

然后我使用以下命令生成库文件:

gcc -shared -o sumLib.o -c sumLib.c
gcc -shared -o sumLib.so sumLib.o

2.在Android客户端上加载库文件

我将文件传输到Android模拟器的SD卡(使用设备文件探索)(/storage/emulated/0/Download/sumLib.so)

这是读取库并将其与符号“sum”链接的c ++代码:

extern "C" {
JNIEXPORT jdouble JNICALL
Java_com_example_user_demo_MainActivity_sum(JNIEnv *env, jobject obj, jdouble n1, jdouble n2) {
    void *handle;
    double (*sum)(double, double);
    char *error;

    char fname[PATH_MAX];
    strcpy(fname, internalPath); //internal path is a predefined string that points to the Download folder in the SD card
    strcat(fname, "/sumLib.so");

    handle = dlopen(fname, RTLD_LAZY);
    if (!handle) {
        __android_log_write(ANDROID_LOG_ERROR, "Creating handle", "Error creating handle");
        exit(EXIT_FAILURE);
    }

    dlerror();

    *(void **) (&sum) = dlsym(handle, "sum");

    if ((error = dlerror()) != NULL) {
        __android_log_write(ANDROID_LOG_ERROR, "Error linking symbol", error);
        exit(EXIT_FAILURE);
    }

    double result = (*sum)(n1, n2);
    dlclose(handle);
    return result;
}
}

以下是调用c ++函数的Java代码:

public native double sum(double n1, double n2);

// function was called after the click of a button
public void onClickTestBtn(View v) {

    // set up the text view to display the result
    TextView tv = findViewById(R.id.sample_text);
    double result = sum(134.3, 11.3);
    tv.setText(String.format("Result is %f", result));
}

这理论上应该调用用c ++定义的“sum”函数,并使用库查找符号“sum”并执行操作,但是dlopen不能从文件创建句柄,它会不断抛出错误。

这是错误消息:

"dlopen failed: library "/storage/emulated/0/Download/sumLib.so" needed or dlopened by "/data/app/com.example.user.demo-fzNLN7tBu86LCNun1yLQlg==/lib/x86_64/libnative-lib.so" is not accessible for the namespace "classloader-namespace""

我注意到在错误消息中的文件地址的末尾,他们添加了正斜杠而不是反斜杠,这有什么影响吗?此外,我不确定错误消息的后半部分是什么意思。

我认为Cygwin上的GCC可能正在编译与运行Android模拟器的架构不同的架构,但在我检查文件后,我发现它是为x64编译的(根据this,我发现PE d +来自阅读文件)。对于模拟器,我正在运行x86_64映像(API 27),并在调用后打印出x86_64:

String arch = System.getProperty("os.arch");
Log.d("Debug", String.format("system arch is %s", arch));

我还检查了文件是否可以从c ++环境访问,所以我使用了

FILE *f = fopen(fname, "r");

并且在调试器中,它没有指向null,它指向的东西,确切地说是0x00007227dd414018,是否可以安全地假设它找到了文件并且可以正确读取它?

我不知道如何修复dlopen给我的这个错误,至于库文件的完整性和正确性,我已经用以下C代码测试了它:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int agrc, char **agrv) {
    void *handle;
    double (*sum)(double, double);
    char *error;

    handle = dlopen("./sumLib.so", RTLD_LAZY);
    if (!handle) {
        printf("error opening file");
        fprintf(stderr, "%s
", dlerror());
        exit(EXIT_FAILURE);
    }
    dlerror();

    *(void **) (&sum) = dlsym(handle, "sum");

    if ((error = dlerror()) != NULL) {
        fprintf(stderr, "%s
", error);
        exit(EXIT_FAILURE);
    }
    printf("Sum is %f
", (*sum)(13.3, 113.4));
    dlclose(handle);
    exit(EXIT_SUCCESS);
    return 0;
}

它能够正确打开库,链接符号并返回正确的结果。

我做错了什么让dlopen抛出那个错误?

答案

尝试为目标SDK 23或更低版本构建应用程序,并在模拟器API 24或更低版本上运行它。我相信你看到的失败是最近安全措施的体现。见Android 7.0 Behavior Changes

从Android 7.0开始,系统会阻止应用程序动态链接到非NDK库。

使用主机gcc编译器构建sumLib.so也没有帮助。即使生成的库以相同的x86_64 ABI为目标,Android上的运行时环境也完全不同。请使用NDK工具链。如果您愿意,可以使用独立工具链代替ndk-build或CMake脚本。

以上是关于使用预编译的C共享库与JNI / NDK的主要内容,如果未能解决你的问题,请参考以下文章

01_JNI是什么,为什么使用,怎么用JNI,Cygwin环境变量配置,NDK案例(使用Java调用C代码),javah命令使用

JNI和NDK编程

将 so|JNI|NDK 之间的关系说明白

使用预编译库PREBUILT LIBRARY

Android JNI入坑之旅:JNI的简单介绍

JNI 与 NDK 入门