欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法

程序员文章站 2022-06-21 19:25:45
 JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法  在前面的章节JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性中讲解了C/C++通过JNI对Java实例属性和静态属性的访问。今天我们继续向JNI的知识海洋进军讲解C/C++通过JNI访问Java通过JNI访问Java实例方法和类静态方法的处理。本章内容有点多哦,所以读者务必上好厕所,搬个......

 JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法

  在前面的章节JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性中讲解了C/C++通过JNI对Java实例属性和静态属性的访问。今天我们继续向JNI的知识海洋进军讲解C/C++通过JNI访问Java通过JNI访问Java实例方法和类静态方法的处理。本章内容有点多哦,所以读者务必上好厕所,搬个小板凳认真围观。

前言

通过前面的正佳我们知道了如何通过 JNI 函数来访问Java实例属性和类静态属性。接下来我们接着学习C/C++本地代码如何通过JNI访问Java类实例方法和静态方法。在正式开讲之前,先我们思考一下在 Java 中调用一个方法时在 虚拟机中的实现原理,有助于下面讲解本地代码调用 Java 方法实现的机制。有过 Java开发经验的小伙伴都知道,调用一个类的静态方法,直接通过 类名.方法 就可以调用。看起来很简单,其实不然,在这个调用过程中,虚拟机是帮我们做了很多工作的,主要是类的加载验证。当我们在运行一个 Java 程序时,虚拟机 会先将程序运行时所要用到所有相关的 class 文件加载到 虚拟机中,并采用按需加载的方式加载,也就是说某个类只有在被用到的时候才会被加载,这样设计的目的也是为了提高程序的性能和节约内存。所以我们在用类名调用一个静态方法之前,虚拟机 首先会判断该类是否已经加载,如果没有被 ClassLoader 加载到 虚拟机中,虚拟机 会从classpath 路径下查找该类,如果找到了,会将其加载到 虚拟机中,然后才是调用该类的静态方法。如果没有找到,虚拟机会抛出 java.lang.ClassNotFoundException 异常,提示找不到这个类。ClassLoader 是 虚拟机加载class 字节码文件的一种机制,关于Java ClassLoader可以参阅篇章详细深入分析 Java ClassLoader 工作机制一文。其实在 JNI 开发当中,本地代码也是按照上面的流程来访问类的静态方法或实例方法的,



一. 初探JNI访问Java实例非静态方法处理函数

好了有了前面知识的铺垫,下面让我们来看看JNIEnv为我们提供了那些常见Java对象方法处理函数。

1.1 GetMethodID

函数原型: jmethodID GetMethodID(JNIEnv * env, jclass clazz, const char* name, const char* sig)
函数功能: 返回Java类或者接口实例非静态方法的方法ID(可以参照属性ID),方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。要获得构造函数的方法 ID,应将 作为方法名,同时将 void (V) 作为返回类型,具体使用可以参见后面章节实战讲解。
参数:

  • env: JNIEnv接口指针
  • clazz: Java类对象,不是Java类的实例jobject。类对象,就是用来描述这种类,都有什么属性,什么方法的。所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法等等。
  • name: 该方法在类中的名称
  • sig: 该方法的方法签名,具体参见JNI/NDK入门指南之JNI数据类型,描述符详解篇章

函数返回值:该Java类实例或者接口实例非静态方法ID,如果找不到指定的方法,则为 NULL。
异常抛出:

  • NoSuchMethodError: 如果找不到指定方法
  • ExceptionInInitializerError: 如果由于异常而导致类初始化程序失败
  • OutOfMemoryError:如果系统内存不足

1.2 Call<PrimitiveType>Method

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType Call<PrimitiveType>Method (JNIEnv*en v, jobject obj , jmethodID methodID, …)
函数功能: 根据所指定的方法 ID 调用 Java 对象的实例(非静态)方法。参数 methodID 必须通过调用 GetMethodID() 来获得。当这些函数用于调用私有方法和构造函数时,方法 ID 必须从obj 的真实类派生而来,而不应从其某个超类派生。当然,附加参数可以为空 。这里的调用Java对象方法的参数都是附加在最后面的,并且参数的顺序必须按照Java类对象方法中参数一一匹配的。
使用说明: 在实际使用中,根据需要调用方法将 Call<PrimitiveType>Method中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);

参数:

  • env: JNIEnv接口指针
  • obj: Java实例对象,可以是通过JNI从Java传递下来的,也可以是在本地方法中创建的。
  • methodID: 通过GetMethodID获取的方法ID
  • : 方法参数

函数返回值: 返回调用Java方法的结果。
异常抛出: 执行Java方法时抛出的异常信息。

函数集表格:

Call<PrimitiveType>Method NativeType JNI本地类型
CallVoidMethod( ) V
CallObjectMethod( ) jobject
CallBooleanMethod ( ) jboolean
CallByteMethod( ) jbyte
CallCharMethod( ) jchar
CallShortMethod( ) jshort
CallIntMethod( ) jint
CallLongMethod() jlong
CallFloatMethod() jfloat
CallDoubleMethod() jdouble

1.3 Call<PrimitiveType>MethodA

读者是不是初一看,以为我老糊涂了咋又把前面的章节重写一遍呢,真不是读者仔细看看是不是多了一个A,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和1.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType Call<PrimitiveType>MethodA (JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args)
使用说明: 在实际使用中,根据需要调用方法将 Call<PrimitiveType>MethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的参数是以指针的形式添加的。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义以及调用:

void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
//jvalue定义如下:
typedef union jvalue {
    jboolean    z;
    jbyte       b;
    jchar       c;
    jshort      s;
    jint        i;
    jlong       j;
    jfloat      f;
    jdouble     d;
    jobject     l;
} jvalue;

//例如
    jvalue * args=new jvalue[3];
     args[0].i=12L;
     args[1].d=1.2;
     args[2].c=L'c';
     jmethodID xxx =env->GetMethodID(env->GetObjectClass(obj),"xxx","(IDC)V");
     env->CallVoidMethodA(obj,xxx,args);
     delete []args;  //释放内存

特别说明:这个函数集合Call<PrimitiveType>Method用法一致,就不过多细说了。函数集基本也是一样。


1.4 Call<PrimitiveType>MethodV

读者是不是猛地一看,以为作者凑字数咋又把前面的章节重写一遍呢,真不是此处多了一个V,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和1.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType Call<PrimitiveType>MethodV (JNIEnv *env, jobject obj, jmethodID methodID, va_list args)
使用说明: 在实际使用中,根据需要调用方法将 Call<PrimitiveType>MethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的va_list args。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);

特别说明:这个函数集合Call<PrimitiveType>Method用法一致,就不过多细说了,并且在C++环境下最后的Call<PrimitiveType>Method里面其实是封装的Call<PrimitiveType>MethodV,代码为证:

    void CallVoidMethod(jobject obj, jmethodID methodID, ...)
    {
        va_list args;
        va_start(args, methodID);
        functions->CallVoidMethodV(this, obj, methodID, args);
        va_end(args);
    }


二. 初探JNI访问Java类静态方法处理函数

在前面的章节降解了JNI访问Java实例对象非静态方法的一系列函数,下面让我们来看看JNIEnv为我们提供了那些常见JNI访问Java类静态方法处理函数。

2.1 GetStaticMethodID

函数原型: jmethodID GetStaticMethodID(JNIEnv * env, jclass clazz, const char* name, const char* sig)
函数功能: 返回Java类对象静态方法的方法ID(可以参照属性ID),方法可在某个 clazz 的超类中定义,也可从 clazz 继承。该方法由其名称和签名决定。具体使用可以参见后面章节实战讲解。
参数:

  • env: JNIEnv接口指针
  • clazz: Java类对象,不是Java类的实例jobject。类对象,就是用来描述这种类,都有什么属性,什么方法的。所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法等等。
  • name: 该静态方法在类中的名称
  • sig: 该静态方法的方法签名,具体参见JNI/NDK入门指南之JNI数据类型,描述符详解篇章

函数返回值:该Java类对象静态方法ID,如果找不到指定的方法,则为 NULL。
异常抛出:

  • NoSuchMethodError: 如果找不到指定方法
  • ExceptionInInitializerError: 如果由于异常而导致类初始化程序失败
  • OutOfMemoryError:如果系统内存不足

2.2 CallStatic<PrimitiveType>Method

这里的PrimitiveType指代的是一系列的JNI数据类型,如果是引用类型的话PrimitiveType用Object替代。
函数原型: NativeType CallStatic<PrimitiveType>Method (JNIEnv*en v, jclass clazz , jmethodID methodID, …)
函数功能: 根据所指定的方法 ID 调用 Java 类对象的静态方法。参数 methodID 必须通过调用 GetStaticMethodID() 来获得。当然,附加参数可以为空 。这里的调用Java对象方法的参数都是附加在最后面的,并且参数的顺序必须按照Java类对象方法中参数一一匹配的。
使用说明: 在实际使用中,根据需要调用方法将 CallStatic<PrimitiveType>Method中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);

参数:

  • env: JNIEnv接口指针
  • clazz: Java类对象,通过findClass方法得到
  • methodID: 通过GetStaticMethodID获取的方法ID
  • : 方法参数,即该方法在Java中的具体参数入参

函数返回值: 返回调用Java方法的结果。
异常抛出: 执行Java方法时抛出的异常信息。

函数集表格:

CallStatic<PrimitiveType>Method NativeType JNI本地类型
CallStaticVoidMethod( ) V
CallStaticObjectMethod( ) jobject
CallStaticBooleanMethod ( ) jboolean
CallStaticByteMethod( ) jbyte
CallStaticCharMethod( ) jchar
CallStaticShortMethod( ) jshort
CallStaticIntMethod( ) jint
CallStaticLongMethod() jlong
CallStaticFloatMethod() jfloat
CallStaticDoubleMethod() jdouble

2.3 CallStatic<PrimitiveType>MethodA

读者此时看到这个应该已经有心理准备了,因为我们前面已经见识过了,所以这里我再次申明真不是为了凑字数。前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和2.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallStatic<PrimitiveType>MethodA (JNIEnv *env, jclazz clazz, jmethodID methodID, jvalue *args)
使用说明: 在实际使用中,根据需要调用方法将 CallStatic<PrimitiveType>MethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的参数是以指针的形式添加的。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义以及调用:

void        (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, jvalue*);

特别说明:这个函数集合CallStatic<PrimitiveType>Method用法一致,就不过多细说了。函数集也是一样。


2.4 CallStatic<PrimitiveType>MethodV

又是一个马甲,前面的函数集重新又穿了另外一个马甲现身了。由于功能和作用和2.2章节的一样我就不过多讲解了,只说一下区别。主要是最后一个参数的问题。
函数原型: NativeType CallStatic<PrimitiveType>MethodV (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)
使用说明: 在实际使用中,根据需要调用方法将 CallStatic<PrimitiveType>MethodA中的 PrimitiveType替换为所调用方法的Java 类型(或使用表中的实际方法名),同时将 NativeType 替换为该方法相应的本地类型,并且这里的va_list args。引用类型需要特别对待。 这里我们以调用void返回值为例看看函数定义:

void        (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);

特别说明:这个函数集合Call<PrimitiveType>Method用法一致,就不过多细说了,并且在C++环境下最后的Call<PrimitiveType>Method里面其实是封装的Call<PrimitiveType>MethodV,代码为证:

    void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
    {
        va_list args;
        va_start(args, methodID);
        functions->CallStaticVoidMethodV(this, clazz, methodID, args);
        va_end(args);
    }


三. 访问Java实例方法和类静态方法实战分析

前面的章节,我们将JNI访问Java实例方法和类静态方法中有关访问函数集,一网打净了(当然是夸张说法了)。说得再多不练,都是纸上谈兵,下面我们来实战一把,跟紧我要开战了,可别走丢了。

3.1 JNI访问Java实例非静态方法

好了前面的理论知识我们已经OK了,那么接下来都懂的必须来点实战的东西,不能光说不练。让我带领读者来看看怎么通过JNI访问Java实例方法,这里会提供两种方法,其差别主要是Java实例是通过JNI传递到本地方法中还是在本地方法中创建。
Java端代码:
Java实例所属类JNIMethodClass.java代码:

package com.xxx.jni.method;
import android.util.Log;

public class JNIMethodClass {
    private int jniCallInstanceMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callInstanceMethod");
        Log.e("ACCESS_METHOD", "callInstanceMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
        return 0;
    }
}

Java端Native方法定义JNIAccessMethodManager.java代码:

package com.xxx.jni.method;
public class JNIAccessMethodManager {
    public  native void callInstanceMethod();//不传递Java对象,在C++层创建一个Java对象并调用其方法
    
    static{
        System.loadLibrary("accessmethod");
    }
}

Java端测试代码:

    private void operateInstanceMethod(){
        JNIAccessMethodManager mjniMethodManager = new JNIAccessMethodManager();
        mjniMethodManager.callInstanceMethod();
    }

JNI端代码:
Java中Native方法对应com_xxx_jni_field_JNIAccessFieldManager.h代码如下:

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

#ifndef _Included_com_xxx_jni_method_JNIAccessMethodManager
#define _Included_com_xxx_jni_method_JNIAccessMethodManager
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callInstanceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callInstanceMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_xxx_jni_field_JNIAccessFieldManager.cpp代码如下:

#include"com_xxx_jni_method_JNIAccessMethodManager.h"

#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <errno.h>


#define TAG "ACCESS_METHOD"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)


//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
	if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
	{
		(env)->ExceptionDescribe();
		(env)->ExceptionClear();
		//printf("jstringToNative函数转换时,传入的参数str为空");
		return NULL;
	} 
 
	jbyteArray bytes = 0; 
	jthrowable exc; 
	char *result = 0; 
	if ((env)->EnsureLocalCapacity(2) < 0) 
	{ 
		return 0; /* out of memory error */ 
	} 
	jclass jcls_str = (env)->FindClass("java/lang/String"); 
	jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B"); 
 
	bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes); 
	exc = (env)->ExceptionOccurred(); 
	if (!exc) 
	{ 
		jint len = (env)->GetArrayLength( bytes); 
		result = (char *)malloc(len + 1); 
		if (result == 0) 
		{ 
			//JNU_ThrowByName( "java/lang/OutOfMemoryError", 	0); 
			(env)->DeleteLocalRef(bytes); 
			return 0; 
		} 
		(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result); 
		result[len] = 0; /* NULL-terminate */ 
	} 
	else 
	{ 
		(env)->DeleteLocalRef( exc); 
	} 
	(env)->DeleteLocalRef( bytes); 
	return (char*)result; 

} 


//将char *  转换成 jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{	
	//定义java String类 strClass
	jclass strClass = (env)->FindClass("java/lang/String");
	//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
	jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
	//建立byte数组
	jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
	//将char* 转换为byte数组
	(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
	//设置String, 保存语言类型,用于byte数组转换至String时的参数
	jstring encoding = (env)->NewStringUTF( "utf-8"); 
	//将byte数组转换为java String,并输出
	return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding); 
}

/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callInstanceMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callInstanceMethod
  (JNIEnv * env, jobject object)
{
	/*******JNI层创建Java实例对象并调用非静态方法*****/
	//1.从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用
	jclass clazz = env->FindClass("com/xxx/jni/method/JNIMethodClass");
	if(NULL == clazz)
	{
		LOGE(TAG, "FindClass failed\n");
		return;
	}

	//2.获取类JNIMethodClass的默认构造函数ID,通常都是这么写
	jmethodID clazz_construct_method = env->GetMethodID(clazz, "<init>","()V");
	if(NULL == clazz_construct_method)
	{
		LOGE(TAG,"GetMethodID for construct failed\n");
	}

	//3.在JNI层创建JNIMethodClass类的实例
	jobject object_out = env->NewObject(clazz, clazz_construct_method);
	if(NULL == object_out)
	{
		LOGE(TAG,"NewObject failed\n");
	}


	//4.查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod
	//其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名
	jmethodID jniCallInstanceMethod_method = env->GetMethodID(clazz, "jniCallInstanceMethod", "(Ljava/lang/String;IBCZJDFS)I");
	if(NULL == jniCallInstanceMethod_method)
	{
		LOGE(TAG,"GetMethodID failed\n");
		return;
	}

	//5.调用Java对象的实例方法jniCallInstanceMethod
	jstring string_out = nativeTojstring(env, "Hello jniCallInstanceMethod!");
	jboolean    z = 1;
    jbyte       b = 2;
    jchar       c = 'c';
    jshort      s = 60;
    jint        i = 100;
    jlong       j = 568978523;
    jfloat      f = 125643.22F;
    jdouble     d = 123456.12D;
	env->CallIntMethod(object_out, jniCallInstanceMethod_method, string_out, i, b, c, z, j, d, f, s);


	//6.最后删除局部引用
	env->DeleteLocalRef(object_out);
	env->DeleteLocalRef(clazz);	
}

运行演示:

λ adb logcat  -s  ACCESS_METHOD
--------- beginning of main
--------- beginning of system
E/ACCESS_METHOD(10421): callInstanceMethod
E/ACCESS_METHOD(10421): callInstanceMethod [str=Hello jniCallInstanceMethod!, i=100, b=2, c=c, bl=true, l=568978523, d=123456.12, f=125643.22, sh=60]

案例分析:
在本例中,Java类JNIAccessMethodManager中定义了一个callInstanceMethod的Native方法,参数类型为void,在其本地方法中我们会通过JNI来访问Java类实例的非静态方法。下面让我们对本地代码以一一分析。
JNI层创建Java实例对象并调用非静态方法:
(1) 调用FindClass从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用。此时可能获取到的类的引用为NULL,所以要做异常的判断处理。
(2) 调用GetMethodID获取类JNIMethodClass的默认构造函数ID,注意这里默认构造函数名和签名。并且这里也要做异常处理。
(3) 调用NewObject在JNI层创建JNIMethodClass类的实例。
(4) 调用GetMethodID 查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod,其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。
(5) 调用CallIntMethod来访问Java对象的实例方法jniCallInstanceMethod,这里重点关注一下参数的传递。
(6) 最后调用DeleteLocalRef删除局部引用


3.2 JNI访问Java类对象静态方法

在前面的章节里面我们介绍了JNI访问Java对象实例非静态方法,在本节中将要介绍JNI访问Java类对象静态方法。
Java端代码:
Java实例所属类JNIMethodClass.java代码:

package com.xxx.jni.method;
import android.util.Log;

public class JNIMethodClass {
    private static void jniCallStaticMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callStaticMethod");
        Log.e("ACCESS_METHOD", "callStaticMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
    }
}

Java端Native方法定义JNIAccessMethodManager.java代码:

package com.xxx.jni.method;
public class JNIAccessMethodManager {
    public native void callStaticMethod();
    
    static{
        System.loadLibrary("accessmethod");//Java类对象静态方法
    }
}

Java端测试代码:

    private void operateStaticMethod(){
        JNIAccessMethodManager mjniMethodManager = new JNIAccessMethodManager();
        mjniMethodManager.callStaticMethod();
    }

JNI端代码:
Java中Native方法对应com_xxx_jni_field_JNIAccessFieldManager.h代码如下:

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

#ifndef _Included_com_xxx_jni_method_JNIAccessMethodManager
#define _Included_com_xxx_jni_method_JNIAccessMethodManager
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callStaticMethod
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_xxx_jni_field_JNIAccessFieldManager.cpp代码如下:

#include"com_xxx_jni_method_JNIAccessMethodManager.h"

#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <errno.h>


#define TAG "ACCESS_METHOD"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)


//将jstring转换成char *
char* jstringToNative(JNIEnv *env, jstring jstr)
{
	if ((env)->ExceptionCheck() == JNI_TRUE || jstr == NULL)
	{
		(env)->ExceptionDescribe();
		(env)->ExceptionClear();
		//printf("jstringToNative函数转换时,传入的参数str为空");
		return NULL;
	} 
 
	jbyteArray bytes = 0; 
	jthrowable exc; 
	char *result = 0; 
	if ((env)->EnsureLocalCapacity(2) < 0) 
	{ 
		return 0; /* out of memory error */ 
	} 
	jclass jcls_str = (env)->FindClass("java/lang/String"); 
	jmethodID MID_String_getBytes = (env)->GetMethodID(jcls_str, "getBytes", "()[B"); 
 
	bytes = (jbyteArray)(env)->CallObjectMethod(jstr, MID_String_getBytes); 
	exc = (env)->ExceptionOccurred(); 
	if (!exc) 
	{ 
		jint len = (env)->GetArrayLength( bytes); 
		result = (char *)malloc(len + 1); 
		if (result == 0) 
		{ 
			//JNU_ThrowByName( "java/lang/OutOfMemoryError", 	0); 
			(env)->DeleteLocalRef(bytes); 
			return 0; 
		} 
		(env)->GetByteArrayRegion(bytes, 0, len, (jbyte *)result); 
		result[len] = 0; /* NULL-terminate */ 
	} 
	else 
	{ 
		(env)->DeleteLocalRef( exc); 
	} 
	(env)->DeleteLocalRef( bytes); 
	return (char*)result; 

} 


//将char *  转换成 jstring
jstring nativeTojstring( JNIEnv* env,const char* str )
{	
	//定义java String类 strClass
	jclass strClass = (env)->FindClass("java/lang/String");
	//获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String
	jmethodID ctorID = (env)->GetMethodID( strClass, "<init>", "([BLjava/lang/String;)V");
	//建立byte数组
	jbyteArray bytes = (env)->NewByteArray( (jsize)strlen(str));
	//将char* 转换为byte数组
	(env)->SetByteArrayRegion( bytes, 0, (jsize)strlen(str), (jbyte*)str);
	//设置String, 保存语言类型,用于byte数组转换至String时的参数
	jstring encoding = (env)->NewStringUTF( "utf-8"); 
	//将byte数组转换为java String,并输出
	return (jstring)(env)->NewObject( strClass, ctorID, bytes, encoding); 
}

/*
 * Class:     com_xxx_jni_method_JNIAccessMethodManager
 * Method:    callStaticMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_jni_method_JNIAccessMethodManager_callStaticMethod
  (JNIEnv * env, jobject object)
{
	/*******JNI调用Java类对象静态方法*****/
	jclass clazz = NULL;
	//1.从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用
	clazz = env->FindClass("com/xxx/jni/method/JNIMethodClass");
	if(NULL == clazz){
		LOGE(TAG,"FindClass failed\n");
		return;
	}

	//2.从clazz类对象中查找静态方法jniCallStaticMethod的ID
	//第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名
	jmethodID jniCallStaticMethod_method = env->GetStaticMethodID(clazz, "jniCallStaticMethod", "(Ljava/lang/String;IBCZJDFS)V");
	if(NULL == jniCallStaticMethod_method)
	{
		LOGE(TAG,"GetMethodID failed\n");
		return;
	}

	//3.调用clazz类对象的jniCallStaticMethod静态方法
	//第一个参数为JNIMethodClass的class引用,第二个参数未jniCallStaticMethod的methodID,第三个参数为...不定长,根据实际情况来确定
	jstring string_out = nativeTojstring(env, "Hello jniCallStaticMethod!");
	jboolean    z = 1;
    jbyte       b = 2;
    jchar       c = 'c';
    jshort      s = 60;
    jint        i = 100;
    jlong       j = 568978523;
    jfloat      f = 125643.22F;
    jdouble     d = 123456.12D;
	env->CallStaticVoidMethod(clazz, jniCallStaticMethod_method, string_out, i, b, c, z, j, d, f, s);


	//4.删除局部引用
	env->DeleteLocalRef(string_out);
	env->DeleteLocalRef(clazz);
}

运行演示:

λ adb logcat  -s ACCESS_METHOD
--------- beginning of main
--------- beginning of system
E/ACCESS_METHOD( 6396): callStaticMethod
E/ACCESS_METHOD( 6396): callStaticMethod [str=Hello jniCallStaticMethod!, i=1, b=255, c=c, bl=true, l=4294967296010, d=100.0, f=0.0, sh=2130903040]

案例分析:
在本例中,Java类JNIAccessMethodManager中定义了一个callStaticMethod的Native方法,参数类型为void,在其本地方法中我们会通过JNI来访问Java类对象的静态方法。下面让我们对本地代码以一一分析。
JNI层回调Java类对象并调用静态方法:
(1) 调用FindClass从classpath路径下搜索JNIMethodClass这个类,并返回该类的Class引用。此时可能获取到的类的引用为NULL,所以要做异常的判断处理。
(2) 调用GetStaticMethodID从clazz类对象中查找静态方法jniCallStaticMethod的ID,这里的第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。并且这里也要做异常处理。
(3) 使用CallStaticVoidMethod函数调用调用clazz类对象的jniCallStaticMethod静态方法,其中第一个参数为JNIMethodClass的class引用,第二个参数未jniCallStaticMethod的methodID,第三个参数为…不定长,根据实际情况来确定。
(4) 调用GetMethodID 查找JNIMethodClass实例对象的非静态方法jniCallInstanceMethod,其中第一个参数为JNIMethodClass的Class引用,第二个参数为方法的名字,第三个参数为方法的签名或者域名。
(5) 最后调用DeleteLocalRef删除局部引用



四. 总结思考

大家对前面的实例有没有一个疑问呢,为啥我们通过JNI能访问Java类实例私有方法和Java类对象私有静态方法呢。这是为什么呢?

public class JNIMethodClass {
    private static void jniCallStaticMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callStaticMethod");
        Log.e("ACCESS_METHOD", "callStaticMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
    }
    private int jniCallInstanceMethod(String str, int i,byte b,char c,boolean bl,long l,double d,float f,short sh){
        Log.e("ACCESS_METHOD", "callInstanceMethod");
        Log.e("ACCESS_METHOD", "callInstanceMethod [str=" + str + ", i=" + i + ", b=" + b + ", c=" + c
                + ", bl=" + bl + ", l=" + l + ", d=" + d + ", f=" + f + ", sh="
                + sh + "]");
        return 0;
    }
}

由于 JNI 函数是直接操作虚拟机中的数据结构,不受 Java 访问修饰符的限制。即,在本地代码中可以调用JNI 函数可以访问 Java 对象中的非 public 属性和方法。这也为操作Java对象中private的属性或者方法的一种思路。

前面的知识讲解都是为了引出最后的结论,下面让我们小结一把:
JNI创建Java实例并访问该Java实例方法的操作步聚:

  • 调用 FindClass 函数获取类的 Class 引用
  • 调用GetMethodID获取Class类的默认构造函数
  • 调用NewObject创建该Java对象实例
  • 调用GetMethodID获取该Java对象需要访问的实例方法ID
  • 最后调用CallXXXMethod函数访问Javad实例对象方法
  • 最后调用DeleteLocalRef释放前面创建的局部引用

JNI访问Java类对象的静态方法的操作步聚:

  • 调用 FindClass 函数获取类的 Class 引用
  • 调用函数GetStaticMethodID获取将要访问的该Java类对象的静态方法ID
  • 然后使用CallStaticXXXMethod函数来访问Java类对象的静态方法
  • 最后释放创建的局部引用


写在最后

  各位读者看官朋友们,关于C/C++通过JNI访问Java实例方法和类静态方法就告一段落了。本篇几乎将JNI中访问Java实例方法和类静态方法各种情况都讲到了,只要仔细阅读本章,应该以后没有访问Java实例方法和类静态方法问题能难住各位了。在最后麻烦读者朋友们如果本篇对你有帮助,关注和点赞一下,当然如果有错误和不足的地方也可以拍砖。

本文地址:https://blog.csdn.net/tkwxty/article/details/103741777