java本地方法调用的流程-进阶

508 阅读7分钟

java本地方法调用如何传递参数给C函数

在Java中,本地方法调用是通过Java Native Interface(JNI)来实现的。使用JNI可以让Java代码与用其他编程语言(如C或C++)编写的代码进行交互。下面是一个简单的示例,展示了如何在Java中调用C函数并传递参数。

1. 编写Java代码

首先,定义一个包含本地方法的Java类:

public class JNIDemo {
    // 声明一个本地方法
    public native int add(int a, int b);

    // 加载本地库
    static {
        System.loadLibrary("nativeLib");
    }

    public static void main(String[] args) {
        JNIDemo demo = new JNIDemo();
        int result = demo.add(3, 4);
        System.out.println("Result: " + result);
    }
}

2. 生成头文件

编译Java代码并生成C/C++头文件:

javac JNIDemo.java
javah -jni JNIDemo

这将生成一个名为JNIDemo.h的头文件,其中包含了本地方法的声明。

3. 编写C代码

创建一个C文件,例如nativeLib.c,并实现头文件中声明的方法:

#include <jni.h>
#include "JNIDemo.h"

JNIEXPORT jint JNICALL Java_JNIDemo_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
}

4. 编译C代码

根据目标平台编译C代码生成共享库。例如,在Linux上可以使用以下命令:

gcc -shared -o libnativeLib.so -fPIC nativeLib.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux

在Windows上,可以使用类似的命令生成DLL文件。

5. 运行Java代码

确保共享库位于Java能找到的路径中,然后运行Java程序:

java JNIDemo

如果所有步骤都正确执行,那么程序将输出计算结果:

Result: 7

java本地方法调用访问静态域

在Java中,本地方法可以访问和修改类的静态成员(即静态域)。通过使用JNI,您可以在本地代码中操作这些静态变量。以下是一个详细的示例,展示了如何在Java中调用本地方法并访问静态域。

步骤1: 编写Java类

首先,定义一个包含静态变量和本地方法的Java类:

public class JNIDemo {
    // 定义一个静态变量
    private static int staticVar = 42;

    // 声明一个本地方法来获取静态变量的值
    public native int getStaticVar();

    // 声明一个本地方法来设置静态变量的值
    public native void setStaticVar(int value);

    // 加载本地库
    static {
        System.loadLibrary("nativeLib");
    }

    public static void main(String[] args) {
        JNIDemo demo = new JNIDemo();
        System.out.println("Initial staticVar: " + demo.getStaticVar());

        demo.setStaticVar(84);
        System.out.println("Updated staticVar: " + demo.getStaticVar());
    }
}

步骤2: 生成头文件

编译Java代码并生成对应的C/C++头文件:

javac JNIDemo.java
javah -jni JNIDemo

这将生成一个名为JNIDemo.h的头文件,其中包含本地方法的声明。

步骤3: 实现C/C++代码

创建一个C文件,例如nativeLib.c,并实现头文件中声明的方法:

#include <jni.h>
#include "JNIDemo.h"

// 获取静态变量的值
JNIEXPORT jint JNICALL Java_JNIDemo_getStaticVar(JNIEnv *env, jobject obj) {
    // 获取JNIDemo类的引用
    jclass cls = (*env)->FindClass(env, "JNIDemo");
    if (cls == NULL) {
        return -1; // 如果类未找到则返回错误
    }

    // 获取静态变量ID:第四个参数 `"I"` 是字段描述符(field descriptor)
    jfieldID fid = (*env)->GetStaticFieldID(env, cls, "staticVar", "I");
    if (fid == NULL) {
        return -1; // 如果变量ID未找到则返回错误
    }

    // 获取静态变量的值
    jint staticVar = (*env)->GetStaticIntField(env, cls, fid);
    return staticVar;
}

// 设置静态变量的值
JNIEXPORT void JNICALL Java_JNIDemo_setStaticVar(JNIEnv *env, jobject obj, jint value) {
    // 获取JNIDemo类的引用
    jclass cls = (*env)->FindClass(env, "JNIDemo");
    if (cls == NULL) {
        return; // 如果类未找到则返回
    }

    // 获取静态变量ID
    jfieldID fid = (*env)->GetStaticFieldID(env, cls, "staticVar", "I");
    if (fid == NULL) {
        return; // 如果变量ID未找到则返回
    }

    // 设置静态变量的值
    (*env)->SetStaticIntField(env, cls, fid, value);
}

步骤4: 编译C代码

将C代码编译成共享库。例如,在Linux上可以使用以下命令:

gcc -shared -o libnativeLib.so -fPIC nativeLib.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux

在Windows上,可以使用类似的命令生成DLL文件。

步骤5: 运行Java代码

确保共享库位于Java程序能够找到的路径中,然后运行Java程序:

java JNIDemo

如果所有步骤都正确执行,那么程序应输出如下内容:

Initial staticVar: 42
Updated staticVar: 84

总结

通过上述步骤,您可以在Java中使用JNI调用本地方法,并在本地代码中访问和修改Java类的静态变量。本示例演示了如何使用JNIEnv提供的函数来获取和设置静态变量,包括:

  • FindClass:查找Java类。
  • GetStaticFieldID:获取静态变量的字段ID。
  • GetStaticIntField:获取静态变量的值。
  • SetStaticIntField:设置静态变量的值。

通过这种方式,您可以在本地方法中与Java的静态成员交互。

示例解释

jfieldID fid = (*env)->GetStaticFieldID(env, cls, "staticVar", "I");

这行代码的作用是获取 Java 类 JNIDemo 中名为 staticVar 的静态整数字段的字段 ID(fieldID)。参数 "I" 指定了该字段的类型为 int

下面列出一些常见的字段描述符及其对应的Java类型:

字段描述符对应的Java类型
Iint
Zboolean
Bbyte
Cchar
Sshort
Jlong
Ffloat
Ddouble
Ljava/lang/String;java.lang.String
[Iint[]

java本地方法调用访问静态方法

步骤1: 编写Java类

首先,定义一个包含静态方法的Java类:

public class JNIDemo {
    // 定义一个静态方法
    public static void staticMethod(String message) {
        System.out.println("Message from Java: " + message);
    }

    // 声明一个本地方法来调用静态方法
    public native void callStaticMethodFromNative();

    // 加载本地库
    static {
        System.loadLibrary("nativeLib");
    }

    public static void main(String[] args) {
        JNIDemo demo = new JNIDemo();
        demo.callStaticMethodFromNative();  // 调用本地方法
    }
}

步骤2: 生成头文件

编译Java代码并生成对应的C/C++头文件:

javac JNIDemo.java
javah -jni JNIDemo

这将生成一个名为JNIDemo.h的头文件,其中包含本地方法的声明。

步骤3: 实现C/C++代码

创建一个C文件,例如nativeLib.c,并实现头文件中声明的方法:

#include <jni.h>
#include "JNIDemo.h"

JNIEXPORT void JNICALL Java_JNIDemo_callStaticMethodFromNative(JNIEnv *env, jobject obj) {
    // 获取JNIDemo类引用
    jclass cls = (*env)->FindClass(env, "JNIDemo");
    if (cls == NULL) {
        return;  // 类未找到,直接返回
    }

    // 获取静态方法ID
    jmethodID mid = (*env)->GetStaticMethodID(env, cls, "staticMethod", "(Ljava/lang/String;)V");
    if (mid == NULL) {
        return;  // 方法未找到,直接返回
    }

    // 创建一个Java字符串参数
    jstring message = (*env)->NewStringUTF(env, "Hello from Native Code");

    // 调用静态方法
    (*env)->CallStaticVoidMethod(env, cls, mid, message);

    // 释放局部引用
    (*env)->DeleteLocalRef(env, message);
}

步骤4: 编译C代码

将C代码编译成共享库。例如,在Linux上可以使用以下命令:

gcc -shared -o libnativeLib.so -fPIC nativeLib.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux

在Windows上,可以使用类似的命令生成DLL文件:

gcc -shared -o nativeLib.dll -I"%JAVA_HOME%/include" -I"%JAVA_HOME%/include/win32" nativeLib.c

步骤5: 运行Java代码

确保共享库位于Java程序能够找到的路径中,然后运行Java程序:

java JNIDemo

如果所有步骤都正确执行,那么程序应输出如下内容:

Message from Java: Hello from Native Code

总结

通过上述步骤,您可以在Java中使用JNI调用本地方法,并从本地代码中调用Java类的静态方法。本示例演示了如何使用JNIEnv提供的函数来进行此操作,包括:

  • FindClass:查找Java类。
  • GetStaticMethodID:获取静态方法的ID。
  • NewStringUTF:创建一个新的Java字符串。
  • CallStaticVoidMethod:调用静态Java方法。
  • DeleteLocalRef:释放本地引用。

通过理解这些函数的用法,您可以在本地代码中与Java类的静态方法进行交互,从而实现在本地代码与Java代码之间的数据传递和方法调用。

示例解释

jmethodID mid = (*env)->GetStaticMethodID(env, cls, "staticMethod", "(Ljava/lang/String;)V");

主要组成部分

  1. (env, cls, "staticMethod", "(Ljava/lang/String;)V")

    • 这些是传递给 GetStaticMethodID 函数的参数。

各个参数解释

  1. env:

    • JNIEnv *env 是JNI环境指针,是所有JNI函数调用的入口,通过这个指针可以调用JNI提供的一系列函数。
  2. cls:

    • jclass cls 是Java类的表示。在前面的代码中,cls 是通过 FindClass 函数获取到的 Java 类 JNIDemo 的引用。
    jclass cls = (*env)->FindClass(env, "JNIDemo");
    
  3. "staticMethod" :

    • 这是方法的名称。在这种情况下,它是 staticMethod,即Java类中的静态方法名。
    public static void staticMethod(String message) {
        System.out.println("Message from Java: " + message);
    }
    
  4. "(Ljava/lang/String;)V" :

    • 这是方法的签名(Signature),描述了方法的参数和返回类型。

    • 签名的格式遵循JNI规范

    • (Ljava/lang/String;)V 分成以下几个部分:

      • ( 和 ) 包含了方法的参数类型。
      • Ljava/lang/String; 表示第一个参数是 java.lang.String 类型。L 表示对象类型,后面跟着类的完全限定名,最后以 ; 结尾。
      • V 表示方法的返回类型是 void

方法签名示例解析

  • ()V: 无参数,返回值为 void
  • (I)V: 一个int参数,返回值为 void
  • (Ljava/lang/String;)V: 一个 java.lang.String 参数,返回值为 void
  • (Ljava/lang/String;I)V: 一个 java.lang.String 参数,一个 int 参数,返回值为 void
  • ([I)V: 一个 int[] 参数,返回值为 void

综上所述

在 env 指定的JNI环境中查找 cls 类中名为 staticMethod,参数为 java.lang.String,返回类型为 void 的静态方法。