使用预编译的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命令使用