`
iaiai
  • 浏览: 2144498 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JNI调用java中的类方法和静态方法

 
阅读更多
在JNI调用中,肯定会涉及到本地方法操作Java类中数据和方法。在Java1.0中“原始的”Java到C的绑定中,程序员可以直接访问对象数据域。然而,直接方法要求虚拟机暴露他们的内部数据布局,基于这个原因,JNI要求程序员通过特殊的JNI函数来获取和设置数据以及调用java方法。
一、取得代表属性和方法的jfieldID和jmethodID
为了在C/C++中表示属性和方法,JNI在jni.h头文件中定义了jfieldID和jmethodID类型来分别代表Java端的属性和方法。我们在访问或是设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfieldID,然后才能在本地代码进行Java属性操作。同样的,我们需要调用Java端的方法时,也是需要取得代表该方法的jmethodID才能进行Java方法调用。
使用JNIEnv提供的JNI方法,我们就可以获得属性和方法相对应的jfieldID和jmethodID:
  • GetFieldID   :取得成员变量的id
  • GetStaticFieldID  :取得静态成员变量的id
  • GetMethodID  :取得方法的id
  • GetStaticMethodID :取得静态方法的id

下面看这个方法的原型
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)   
  
jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig)  
  
jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig)  
  
jmethodID GetMethodID(jclass clazz, const char *name,const char *sig)

可以看到这四个方法的参数列表都是一模一样的,下面来分析下每个参数的含义:
第一个参数jclass clazz :
上篇讲到jclass,相当于Java中的Class类,代表一个Java类,而这里面的代表的就是我们操作的Class类,我们要从这个类里面取的属性和方法的ID。
第二个参数const char *name
这是一个常量字符数组,代表我们要取得的方法名或者变量名。
第三个参数const char *sig
这也是一个常量字符数组,代表我们要取得的方法或变量的 签名。
什么是方法或者变量的签名呢?
看下面的例子:
下面的类中,有一个本地方法,和两个重载的show方法。
public class NativeTest {  
    public native void showNative();  
    public void show(int i){  
        System.out.println(i);  
    }  
    public void show(double d){  
        System.out.println(d);  
    }  
}

那么,我们在本地方法中调用其中的某一个show方法:
jclass clazz_NativeTest=env->FindClass("cn/itcast/NativeTest");
//取得jmethodID
jmethodID id_show=env->GetMethodID(clazz_NativeTest,“show”,"???");

那这样取得的jmethodID到底是哪个show方法呢?所以这就是第三个参数sig的作用,sig其实是单词sign的缩写,它用于指定要取得的属性或方法的类型。
这里的sig如果指定为"(I)V",则返回void show(int)方法的jmethodID。如果指定为"(D)V",则返回void show(double)方法的jmethodID。
如何签名:
下面看看Sign签名如何写,来表示要取得的属性或方法的类型。
1、普通类型签名
    boolean     Z
    byte           B
    char          C
    short         S
    int              I
    long          L
    float          F
    double      D
   void            V
2、引用类型签名
object     L开头,然后以/ 分隔包的完整类型,后面再加;   比如说String    签名就是   Ljava/lang/String;
Array      以[ 开头,在加上数组元素类型的签名            比如int[]   签名就是[I       ,在比如int[][] 签名就是[[I      ,object数组签名就是[Ljava/lang/Object;
3、方法签名
(参数1类型签名 参数2类型签名 参数3类型签名  .......)返回值类型签名
还要注意,就算java构造器没返回值,也加上V签名

由于签名比较难以记忆,JDK提供了一个工具javap来查看一个类的声明。其中就可以设置输出每个方法/属性的签名。
javap -s <options> className
-s 表示是签名
options 可以使-private  -protected -public 用于选择性的输出private 或protected 或 public声明的方法/属性。

二、根据获取的ID,来取得和设置属性,以及调用方法。
取得了代表属性和方法的ID,就可以利用JNIEnv提供的方法来进行下一步的操作了。
1、如何获得和设置属性/静态属性
取得了代表属性和静态属性的jfieldID,就可以用在JNIEnv中提供的一系列的方法,来获取和设置属性/静态属性。
获取的形式如下:Get<Type>Field     GetStatic<Type>Field。
设置的形式如下:Set<Type>Field     SetStatic<Type>Field。
比如:取得属性:
jobject GetObjectField(jobject obj, jfieldID fieldID)   
jboolean GetBooleanField(jobject obj, jfieldID fieldID)  
jbyte GetByteField(jobject obj, jfieldID fieldID)

比如:取得静态属性:
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)   
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID)   
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID)

Get方法的第一个参数代表要获取的属性的对象或者jclass对象,第二个参数即属性的ID
比如;设置属性:
void SetObjectField(jobject obj, jfieldID fieldID, jobject val)   
void SetBooleanField(jobject obj, jfieldID fieldID,jboolean val)   
void SetByteField(jobject obj, jfieldID fieldID, jbyte val)

比如;设置静态属性:
void SetStaticObjectField(jobject obj, jfieldID fieldID, jobject val)   
void SetStaticBooleanField(jobject obj, jfieldID fieldID,jboolean val)   
void SetStaticByteField(jobject obj, jfieldID fieldID, jbyte val) 

Set方法的第一个参数代表要设置的属性的对象或者jclass对象,第二个参数即属性的ID,第三个参数代表要设置的值。

2、如何调用方法
取得了代表方法和静态方法的jmethodID,就可以用在JNIEnv中提供的一系列的方法,来调用方法和静态方法。
有三种形式来调用方法和静态方法:
普通方法:
Call<Type>Method(jobject obj, jmethodID methodID,...)   
Call<Type>MethodV(jobject obj, jmethodID methodID,va_list args)   
Call<Type>tMethodA(jobject obj, jmethodID methodID,const jvalue *args)

静态方法:
CallStatic<Type>Method(jclass clazz, jmethodID methodID,...)   
CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_list args)   
CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,const jvalue *args)

这里面的Type也和上面获取和设置属性的Type一样,表示各种类型,如Int,Char,Byte等等,这里面的Type代表的是这个方法的返回值类型。
第一个参数代表调用的这个方法属于哪个对象,或者这个静态方法属于哪个类。
第二个参数代表jmethodID。
后面的参数,就代表这个方法的参数列表了。只不过有3中方式来设置这个参数列表。
下面来详细说明如何通过这3个方法来设置参数列表(以非静态方法举例,静态方法和的设置一致)。
方式1、Call<Type>Method(jobject obj, jmethodID methodID,...)
用不定参数的形式来一个个指定对应的参数列表。比如有这么一个Java方法
public int show(int i,double d,char c){  
     。。。。  
}

通过这个方法,要这样设置参数列表:
jint i=10L;  
jdouble d=2.4;  
jchar c=L'd';  
env->CallIntMethod(obj,id_show,i,d,c);

方式2、Call<Type>MethodV(jobject obj, jmethodID methodID,va_list args)
这种方式使用很少。

方式三:Call<Type>MethodA(jobject obj, jmethodID methodID,jvalue* v)
第三种传参的形式是传入一个jvalue的指针。jvalue类型是在 jni.h头文件中定义的联合体union,看它的定义:
typedef union jvalue {  
    jboolean z;  
    jbyte    b;  
    jchar    c;  
    jshort   s;  
    jint     i;  
    jlong    j;  
    jfloat   f;  
    jdouble  d;  
    jobject  l;  
} jvalue;

联合体可以储存不同类型的变量,但是一个联合体对象只能存储它里面定义的某一个类型的变量。
所以这里要传入一系列参数,要使用jvalue数组或者指针。
例子:
jvalue * args=new jvalue[3];  
  
       args[0].i=12L;  
args[1].d=1.2;  
args[2].c=L'c';  
jmethodID id_goo=env->GetMethodID(env->GetObjectClass(obj),"goo","(IDC)V");  
env->CallVoidMethodA(obj,id_goo,args);  
       delete [] args;  //释放内存 

静态方法 的使用也是类似的形式。

三、如何调用子类重写的父类方法
在Java:
class Father{  
    public void show(){  
        System.out.println("Father");  
    }  
}  
  
class Son extends Father{  
    @Override  
    public void show() {  
        System.out.println("Son");  
    }  
}

public static void main(String[] args) {  
        Father father=new Son();  
        father.show();  
    }

这里调用的是Son的show方法,而不是Father的show方法,这就是java的多态。

但是在C++中,
class Father{  
public:  
    void show(){  
        cout<<"Father"<<endl;  
    }  
};  
class Son:public Father{  
public :  
    void show(){  
        cout<<"Son"<<endl;  
    }  
};

Father* father=new Son();  
    father->show();

这里,调用的却是父类的show方法。

这是因为C++和Java的多态的不一样的地方,只有当一个父类的方法被声明为virtual——虚函数的时候,才能被子类覆盖,才能实现多态。
而Java默认实现了这一点,Java类中的所有成员方法都是虚函数。所以能够直接实现多态,但是C++中的不同,必须我们自己加上关键字virtual。
所以,如果把上面的C++代码改成如下形式,就可以实现和Java一样的效果:
class Father{  
public:  
    virtual void function(){  
        cout<<"Father"<<endl;  
    }  
};  
class Son:public Father{  
public :  
    void function(){  
        cout<<"Son"<<endl;  
    }  
};

那么,在JNI中,我们可不可以通过子类的对象,去调用父类被子类重写的方法呢?我们知道,在Java中是不可以的,最多只能在重写该方法的时候,使用super调用一下,但是在其他的地方就可以调用了。但是我们在JNI中,却可以实现子类调用父类被重写的方法。 这也是为了让JNI的本地方法更加贴近C/C++的特性。

要想实现子类调用父类被重写的方法,要使用JNIEnv提供的一系列方法。形式如下:
CallNonvirtual<Type>Method(jobject obj, jclass clazz,jmethodID methodID, ...)
CallNonvirtual<Type>MethodV(jobject obj, jclass clazz, jmethodID methodID, va_list args)
CallNonvirtual<Type>MethodA(jobject obj, jclass clazz,jmethodID methodID, const jvalue * args)
Type就是表示这个方法的返回类型,如Object、Boolean、Int。再看方法参数的含义:
第一个参数 jobject obj   代表的子类的对象
第二个参数jclass clazz   代表的是父类的jclass对象
第三个参数jmethodID methodID   代表的是父类中   被子类重写的方法的ID(也就是父类方法的ID,而不是子类的)。
后面的参数都是指定方法的参数列表,这和上面讲到的方法调用一样的。

下面看看使用实例:
有这样一段Java代码。
package com.tao.test;  
  
public class Test {  
    private Son son=new Son();  
    public native void show();  
    static{  
        System.loadLibrary("NativeTest");  
    }  
    public static void main(String[] args) {  
        new Test().show();  
    }  
}  
class Father{  
    public void function(){  
        System.out.println("Father");  
    }  
}  
  
class Son extends Father{  
    @Override  
    public void function() {  
        System.out.println("Son");  
    }  
}

在上面的Java代码中,有一个父类Father,和一个子类的Son方法,然后有一个本地方法show。
下面,我们来书写我们的本地方法show。
JNIEXPORT void JNICALL Java_com_tao_test_Test_show  
  (JNIEnv * env, jobject obj)  
{  
       //首先取得Test类中son属性的ID  
       jfieldID id_son=env->GetFieldID(env->GetObjectClass(obj),"son","Lcom/tao/test/Son;");  
        //取得son属性  
    jobject son=env->GetObjectField(obj,id_son);  
          
        //先看调用子类的show方法  
        //取得子类的show方法的id,然后调用子类的show方法  
        jmethodID id_show=env->GetMethodID(env->GetObjectClass(son),"function","()V");  
    env->CallVoidMethod(son,id_show);  
  
        //再看如果调用父类中被重写的方法  
        //取得父类Father的jclass对象  
        jclass clazz_father=env->FindClass("com/tao/test/Father")  
        //取得父类中show方法的id  
        jmethodID id_show_father=env->GetMethodID(clazz_father,"function","()V");  
    //调用CallNonvirtual<Type>Method方法调用父类中被重写的方法  
        env->CallNonvirtualVoidMethod(son,env->FindClass("com/tao/test/Father"),id_show_father);  
}

输出结果为
Son
Father

android职业交流QQ 群,有兴趣的可以一起来多搞搞技术、职业交流,互相学习、提高,互通好的职业信息,群号:104286694
分享到:
评论

相关推荐

    jni调用java静态方法

    这是我自己写的demo,实现了在jni里面调用java的static方法

    JNI实现java cpp相互调用

    JNI实现java cpp相互调用,包括动态注册和静态注册两种方式,具体包含 静态方式实现: C/C++中访问Java方法 C/C++中访问Java父类的方法 C/C++中访问/修改Java变量 Java中访问C/C++方法 Java中访问/修改C/C++变量 ...

    Android JNI 调用演示代码

    演示JNI中几种不同的调用方法 1. 在应用的JAVA代码中调用NDK中C/C++实现的函数。 2. 在NDK开发中的C/C++代码调用应用中JAVA类的静态函数。 3. 在NDK开发中的C/C++代码调用应用中JAVA类当前传入NDK中的实例的函数。 ...

    实用技术在Android 应用中调用 C++ 代码并在新线程中执行 Java 静态方法

    这是 Kotlin 语言编写...这个接口函数的作用是创建新线程,并在新线程中调用 callJavaStaticMethod 方法,这个方法会获取当前线程的 JNIEnv 对象和 Java 类对象,并通过这些对象调用 Java 层的静态方法 staticMethod。

    JNI反射调用Java方法1

    方法签名,如果是一个非静态数据类型,就得把整个路径都写上,并且在最前面加上L,例如String类型的写法是: Ljava/lang/String创建对象(如果被

    JNI完全技术手册 带完整书签

    6、实例六:在jni函数中调用java类的静态方法... 61 7、实例七:jni函数中传递基本数据类型参数... 62 8、实例八:在jni函数中传递对象类型参数... 62 9、实例九:在jni函数中处理字符串... 63 10、实例十:在...

    学习android jni java和C相互调用实用源码

    /*讲述了调用java空方法 * C调用java中的带两个int参数的方法 * C调用java中参数为string的方法 * C回调java静态方法 * C回调java刷新界面 * java调c锅炉压力系统检测 * */

    JniCallJava_ok2

    jni调用java的静态方法和私有方法例子

    JNI学习示例代码,含java代码工程和win32 dll工程

    即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/...

    LINUX C调用JAVA的静态方法和非静态方法(实例方法)小实例

    编译方法在cmd.sh里面,ubuntu实测可以运行

    android jni c回调java

    android jni demo,有c调用java 。java调用c。分有参无参,有无返回和静态非静态几种不同情况。

    JNI文档资料源码_2020_02_04.zip

    【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 ) I . 调用 Java 方法流程 II . 获取 jclass 对象 ( GetObjectClass ) III . 获取 jclass 对象 ( Find...

    hello-jni.rar_C 调用JAVA_jni 数组

    java和c/c++相互调用实例,包括静态和动态生成c/c++源码,和c/c++数组,线程,对象的使用。

    android jni使用static变量

    这是我自己写的android jni调用java static属性的例子

    JNI接口开发

    native方法中实现JNI_OnLoad,返回JNI的版本号;如果native不实现,则由虚拟机生成默认实现。在加载本地库时调用 b) 静态绑定时,运行时按照特定规则查找对应的方法 2. 参数转换 a) 将java参数类型转换为jni提供的...

    JNI官方开发规范

    JNI开发规范官方权威文档,JNI数据类型、内存管理、C/C++调用Java实例方法、静态方法、成员变量、静态变量、异常处理、线程处理等

    NDKC调用Java函数传参或获取变量

    NDK开发时,C/C++调用Java的函数的一些案例; 传递int类型参数: https://blog.csdn.net/niuba123456/article/details/80978500 传递String类型参数: https://blog.csdn.net/niuba123456/article/details/80978916 ...

    android中使用jni对字符串加解密实现分析demo

    我们需要考虑不同系统平台上数据类型的差异问题,这里推荐另一种易于实现的方法,即使用Java中的AES加解密逻辑,而将AES加解密所需要的核心秘钥放入到C中,通过调用jni来从静态类库中读取需要的秘钥

    Android NDK Test Demo

    目前时间:2018-6-27 记录一下Ndk 开发。主要内容是: (1)java调用c++方法 (2)c++调用java类成员属性 (3)c++调用java静态属性 (4)c++调用java类成员方法和静态方法

    NDKDemo.zip

    一个NDK案例项目, 包含了Java调用 NDK C++层的几种解决方案( Java调用单个C++文件中的某个...3.native C++层代码访问Java静态方法; 4.native C++层代码访问Java非静态方法; 5.native C++层代码访问Java构造函数;

Global site tag (gtag.js) - Google Analytics