JNI(调用JNI函数)

Author Avatar
罗炜光 9月 29, 2016
  • 在其它设备中阅读本文章

示例程序运行顺序

  • 调用JNI本地函数
  • 访问静态成员获取值
  • 创建JniTest对象
  • 调用JniTest对象的方法
  • 传递返回值
  • 访问成员变量设定其值

JniFuncMain.java

public class JniFuncMain 
{
    private static int staticIntField = 300;

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

    //本地方法声明
    public static native JniTest createJniObject();

    public static void main(String[] args) 
    {
        //从本地代码生成JniTest对象
        System.out.println("[Java]createJniObject()调用本地方法");
        //使用static关键字,不需要创建对象,直接通过JniFuncMain类调用即可
        JniTest jniObj = createJniObject();

        //调用JniTest对象的方法
        jniObj.callTest();
    }

}

JniTest.java

public class JniTest 
{
    private int intField;

    //构造方法
    public JniTest(int num)
    {
        intField = num;
        System.out.println("[Java] 调用JniTest对象的构造方法:intField = " + intField);
    }

    //此方法由JNI本地函数调用
    public int callByNative(int num)
    {
        System.out.println("[Java] JniTest对象的callByNative(" + num + ")调用");
        return num;
    }

    public void callTest()
    {
        System.out.println("[Java] JniTest对象的callTest()方法调用:intField="+intField);
    }
}

JniFuncMain.h

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

#ifndef _Included_JniFuncMain
#define _Included_JniFuncMain
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniFuncMain
 * Method:    createJniObject
 * Signature: ()LJniTest;
 */
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

jnifunc.cpp

#include "JniFuncMain.h"
#include <stdio.h>

JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env,jclass clazz)
{
    jclass targetClass;
    jmethodID mid;
    jobject newObject;
    jstring helloStr;
    jfieldID fid;
    jint staticIntField;
    jint result;

    //获取JniFuncMain类的staticIntField变量值
    fid = env->GetStaticFieldID(clazz,"staticIntField","I");
    //读取jclass与fieldid指定的成员变量值
    staticIntField = env->GetStaticIntField(clazz,fid);
    printf("[CPP]获取JniFuncMain类的staticIntField变量值\n");
    printf("        JniFuncMain.staticIntField = %d\n", staticIntField);

    //查找生成对象的类
    targetClass = env->FindClass("JniTest");

    //查找构造方法
    mid = env->GetMethodID(targetClass,"<init>","(I)V");

    //生成JniTest对象(返回对象的引用)
    printf("[CPP]JniTest对象生成\n");
    newObject = env->NewObject(targetClass,mid,100);

    //调用对象的方法
    mid = env->GetMethodID(targetClass,"callByNative","(I)I");
    result = env->CallIntMethod(newObject,mid,200);

    //设置JniObject对象的intField值
    fid = env->GetFieldID(targetClass,"intField","I");
    printf("[CPP]设置JniObject对象的intField值为200\n");
    env->SetIntField(newObject,fid,result);

    //返回对象的引用
    return newObject;

}

编译命令

gcc -finput-charset=UTF-8 -fexec-charset=GBK -shared -o jnifunc.dll jnifunc.cpp

运行命令

java JniFuncMain

输出结果

[Java]createJniObject()调用本地方法
[CPP]获取JniFuncMain类的staticIntField变量值
                JniFuncMain.staticIntField = 300
[CPP]JniTest对象生成
[Java] 调用JniTest对象的构造方法:intField = 100
[Java] JniTest对象的callByNative(200)调用
[CPP]设置JniObject对象的intField值为200
[Java] JniTest对象的callTest()方法调用:intField=200

访问Java类/对象的成员变量

步骤:

  • 查找含待访问的成员变量的Java类的jclass值
  • 查找此类成员变量的jfieldID值
  • 使用jclass与jfieldID值,获取或设置成员变量值

若想要在本地代码中访问Java的成员变量,必须获取相应成员变量的ID值。
成员变量的ID保存在jfieldID类型的变量中。由于待读取数值的staticIntField成员变量是JniFuncMain类的静态成员变量,在获取staticIntField的ID时,应调用名称为GetStaticFieldID()的JNI函数
若想获取普通对象中的非静态成员变量的ID,应调用名称为GetFieldID()的JNI函数

获取jclass值

FindClass()

  • 形式:
    jclass FindClass(JNIEnv *env,const char *name)

  • 说明:
    查找name指定的Java类

  • 参数
    env JNI接口指针
    name 待查找的类名

  • 返回值
    返回类的jclass值

GetObjectClass()

  • 形式:
    jclass GetObjectClass(JNIEnv *env, jobject obj)

  • 功能:

  • 通过对象获取这个类。该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。

  • 参数:

    • env JNI 接口指针。
    • obj Java 类对象实例。

获取jfieldID值

GetStaticFieldID()

  • 形式:
    jfield GetStaticFieldID(JNIEnv *env,jclass clazz,const char *name,const *signature)

  • 说明:
    返回指定类的指定的静态成员变量的jfieldID的值

  • 参数:

    • env JNI接口指针
    • clazz 包含成员变量的类的jclass
    • name 成员变量名
    • signature 成员变量签名

GetFieldID()

  • 形式:
    jfield GetFieldID(JNIEnv *env,jclass clazz,const char *name,const *signature)

  • 说明:
    返回对象中指定的成员变量的jfieldID的值

  • 参数:

    • env JNI接口指针
    • clazz 包含成员变量的类的jclass
    • name 成员变量名
    • signature 成员变量签名

获取签名

在JNI中获取成员变量或成员方法签名

形式:javap [选项] ‘类名’
选项:

  • -s 输出Java签名
  • -p 输出所有类及成员
javap -s -p JniFuncMain

输出结果

Compiled from "JniFuncMain.java"
public class JniFuncMain {
  private static int staticIntField;
    descriptor: I   //签名
  public JniFuncMain();
    descriptor: ()V

  public static native JniTest createJniObject();
    descriptor: ()LJniTest;

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

  static {};
    descriptor: ()V
}

获取成员变量值

在获取成员变量所在的类的ID后,根据个成员变量的类型与存储区块,调用相应的JNI函数读取成员变量值即可。在JNI中有两种函数用来获取成员变量的值,分别为Get<type>Field函数与GetStatic<type>Field函数(<type>指Int、Char、Double等基本数据类型,具体参考JNI文档)

GetStatic<type>Field()

  • 形式:
    <jnitype> GetStatic<type>Field(JNIEnv *env,jclass clazz,jfieldID fieldID)

  • 说明:
    返回clazz类中ID为fieldID的静态变量的值

  • 参数:

    • env JNI接口指针
    • clazz 包含成员变量的类
    • fieldID 成员变量的ID
  • 参考:
    <type>指Object、Boolean、Byte、Char、Short、Int、Long、Float、Double九种基本类型。
    返回类型<jnitype>指jobject、jboolean、jbyte、jchar、jshort、jint、jlong、jfloat、jdouble九种基本类型
    这些类型也被应用到其他JNI函数的<type>

  • 返回值
    返回静态成员变量的值

Get<type>Field()

  • 形式:
    <jnitype> Get<type>Field(JNIEnv *env,jobject obj,jfieldID fieldID)

  • 说明:
    返回obj对象中ID为fieldID的成员变量的值

  • 参数:

    • env JNI接口指针
    • obj 包含成员变量的对象
    • fieldID 成员变量的ID
  • 返回值
    返回成员变量的值

生成Java对象

步骤:

  • 查找指定的类,并将查找到的类赋给jclass类型的变量
  • 查找Java类构造方法的ID值(类型为jmethodID)
  • 生成Java类对象

首先调用JNi函数FindClass(),查找生成对象的类。

获取jmethodID值

在JNI函数中有一个GetMethodID()函数用来获取指定类的指定方法ID.此函数除了可以用来获取指定类的构造方法的ID外,还可以获取类的其他方法的ID。若指定的方法是静态方法,则可以调用JNI函数中的GetStaticMethodID()函数,获取指定静态方法的ID

在生成指定类的对象之前,需要先调用GetMethodID()函数获取该类构造方法的ID。在调用GetMethodID()函数时,除了提供生成对象的类,还要提供类的构造方法名称(类的构造方法名称为“<init>”,其他非构造方法,直接提供方法名即可),以及构造方法的签名。

GetMethodID()

  • 形式:
    jmethodID GetMethodID(JNIEnv *env,jclass clazz,const char *name,const char *signature)

  • 说明:
    获取clazz类对象的指定方法的ID。注意,方法名(name)与签名应当保持一致。若获取类构造方法的ID,方法名为”<init>

  • 参数:

    • env JNI接口指针
    • clazz Java类
    • name 方法名
    • signature 方法签名
  • 返回值
    若方法ID错误,则返回NULL

生成Java类对象

以获得的JniTest类的jclass与构造方法的ID为参数,调用JNI函数NewObject(),生成JniTest类的对象。JniTest类的构造方法JniTest(int num)带有一个int类型的参数,在调用NewObject()时,同时传入100这个int数据。在生成JniTest类的对象后,将对象的引用保存在jobject变量中。

NewObject()

  • 形式:
    jobject NewObject(JNIEnv *env,jclass clazz,jmethodID methodID,...)

  • 说明:
    生成指定类的对象。methodID指类的构造方法的ID

  • 参数:

    • env JNI接口指针
    • clazz Java类
    • methodID 类的构造方法的ID
    • … 传递给类的构造方法的参数
  • 返回值
    放回类对象的引用。若发生错误,返回NULL

调用Java方法

步骤:

  • 获取含待调方法的Java类的jclass
  • 获取待调方法的ID
  • 调用Java方法保存返回值

CallStatic<type>Method()

  • 形式:
    <jnitype> CallStatic<type>Method(JNIEnv *env,jclass clazz,jmethodID methodID,...)

  • 说明:
    调用methodID指定的类的静态方法

  • 参数:

    • env JNI接口指针
    • clazz 含待调方法的类
    • methodID 待调方法的ID
    • … 传递给待调方法的参数
  • 返回值:
    被调方法的返回值

  • 参考:
    <type>指Object、Boolean、Byte、Char、Short、Int、Long、Float、Double、void十种基本类型。
    返回类型<jnitype>指jobject、jboolean、jbyte、jchar、jshort、jint、jlong、jfloat、jdouble、void十基本类型
    待调方法的返回值不同<type>也不同。若待调方法的返回值类型为int,则调用函数为CallStaticIntMethod()

Call<type>Method()

  • 形式:
    <jnitype> Call<type>Mrthod(JNIEnv *env,jobject obj,jmethodID method,...)

  • 说明:
    调用methodID指定的Java对象的方法

  • 参数:

    • env JNI接口指针
    • obj 含待调方法的Java对象的方法
    • methodID 待调方法的ID
    • … 传递给待调方法的参数
  • 返回值:
    被调方法的返回值

设置成员变量的值

步骤:

  • 获取成员变量所在类的jclass值
  • 获取对象的变量值
  • 设置变量值

SetStatic<type>Field()

  • 形式:
    void SetStatic<type>Field(JNIEnv *env,jclass clazz,jfieldID field,<type> value)

  • 说明:
    设置fieldID指定的Java类静态成员变量的值

  • 参数:

    • env JNI接口指针
    • clazz 含待设置成员变量的类的引用
    • fieldID 待设成员变量的ID
    • value 指定设置值

Set<type>Field()

  • 形式
    void Set<type>Field(JNIEnv *env,jobject obj,jfieldID field,<type> value)

  • 说明:
    设置fieldID指定的Java对象的成员变量的值

  • 参数:

    • env JNI接口指针
    • obj 含待设置成员变量的Java对象的引用
    • fieldID 待设成员变量的ID
    • value 指定设置值

局部引用与全局引用

在实现JNI本地函数时,由GetObjectClass()、FindClass()等JNI函数返回的jclass、jobject等引用都是局部引用。

局部引用是JNI默认的,它仅在JNI本地函数内才有效,即当JNI本地函数返回后,其内部的引用就会失效。

JNI提供了一个名称为NewGlobalRef()的JNI函数,用来为指定的类或对象生成全局引用(Global Reference),以便在JNI本地函数中在全局范围内使用该引用

NewGlobalRef()

  • 形式:
    jobject NewGlobalRef(JNIEnv *env,jobject obj)

  • 说明:
    为obj指定的类或对象,生成全局引用

  • 参数:

    • env JNI接口指针
    • obj 待生成全局引用的引用值
  • 返回生成的全局引用,若发生错误,则返回NULL

当全局引用使用完毕后,应当调用名称为DeleteGlobalRef()的JNI函数,显性地将全局引用销毁。

例子

#include "RefTestMain.h"

static jclass globalTargetClass = 0;

JNIEXPORT jint JNICALL Java_RefTestMain_getMember(JNIEnv *env,jclass clazz)
{
    jfieldID fid;
    jint intField;
    jclass targetClass;

    if(globalTargetClass == 0)
    {
        targetClass = env->FindClass("RefTest");
        globalTargetClass =(jclass) env->NewGlobalRef(targetClass);
    }

    fid = env->GetStaticFieldID(globalTargetClass,"intField","I");
    intField = env->GetStaticIntField(globalTargetClass,fid);

    return intField;
}

参考资料

Amdroid框架揭秘