如何在Linux C++中调用从Android APK提取的.so文件的JNI函数
实现JNI函数调用的完整指南
我来帮你搞定这个JNI函数的调用问题。首先,咱们得先明确目标函数的JNI签名规则,然后一步步修改代码和Makefile,确保能正确加载库、获取函数指针并调用它。
1. 先搞懂目标JNI函数的签名
从nm -D libnative-lib.so拿到的Java_com_example_nativelib_utils_Constants_getStringFromJNI是一个标准的JNI静态方法绑定函数,它的C语言原型是固定的:
jstring Java_com_example_nativelib_utils_Constants_getStringFromJNI(JNIEnv* env, jclass clazz);
参数说明:
JNIEnv*:JNI环境指针,用来调用JNI提供的各类API(比如字符串转换)jclass:对应Java中Constants类的引用(因为是静态方法)- 返回值
jstring:JNI层的字符串类型,对应Java的String
2. 修改C代码实现调用
你的现有代码只完成了库的加载,还需要补充获取函数指针、处理JNI环境(如果函数依赖JNI API)、调用目标函数这几步。分两种场景处理:
场景A:目标函数不依赖JNI环境(仅返回硬编码字符串)
如果这个JNI函数只是简单返回一个固定C字符串,内部没调用任何JNI API,那可以偷懒传NULL当参数(注意:这是不安全的,仅适用于函数完全没用到env和clazz的情况):
#include <stddef.h> #include <dlfcn.h> #include <stdio.h> #include <jni.h> // 必须引入JNI头文件 // 定义匹配JNI函数签名的函数指针类型 typedef jstring (*JNI_GetStringFunc)(JNIEnv*, jclass); // 修改init_library,返回库句柄(方便后续使用) void* init_library() { void* hdl = dlopen("./libnative-lib.so", RTLD_LAZY); if (!hdl) { fprintf(stderr, "加载库失败: %s\n", dlerror()); } return hdl; } int main() { void* lib_handle = init_library(); if (!lib_handle) { printf("ERROR\n"); return 1; } // 获取目标函数的指针 JNI_GetStringFunc get_string_func = (JNI_GetStringFunc)dlsym(lib_handle, "Java_com_example_nativelib_utils_Constants_getStringFromJNI"); if (!get_string_func) { fprintf(stderr, "获取函数指针失败: %s\n", dlerror()); dlclose(lib_handle); return 1; } // 调用函数(假设函数不依赖JNI环境) jstring result = get_string_func(NULL, NULL); // 如果函数返回的是标准jstring,这里直接强转成char*会打印地址;如果是硬编码的C字符串,才能正常打印 printf("调用结果: %s\n", (char*)result); // 清理资源 dlclose(lib_handle); return 0; }
场景B:目标函数依赖JNI环境(标准JNI实现)
如果函数内部调用了JNI API(比如用(*env)->NewStringUTF创建Java字符串),那必须先初始化一个Java虚拟机(JVM)来获取有效的JNIEnv*和jclass:
#include <stddef.h> #include <dlfcn.h> #include <stdio.h> #include <jni.h> typedef jstring (*JNI_GetStringFunc)(JNIEnv*, jclass); // 初始化Java虚拟机,获取JNI环境指针 JNIEnv* create_jvm(JavaVM** jvm) { JNIEnv* env; JavaVMInitArgs vm_args; JavaVMOption options[1]; // 设置JVM启动参数,指定类路径(如果需要加载Constants类的话) options[0].optionString = "-Djava.class.path=./"; // 假设Constants.class在当前目录 vm_args.version = JNI_VERSION_1_8; // 匹配你的Java版本 vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_TRUE; // 创建JVM jint res = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args); if (res != JNI_OK) { fprintf(stderr, "创建JVM失败: %d\n", res); return NULL; } return env; } void* init_library() { void* hdl = dlopen("./libnative-lib.so", RTLD_LAZY); if (!hdl) { fprintf(stderr, "加载库失败: %s\n", dlerror()); } return hdl; } int main() { // 1. 初始化JVM JavaVM* jvm; JNIEnv* env = create_jvm(&jvm); if (!env) { return 1; } // 2. 加载Constants类(获取jclass引用) jclass clazz = (*env)->FindClass(env, "com/example/nativelib/utils/Constants"); if (!clazz) { fprintf(stderr, "找不到Constants类\n"); (*jvm)->DestroyJavaVM(jvm); return 1; } // 3. 加载本地库 void* lib_handle = init_library(); if (!lib_handle) { printf("ERROR\n"); (*jvm)->DestroyJavaVM(jvm); return 1; } // 4. 获取目标函数指针 JNI_GetStringFunc get_string_func = (JNI_GetStringFunc)dlsym(lib_handle, "Java_com_example_nativelib_utils_Constants_getStringFromJNI"); if (!get_string_func) { fprintf(stderr, "获取函数指针失败: %s\n", dlerror()); dlclose(lib_handle); (*jvm)->DestroyJavaVM(jvm); return 1; } // 5. 调用JNI函数并转换结果 jstring j_result = get_string_func(env, clazz); const char* c_result = (*env)->GetStringUTFChars(env, j_result, NULL); if (c_result) { printf("JNI函数返回结果: %s\n", c_result); // 释放JNI字符串资源 (*env)->ReleaseStringUTFChars(env, j_result, c_result); } // 6. 清理所有资源 (*env)->DeleteLocalRef(env, clazz); (*env)->DeleteLocalRef(env, j_result); dlclose(lib_handle); (*jvm)->DestroyJavaVM(jvm); return 0; }
3. 完善Makefile
编译时需要链接JNI的库和动态链接库,记得替换成你自己的JAVA_HOME路径:
# 替换为你的Java安装路径,比如Linux下的openjdk路径 JAVA_HOME := /usr/lib/jvm/java-8-openjdk-amd64 JNI_INCLUDE := $(JAVA_HOME)/include JNI_INCLUDE_LINUX := $(JNI_INCLUDE)/linux all: exe # 编译命令:链接jvm库和动态链接库,引入JNI头文件 exe: main.c gcc -o exe main.c -L$(JAVA_HOME)/jre/lib/amd64/server -ljvm -ldl -I$(JNI_INCLUDE) -I$(JNI_INCLUDE_LINUX) # 清理生成文件 clean: rm -f exe
4. 关键注意事项
- 确保
libnative-lib.so和编译出的exe在同一目录,或者dlopen时指定完整路径 - 如果目标JNI函数是实例方法(非静态),第二个参数会是
jobject(类的实例),需要先通过(*env)->NewObject创建实例再调用 - 编译时必须指定正确的
JAVA_HOME,否则会找不到JNI的头文件和库文件
内容的提问来源于stack exchange,提问作者Axbor Axrorov
相关产品推荐
相关产品推荐

