JNI/NDK入门指南之C/C++通过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