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

时间:2018-09-11 19:39:44

标签: java android c++ android-ndk java-native-interface

我目前正在开发一个具有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 进行编译的(根据{{​​3}},通过读取文件,我发现 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\n", dlerror());
        exit(EXIT_FAILURE);
    }
    dlerror();

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

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

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

我在做什么导致 dlopen 抛出该错误?

1 个答案:

答案 0 :(得分:0)

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

  

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

使用主机 gcc 编译器构建 sumLib.so 也无济于事。即使生成的库针对相同的 x86_64 ABI,Android上的运行时环境也完全不同。请使用NDK工具链。如果愿意,可以使用独立工具链代替 ndk-build CMake 脚本。