JNI C++和JAVA交互

1.C++层调用Java:

在C++中调用Java方法是通过JNI提供的接口实现的。这种调用的过程涉及以下几个步骤:

  1. 获取JNIEnv指针:这是调用Java方法的核心接口。
  2. 定位Java类:通过JNI提供的方法,找到你要调用的方法所在的Java类。
  3. 获取方法ID:通过方法名和签名来获取Java方法的ID。
  4. 调用Java方法:使用获取的方法ID来调用相应的Java方法。
  5. 处理返回值:如果Java方法有返回值,处理它。

下面是一个具体的例子,演示如何在C++中调用Java方法。

示例场景

假设我们有一个Java类 JavaClass,其中包含一个方法 sayHello 和一个静态方法 addNumbers

Java代码
java 复制代码
package com.example;

public class JavaClass {
    public void sayHello() {
        System.out.println("Hello from Java!");
    }

    public static int addNumbers(int a, int b) {
        return a + b;
    }
}

C++代码调用Java方法

在C++代码中,我们要调用JavaClass类的 sayHello 实例方法和 addNumbers 静态方法。

C++代码
cpp 复制代码
#include <jni.h>
#include <iostream>

// 一个例子函数,用于调用Java方法
void callJavaMethod(JNIEnv *env) {
    // 1. 获取Java类的引用
    jclass javaClass = env->FindClass("com/example/JavaClass");
    if (javaClass == nullptr) {
        std::cerr << "Failed to find Java class" << std::endl;
        return;
    }

    // 2. 调用实例方法 sayHello
    // 获取方法ID
    jmethodID sayHelloMethod = env->GetMethodID(javaClass, "sayHello", "()V");
    if (sayHelloMethod == nullptr) {
        std::cerr << "Failed to find method sayHello" << std::endl;
        return;
    }

    // 创建Java类的实例
    jobject javaObject = env->AllocObject(javaClass);
    if (javaObject == nullptr) {
        std::cerr << "Failed to create Java object" << std::endl;
        return;
    }

    // 调用实例方法
    env->CallVoidMethod(javaObject, sayHelloMethod);

    // 3. 调用静态方法 addNumbers
    // 获取静态方法ID
    jmethodID addNumbersMethod = env->GetStaticMethodID(javaClass, "addNumbers", "(II)I");
    if (addNumbersMethod == nullptr) {
        std::cerr << "Failed to find static method addNumbers" << std::endl;
        return;
    }

    // 调用静态方法
    jint result = env->CallStaticIntMethod(javaClass, addNumbersMethod, 3, 7);
    std::cout << "Result from Java addNumbers: " << result << std::endl;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // 在加载时调用Java方法
    callJavaMethod(env);

    return JNI_VERSION_1_6;
}

详细解析

  1. 获取Java类的引用 :使用FindClass方法来查找Java类。类名需要使用完整的包名,且用斜杠/分隔。

  2. 获取方法ID

    • 使用GetMethodID来获取实例方法的ID。需要提供方法名和方法签名。
    • 使用GetStaticMethodID来获取静态方法的ID。签名格式为(参数类型)返回类型,例如(II)I表示两个int参数,返回一个int
  3. 创建Java类的实例 :使用AllocObject创建一个Java对象实例。然后,使用CallVoidMethod调用实例方法。

  4. 调用静态方法 :直接使用CallStaticIntMethod调用静态方法,并传递参数。

  5. 处理返回值 :静态方法addNumbers返回一个int,所以C++代码中捕获并输出这个结果。

注意事项

  • 确保正确处理异常。使用ExceptionCheckExceptionOccurred来检查Java方法调用中是否出现异常。
  • 调用完成后释放资源,比如释放局部引用DeleteLocalRef

通过这些步骤,你可以在C++代码中灵活地调用Java方法,利用JNI在两个语言之间传递数据和功能。

2.java层调用C++层:

java层声明native函数,这个函数由Jni(C++)层实现,java层是如何通过native函数找到C++层的函数的?

需要把C++这些函数要注册到jni中,jni方法注册分为静态注册和动态注册:

1. 静态注册

通过Java中的native方法与C/C++代码中的JNI函数名称相匹配来实现的。静态注册要求C++代码中的函数名称与Java类中的native方法按照一定的规则命名,并自动进行绑定。这就是"静态"的含义,不需要额外的注册代码。

静态注册有明显缺点的:

  • JNI层的函数名太长;
  • 声明Native方法的类需要用javah工具生成头文件;
  • 第一次调用Native方法需要建立关联,影响运行效率;

2.JNI 动态注册

JNI 动态注册是通过在运行时将 Java native 方法与本地(C/C++)代码中的方法进行绑定。与静态注册不同,动态注册不要求本地函数名称遵循特定的命名规则,允许你在代码中手动注册方法。这种方式提供了更大的灵活性,尤其是在处理大型项目或多模块项目时非常有用。

动态注册的工作流程

  1. 定义 Java native 方法 :在 Java 类中声明 native 方法,但不需要关心 C/C++ 代码中的函数名称。

  2. 实现 JNI_OnLoad 函数 :在加载本地库时,JVM 会调用 JNI_OnLoad 函数。在这个函数中,你可以注册需要与 native 方法绑定的 C/C++ 函数。

  3. 使用 RegisterNatives 函数注册本地方法 :通过 RegisterNatives 函数将 Java 的 native 方法与对应的 C/C++ 函数进行绑定。

动态注册的例子

Java 代码:
java 复制代码
package com.example;

public class MyClass {
    static {
        System.loadLibrary("mylib"); // 加载本地库
    }

    // 声明两个native方法
    public native int addNumbers(int a, int b);
    public native String getGreeting(String name);

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        int result = obj.addNumbers(5, 10);
        System.out.println("Result: " + result);
        
        String greeting = obj.getGreeting("John");
        System.out.println("Greeting: " + greeting);
    }
}
C/C++ 代码:
c 复制代码
#include <jni.h>
#include <string.h>

// 实现两个本地方法
jint addNumbers(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
}

jstring getGreeting(JNIEnv *env, jobject obj, jstring name) {
    const char *nameStr = (*env)->GetStringUTFChars(env, name, 0);
    char greeting[100];
    sprintf(greeting, "Hello, %s!", nameStr);
    (*env)->ReleaseStringUTFChars(env, name, nameStr);
    return (*env)->NewStringUTF(env, greeting);
}

// 定义方法数组,将Java方法名和C函数进行绑定
static JNINativeMethod methods[] = {
    {"addNumbers", "(II)I", (void *)addNumbers},
    {"getGreeting", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getGreeting}
};

// 在JNI_OnLoad中注册这些本地方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // 获取Java类
    jclass cls = (*env)->FindClass(env, "com/example/MyClass");
    if (cls == NULL) {
        return JNI_ERR;
    }

    // 注册本地方法
    if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

相关推荐
过过过呀Glik36 分钟前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
小灰灰要减肥38 分钟前
装饰者模式
java
张铁铁是个小胖子1 小时前
MyBatis学习
java·学习·mybatis
蜀黍@猿1 小时前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧4091 小时前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
Yan.love2 小时前
开发场景中Java 集合的最佳选择
java·数据结构·链表
zh路西法2 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
椰椰椰耶2 小时前
【文档搜索引擎】搜索模块的完整实现
java·搜索引擎
大G哥2 小时前
java提高正则处理效率
java·开发语言
轩辰~2 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议