JNI知识点总结

JNI开发中静态和动态注册

JNI(Java Native Interface)是Java平台提供的一种机制,用于在Java程序中调用本地代码(例如C/C++代码)。 JNI中涉及两种类型的方法注册:静态注册和动态注册。

静态注册是指将本地方法的名称和实现直接映射到Java类的静态方法。这可以通过在C/C++代码中使用特定命名模式(例如Java_com_example_MyClass_myMethod)来实现。然后,可以使用System.loadLibrary()方法在Java代码中加载本地库,并直接调用本地方法。

动态注册是指在Java代码中使用JNI提供的一组API动态地注册本地方法。使用动态注册,可以在运行时加载本地库,并将方法名称和实现与Java类的实例方法绑定。这样,在Java代码中创建类实例时,可以直接调用本地方法。

静态注册通常比动态注册更快,因为它可以避免在Java代码中使用JNI API注册本地方法。但是,它也有一些缺点。例如,由于本地方法的名称必须遵循特定的命名约定,因此它可能不太灵活。此外,如果在运行时加载本地库时发生错误,将无法捕获它。

动态注册通常更灵活,因为它可以在运行时动态加载本地库并动态注册方法。此外,它也可以避免命名约定的限制。但是,它也比静态注册慢,因为它涉及在Java代码中使用JNI API注册本地方法。

假设有以下Java类:

public class MyNativeClass {
    static {
        System.loadLibrary("MyNativeLibrary");
    }
    
    public static native void myStaticMethod();
    
    public native void myInstanceMethod();
}

此Java类具有两种本地方法:静态方法myStaticMethod和实例方法myInstanceMethod。这两个方法的实现将在本地库"MyNativeLibrary"中定义。

下面是静态注册和动态注册的具体示例:

静态注册

假设我们使用以下C/C++代码实现本地方法:

JNIEXPORT void JNICALL Java_com_example_MyNativeClass_myStaticMethod(JNIEnv *env, jclass clazz) {
    // 实现静态方法
}
 
JNIEXPORT void JNICALL Java_com_example_MyNativeClass_myInstanceMethod(JNIEnv *env, jobject obj) {
    // 实现实例方法
}

在静态注册中,必须使用特定的命名约定将本地方法名称映射到Java类的静态方法。在本例中,我们使用的是"Java_com_example_MyNativeClass_myStaticMethod"和"Java_com_example_MyNativeClass_myInstanceMethod"。这些方法必须在本地库中实现。

然后,在Java代码中,我们可以直接调用静态方法myStaticMethod(),如下所示:

MyNativeClass.myStaticMethod();

动态注册

在动态注册中,我们必须在Java代码中使用JNI API动态注册本地方法。以下是动态注册的示例代码:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    jclass cls;
    JNINativeMethod methods[] = {
        {"myInstanceMethod", "()V", (void*) myInstanceMethod}
    };
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    cls = (*env)->FindClass(env, "com/example/MyNativeClass");
    if (cls == NULL) {
        return JNI_ERR;
    }
    if ((*env)->RegisterNatives(env, cls, methods, 1) < 0) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNICALL myInstanceMethod(JNIEnv *env, jobject obj) {
    // 实现实例方法
}

首先,我们实现了JNI_OnLoad()方法,在其中使用JNI API动态注册本地方法。在此示例中,我们只注册了一个实例方法myInstanceMethod()。

然后,在Java代码中,我们可以创建MyNativeClass的实例并调用本地方法myInstanceMethod(),如下所示:

MyNativeClass obj = new MyNativeClass();
obj.myInstanceMethod();

总体而言,静态注册和动态注册都可以实现在Java代码中调用本地方法。静态注册更简单,但有一些限制。动态注册更灵活,但需要在Java代码中使用JNI API注册本地方法。

jni的动态注册能解决什么问题

动态注册是JNI的一个功能,它允许我们在C/C++代码中动态地将本地方法与Java类绑定在一起。相对于静态注册,动态注册具有以下优势:

  1. 灵活性更高:使用动态注册,我们可以在程序运行时动态地添加或删除本地方法,而不需要重新编译整个程序。

  2. 可移植性更好:使用静态注册时,我们需要手动编写Java本地接口(JNI)代码,并将其与C/C++代码一起编译成共享库。这会导致库在不同的操作系统和编译器上表现不一致。而使用动态注册,我们只需要将C/C++代码编译成共享库,然后在Java代码中加载该库即可。

  3. 减少代码量:使用静态注册时,我们需要为每个本地方法编写一个Java本地接口(JNI)代码。而使用动态注册,我们可以使用同一个JNI接口方法来处理多个本地方法,从而减少代码量。

jni中方法签名有哪些。怎么选用

在JNI中,方法签名是指用来标识一个方法的唯一字符串,它包括了方法的名称、返回类型和参数列表。方法签名的格式为:(参数类型1, 参数类型2, ... )返回类型,其中参数类型和返回类型都使用Java的类型描述符来表示。

下面是一些常见的Java类型描述符:

  • Z:boolean类型
  • B:byte类型
  • C:char类型
  • S:short类型
  • I:int类型
  • J:long类型
  • F:float类型
  • D:double类型
  • L类名;:引用类型
  • [:数组类型

例如,java.lang.String的类型描述符为Ljava/lang/String;int[]的类型描述符为[I

在JNI中,有几种常见的方法签名格式,如下:

  • (JNIEnv *, jclass):静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jclass对象,表示Java类对象。
  • (JNIEnv *, jobject):非静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jobject对象,表示Java对象。
  • (JNIEnv *, jobject, ...)返回类型:非静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jobject对象,表示Java对象,后面跟着一系列方法参数,最后是返回类型。
  • (JNIEnv *, jclass, ...)返回类型:静态方法的签名,参数列表中的第一个参数是JNIEnv指针,第二个参数是jclass对象,表示Java类对象,后面跟着一系列方法参数,最后是返回类型。

在选择方法签名时,需要注意以下几点:

  • 参数类型和返回类型需要使用Java类型描述符来表示,不同类型之间使用逗号隔开。
  • 方法名需要与Java中的方法名保持一致,大小写敏感。
  • 对于非静态方法,需要在参数列表中添加一个jobject对象,表示Java对象。对于静态方法,需要在参数列表中添加一个jclass对象,表示Java类对象。
  • 参数类型和返回类型应该与Java代码中的类型保持一致。

通常情况下,可以通过在Java代码中使用javap命令来获取方法的签名,然后在JNI代码中使用对应的签名来实现方法的调用。

使用javap命令

在Java中,可以使用javap命令来查看一个类的方法签名。具体步骤如下:

  1. 在命令行中打开终端或命令提示符。

  2. 切换到包含要查看方法签名的类的目录。

  3. 输入以下命令:

    javap -s <class name>
    

    其中,<class name>是要查看方法签名的类的名称,不包括.class后缀名。

    例如,如果要查看MyClass类的方法签名,可以输入以下命令:

    javap -s MyClass
    
  4. 然后会输出MyClass类的所有方法及其签名。

    例如:

    public static void main(java.lang.String[]);
      descriptor: ([Ljava/lang/String;)V
    
    public void hello();
      descriptor: ()V
    

    其中,descriptor后面的字符串就是方法的签名。

注意,在使用javap命令时,需要确保编译器已经编译了该类,并且该类的.class文件已经存在。如果该类还没有被编译,可以先使用javac命令进行编译。

另外,如果要查看一个类的所有继承方法和接口方法的签名,可以在javap命令中加上-s选项。

JNI和java通讯的原理是什么

JNI(Java Native Interface)是Java提供的一种与本地代码交互的机制。它提供了一组标准的接口和规范,使得Java应用程序可以调用本地库中的函数,并且本地库中的函数也可以调用Java应用程序中的函数。JNI的原理可以简单地概括为以下三个步骤:

  1. Java应用程序通过JNI调用本地库中的函数。在调用之前,Java应用程序需要使用System.loadLibrary()System.load()方法加载本地库。然后,Java应用程序通过JNI提供的接口将参数传递给本地库中的函数,并且将本地库中函数的返回值传递回Java应用程序。

  2. 本地库中的函数接收到Java应用程序传递的参数后,通过JNI提供的接口将参数转换为本地代码可以处理的格式,并且调用本地代码中的函数进行处理。

  3. 本地代码中的函数处理完毕后,将结果返回给本地库中的函数。本地库中的函数将结果转换为Java应用程序可以处理的格式,并且将结果返回给Java应用程序。

总体来说,JNI的原理就是通过提供一组标准的接口和规范,使得Java应用程序可以调用本地库中的函数,并且本地库中的函数也可以调用Java应用程序中的函数,从而实现Java和本地代码之间的交互。

jni中本地引用和全局引用

JNI(Java Native Interface)中有两种引用类型:本地引用(local reference)和全局引用(global reference)。本地引用和全局引用的主要区别在于它们的生命周期和作用域范围。

本地引用是指在本地方法中创建的引用。它们只在本地方法的执行期间有效,并且不能被跨越多个本地方法调用使用。当本地方法返回时,所有本地引用都会自动失效,并且对应的内存资源也会被回收。如果在本地方法中创建的本地引用不及时释放,就会导致内存泄漏。

全局引用是指可以在Java虚拟机的整个生命周期中使用的引用。它们是通过调用JNI提供的函数NewGlobalRef()来创建的。全局引用可以被多个本地方法使用,并且在整个Java虚拟机生命周期内都有效。当不再需要全局引用时,可以通过调用JNI提供的函数DeleteGlobalRef()来释放对应的内存资源。

一般来说,当需要在多个本地方法中使用同一个对象时,可以将其转换为全局引用。同时,在创建本地引用时,应该尽可能地减少其数量,以避免内存泄漏的风险。在使用完本地引用后,应该及时将其释放掉,以释放对应的内存资源。

举个实例

假设有一个Java类MyClass,其中有一个本地方法nativeMethod()。在该方法中,需要使用一个本地引用来引用一个Java对象。同时,由于该对象需要在多个本地方法中使用,因此需要将其转换为全局引用。

java

public class MyClass {
    private Object obj;

    public native void nativeMethod();

    public void setObj(Object obj) {
        this.obj = obj;
    }

    public Object getObj() {
        return obj;
    }
}

在实现nativeMethod()方法的本地代码中,需要使用一个本地引用来引用MyClass对象的obj属性,并且将其转换为全局引用。可以使用以下代码来实现:

c

JNIEXPORT void JNICALL Java_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 获取MyClass对象的obj属性
    jfieldID fid = (*env)->GetFieldID(env, (*env)->GetObjectClass(env, obj), "obj", "Ljava/lang/Object;");
    jobject localRef = (*env)->GetObjectField(env, obj, fid);

    // 将本地引用转换为全局引用
    jobject globalRef = (*env)->NewGlobalRef(env, localRef);

    // 在这里使用全局引用

    // 释放全局引用
    (*env)->DeleteGlobalRef(env, globalRef);
}

在以上代码中,GetObjectClass()函数用于获取MyClass对象的类引用,GetFieldID()函数用于获取obj属性的ID,GetObjectField()函数用于获取obj属性的值,并将其存储在本地引用localRef中。接下来,可以使用NewGlobalRef()函数将本地引用转换为全局引用,并在代码中使用它。最后,通过DeleteGlobalRef()函数释放全局引用的内存资源。

JNI和JNA的区别是什么
JNI(Java Native Interface)和JNA(Java Native Access)都是Java程序与本地代码进行交互的技术。

JNI是Java平台提供的一种机制,它可以使Java程序调用C/C++等本地语言编写的代码,并且可以将Java对象传递到本地代码中。JNI需要开发者手动编写一些本地代码,并且需要进行编译和链接。

JNA是一个开源项目,它提供了一种更为简单的方式,使Java程序可以直接访问本地代码。JNA通过Java反射机制实现本地函数的调用,开发者不需要编写任何本地代码,只需要定义本地函数的接口并提供本地库的名称即可。JNA还支持结构体、指针等复杂类型的传递,并且可以自动进行内存管理。

因此,JNI相对而言更加灵活,可以进行更细粒度的控制,但需要更多的开发工作;而JNA则更为简单,开发效率更高,但可能会牺牲一些灵活性和性能。选择何种技术应该根据具体的需求和场景来决定。

举一个JNA实现实例
以下是一个使用JNA实现本地函数调用的简单示例:

假设有一个本地库libmymath.so,其中定义了一个函数add,用于计算两个整数的和。

创建Java接口,定义本地函数的接口。

import com.sun.jna.Library;
import com.sun.jna.Native;

public interface MyMath extends Library {
    MyMath INSTANCE = (MyMath) Native.load("mymath", MyMath.class);

    int add(int a, int b);
}

加载本地库,获取接口实例。

MyMath myMath = MyMath.INSTANCE;

调用本地函数。

int result = myMath.add(1, 2);

在这个示例中,我们使用JNA加载了本地库mymath,并定义了一个接口MyMath,其中add函数的参数和返回值类型与本地库中的函数保持一致。在Java代码中,我们可以通过MyMath.INSTANCE获取接口实例,并直接调用add函数来进行本地函数调用。

JNA不是Android官方的

JNA不是Android官方实现的技术,它是一个独立的开源项目。在Android开发中,通常使用的是Android NDK(Native Development Kit)提供的JNI技术来实现Java和本地代码的交互。NDK是Android官方提供的工具集,可以帮助开发者将本地代码集成到Android应用中。

虽然JNA在Android开发中并不常用,但是在其他平台(如Windows、Linux等)上,JNA广泛应用于Java和本地代码的交互。JNA提供了一种更为简单和便捷的方式来进行本地函数调用,相对于JNI更加容易上手和使用,因此得到了不少开发者的青睐。

如何快速生成native方法

可以通过Java SE提供的javah工具快速生成native方法。

javah命令可以根据Java类的字节码文件,生成对应的C/C++头文件,以便我们在本地代码中实现native方法。具体步骤如下:

  1. 编写Java类,在其中定义native方法。
public class MyClass {
    public native void nativeMethod();
}
  1. 在命令行中切换到Java类所在目录下,使用javac命令编译Java类。
javac MyClass.java
  1. 使用javah命令生成C/C++头文件。
javah MyClass

执行上述命令后,会在当前目录下生成一个名为MyClass.h的头文件,其中包含了Java类中定义的native方法的函数原型。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class MyClass */

#ifndef _Included_MyClass
#define _Included_MyClass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     MyClass
 * Method:    nativeMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_MyClass_nativeMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  1. 在本地代码中实现native方法。

在生成的头文件中,可以找到对应native方法的函数原型。我们可以根据函数原型在本地代码中实现native方法,并使用System.loadLibrary加载对应的本地库。

例如,在C/C++代码中实现nativeMethod函数的示例代码如下:

#include "MyClass.h"

JNIEXPORT void JNICALL Java_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
    // 实现native方法的代码
}

通过以上步骤,我们就可以在Java类中使用定义的native方法,并在本地代码中实现native方法。

posted @ 2023-03-29 00:01  懒懒初阳  阅读(162)  评论(0编辑  收藏  举报