You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:48:52