首页 > 编程语言 >JNI调用java中的类方法和静态方法

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

时间:2023-02-07 10:02:57浏览次数:45  
标签:静态方法 show jobject void jmethodID obj java JNI 方法


在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:
[list]
[*]GetFieldID :取得成员变量的id
[*]GetStaticFieldID :取得静态成员变量的id
[*]GetMethodID :取得方法的id
[*]GetStaticMethodID :取得静态方法的id
[/list]
下面看这个方法的原型

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

标签:静态方法,show,jobject,void,jmethodID,obj,java,JNI,方法
From: https://blog.51cto.com/u_15955464/6041282

相关文章

  • java GZIPOutputStream,GZIPInputStream 用法
    GZIP常常用在linxu环境下,是一种非常简单的压缩算法。在Java实现API中,它仅仅包含两个实现类:GZIPInputStream和GZIPOutputStream。GZIPOutputStream类......
  • 掌握java枚举类型(enum type)
    1背景在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量。之前我们通常利用publicfinalstatic方法定义的代码如......
  • [java] JSON格式校验
    对JSON字符串进行格式校验,不依赖于第三方包packagecom.iaiai.test;importjava.text.CharacterIterator;importjava.text.StringCharacterIterator......
  • [java] 判断两台机器之间网络是否可达
    Java类库判断两台机器之间网络是否可达,常用ping方法来实现。packagecom.iaiai.test;importjava.net.InetAddress;/****<br/>*Title:Test.ja......
  • 用java实现zip压缩
    本来是写到spaceslive上的,可是代码的显示效果确实不怎么好看。在javaeye上试了试代码显示的顺眼多了。今天写了个用java压缩的功能,可以实现对文件......
  • java.lang.SecurityException: Access to default session denied
    {exception=java.lang.SecurityException:Accesstodefaultsessiondenied}java.lang.SecurityException:Accesstodefaultsessiondeniedat......
  • 根据经纬度求两点间距离实现源码(java)
    研究了一下GoogleMap上的根据经纬度求地球表面两点间距离的实现,用java实现了一把,对我国境内的Beijing54,Xian80,WGS84三种坐标系的空间距离计算感......
  • ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type
    用java处理图片的时候,ImageIO读jpg的时候出现exception:bandOffsets.lengthiswrong。我也遇到过,最终也解决啦,把成果跟大家分享。原因可能是JPEG图......
  • java 最短路径算法
    packageorg.iaiai.suanfa;importjava.util.ArrayList;/****<p>*Title:Dijkstra.java*</p>*<p>*Http:iaiai.iteye.com*</p>*<p>*Createtime:2011-8-......
  • java 广度优先
    就是按照正常顺序把tree打出来,假设有个tree是1/\23/\/\4567打印出来就是1,2,3,4,5,6,7;这个就是宽度优先packageorg.iaiai.suanfa;i......