IntelliJ IDEA平台下JNI编程(四)—本地C代码访问JAVA对象

简介: IntelliJ IDEA平台下JNI编程(四)—本地C代码访问JAVA对象

IntelliJ IDEA平台下JNI编程(四)—本地C代码访问JAVA对象


转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001/article/details/64924057

本文主要针对C代码中访问JVM中对象的普通变量、静态属性、普通函数、静态函数进行举例讲解,通过本文的学习将进一步理解JNIEnv在本地代码和Java之间的重要性。有了前面几篇文章的基础,学习起本文来将更容易。好了,接下来往下学习吧~。

1. 访问实例对象的属性

下面实现一个简单的功能:在本地C代码中,修改Java类实例的属性值。首先在Java类中定义2个简单的属性,代码如下:

package com.huachao.java;
/**
 * Created by HuaChao on 2017/03/22.
 */
public class HelloJNI { 
    private int number = 88;
    private String message = "Hello from Java";
    static {
        System.loadLibrary("HelloJNI");
    }
    private native void modifyField();
    public static void main(String[] args) {
        HelloJNI obj = new HelloJNI();
        obj.modifyField();
        System.out.println("Java类中,number的值为:" + obj.number);
        System.out.println("Java类中,message属性为:" + obj.message);
    }
}

上面代码很简单,就是定义了一个本地函数modifyField(),还有两个属性number和message。接下来,生成c代码的头文件后,c代码对应的函数代码如下:

#include<jni.h>
#include <stdio.h>
#include "com_huachao_java_HelloJNI.h"
JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_modifyField
  (JNIEnv * env,jobject thisObj){
       //获取实例对象类的引用
       jclass thisClass = (*env)->GetObjectClass(env, thisObj);
       // 获取实例对象的nunber对应的属性ID,int类型
       jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
       if (NULL == fidNumber) return;
       //获取实例对象中对应的属性的值
       jint number = (*env)->GetIntField(env, thisObj, fidNumber);
       printf("在C代码中,number属性值为:%d\n", number);
       // 修改number的值
       number = 99;
       (*env)->SetIntField(env, thisObj, fidNumber, number);
       // 同理,获取类对象中的message的属性ID
       jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
       if (NULL == fidMessage) return;
       // 获取给定属性ID的属性对象
       jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
       // 创建C语言中的字符串
       const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
       if (NULL == cStr) return;
       printf("在C代码中, 字符串message为:%s\n", cStr);
       (*env)->ReleaseStringUTFChars(env, message, cStr);
       // 创建新的C字符串,并赋值给实例对象的属性
       message = (*env)->NewStringUTF(env, "Hello from C");
       if (NULL == message) return;
       // 修改实例对象的属性值
       (*env)->SetObjectField(env, thisObj, fidMessage, message);
 }

编译为dll文件后,运行结果如下:

Java类中,number的值为:99
Java类中,message属性为:Hello from C
在C代码中,number属性值为:88
在C代码中, 字符串message为:Hello from Java

看到运行结果觉得挺奇怪,因为先调用modifyField函数,在打印结果中先打印了Java中的代码后打印C中的代码。现在我也还不清楚具体原因,但从结果上看,是先执行本地代码的。因此打印的先后顺序暂时可不管他,我目前认为是c的输出流和Java的输出流在输出到IntelliJ的控制台时合并的先后顺序造成的吧,如果有小伙伴知道原因,可以在评论中指出。

下面对代码进行解释,访问实例对象的属性可分为如下步骤:

首先是通过jobject指代的实例对象获取实例对象在Java虚拟机中对应的Class对象,即对应于jclass类型。这一步通过调用JNIEnv对象的jclass GetObjectClass(JNIEnv *env, jobject obj);函数来完成。

接下来,获取用于标识属性的ID对象,即jfieldID类型对象。通过调用JNIEnv的jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);函数实现。GetFieldID函数前两个参数不用解释,第三个参数是表示Java类中属性的名称,第四个参数表示属性的描述符。关于描述符在《IntelliJ IDEA平台下JNI编程(二)—类型映射》已经做过介绍,不清楚的可以回去查阅。

接下来是根据属性的ID获取属性的值,通过调用JNIEnv的NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);来实现。此时可获取Java实例对象的的属性。

如果需要对实例对象的属性进行修改,调用JNIEnv的void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);实现。

可以看到,在C代码中访问Java实例对象的属性与Java中通过反射的方法访问非常类似。只是这里用到了ID的方式来进行访问,而不是Java反射中的Field对象来访问。这主要是在跨越两种语言相互访问内存时,通过唯一标识码来间接访问效率更高也更简单。

2. 访问类的静态变量

因为静态变量是属于类对象,即Class对象。因此在访问静态变量时,c代码获取到对应的jclass对象后,即可访问。下面通过一个简单例子说明,首先在HelloJNI.java类中添加一个静态变量。

在c代码的Java_com_huachao_java_HelloJNI_modifyField中,代码如下:

在c代码的Java_com_huachao_java_HelloJNI_modifyField中,代码如下:

JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_modifyField
  (JNIEnv * env,jobject thisObj){
     // 获取实例对象类的引用
       jclass thisClass = (*env)->GetObjectClass(env, thisObj);
      // 获取静态变量,并修改静态变量的值
      jfieldID fidNumber = (*env)->GetStaticFieldID(env, thisClass, "price", "D");
      if (NULL == fidNumber) return;
      jdouble number = (*env)->GetStaticDoubleField(env, thisClass, fidNumber);
      printf("In C, the double is %f\n", number);
      number = 77.88;
      (*env)->SetStaticDoubleField(env, thisClass, fidNumber, number);
 }

运行结果如下:

Java类中,price的值为:77.88
In C, the double is 55.660000

静态变量的访问过程跟实例变量类似,只是对应的函数多了static部分,这里不再继续解释。

3. 调用Java的对象方法和静态方法

调用Java的函数过程与访问Java属性过程一致,有了前面的经验后,学习起调用函数来更容易了。下面同样通过一个简单例子来学习。首先在Java类中,定义3个具有代表性的函数:有返回值的对象方法、无返回值的对象方法,以及有返回值的静态方法。

package com.huachao.java;
/**
 * Created by HuaChao on 2017/03/22.
 */
public class HelloJNI {
    static {
        System.loadLibrary("HelloJNI");
    }
    private native void nativeMethod();
    private int sum(int n1, int n2) {
        return n1 + n2;
    }
    private static double avg(int n1, int n2) {
        return (double) ((n1 + n2) * 1.0f / 2);
    }
    private void display() {
        System.out.println("invoke display()");
    }
    public static void main(String[] args) {
        HelloJNI obj = new HelloJNI();
        obj.nativeMethod();
    }
}

在c代码中,同样是获取方法的id后,再调用方法。

JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_nativeMethod
  (JNIEnv * env,jobject thisObj){
     // 获取实例对象类的引用
     jclass thisClass = (*env)->GetObjectClass(env, thisObj);
     //获取方法ID,注意普通函数和静态函数的获取区别
     jmethodID sumId = (*env)->GetMethodID(env, thisClass, "sum", "(II)I");
     jmethodID avgId = (*env)->GetStaticMethodID(env, thisClass, "avg", "(II)D");
     jmethodID displayId = (*env)->GetMethodID(env, thisClass, "display", "()V");
     if (NULL == sumId) return;
     if (NULL == avgId) return;
     if (NULL == displayId) return;
    //调用方法,注意调用静态方程和普通方法的区别
     (*env)->CallVoidMethod(env, thisObj, displayId);
     double avgRs=(*env)->CallStaticDoubleMethod(env,thisClass,avgId,1,3);
     printf("avg(1,3)=%f\n",avgRs);
     int sumRs=(*env)->CallIntMethod(env,thisObj,sumId,1,3);
     printf("sum(1,3)=%d\n",sumRs);
 }

运行结果如下:

invoke display()
avg(1,3)=2.000000
sum(1,3)=4

代码中解释很清楚了,这里就不再解释。

4. 调用父类中被覆盖的方法

构造一个简单的继承关系:

package com.huachao.java;
/**
 * Created by HuaChao on 2017/03/22.
 */
class Parent {
    protected void sayHello() {
        System.out.println("Hello in Parent");
    }
}
public class HelloJNI extends Parent{
    static {
        System.loadLibrary("HelloJNI");
    }
    private void sayHello() {
        System.out.println("Hello in Child");
    }
    private native void nativeMethod();
    public static void main(String[] args) {
        HelloJNI obj = new HelloJNI();
        obj.nativeMethod();
    }
}

在c代码中,通过子类的实例调用父类的被覆盖的函数方法如下:

JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_nativeMethod
  (JNIEnv * env,jobject thisObj){
     // 在JVM中查找指定的Class对象
     jclass parentClass=(*env)->FindClass( env,"com/huachao/java/Parent");
     jmethodID sayHelloId = (*env)->GetMethodID(env, parentClass, "sayHello", "()V");
     if (NULL == sayHelloId) return;
     (*env)->CallNonvirtualVoidMethod(env, thisObj, parentClass,sayHelloId);
 }

运行结果如下:

Hello in Parent

注意GetMethodID函数,因为传入的是com/huachao/java/Parent对应的Class对象,因此返回的ID是指com/huachao/java/Parent类中的sayHello函数。调用父类的函数通过CallNonvirtual<TYPE>Method来实现。CallNonvirtual<TYPE>Method函数有如下几种原型:

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, va_list args);

学到这里会发现,c代码中要想访问JVM中对象,必须通过JNIEnv提供的函数来间接完成。这也进一步加深了对JNIEnv的了解。

相关文章
|
12天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
32 2
|
1天前
|
Oracle Java 关系型数据库
Java编程之旅:从基础到进阶
Java,一种广泛使用的编程语言,因其平台无关性、面向对象的特性而备受推崇。本文旨在通过简明易懂的语言和实际代码示例,引导初学者了解Java的基本概念,并逐步深入到更复杂的编程技巧。我们将从Java的安装开始,经过变量、数据类型、控制结构等基础知识的学习,最后探讨异常处理和文件操作等进阶话题。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考和启示。
|
22小时前
|
Java API 调度
Java 多线程编程详解
《Java多线程编程详解》深入浅出地讲解了Java平台下的多线程核心概念、API使用及最佳实践。从基础理论到实战案例,本书帮助读者掌握并发编程技巧,提升软件开发中的效率与性能,是Java开发者不可或缺的参考指南。
|
15天前
|
Linux Windows
IDEA如何查看每一行代码的提交记录(人员,时间)
【9月更文挑战第24天】在IntelliJ IDEA中,可通过安装GitToolBox插件并利用其功能来便捷地查看每行代码的提交记录,包括提交者、时间和提交信息。具体操作为:首先安装GitToolBox插件,然后在代码编辑区域将鼠标悬停于目标代码行以查看简要信息,或使用快捷键打开“Version Control”窗口查看详细提交历史。
260 2
|
16天前
|
算法 安全 Java
JAVA并发编程系列(12)ThreadLocal就是这么简单|建议收藏
很多人都以为TreadLocal很难很深奥,尤其被问到ThreadLocal数据结构、以及如何发生的内存泄漏问题,候选人容易谈虎色变。 日常大家用这个的很少,甚至很多近10年资深研发人员,都没有用过ThreadLocal。本文由浅入深、并且才有通俗易懂方式全面分析ThreadLocal的应用场景、数据结构、内存泄漏问题。降低大家学习啃骨头的心理压力,希望可以帮助大家彻底掌握并应用这个核心技术到工作当中。
|
2天前
|
缓存 安全 Java
Singleton:在 Java 编程中编写和使用的 6 种方法
Singleton:在 Java 编程中编写和使用的 6 种方法
5 0
|
2天前
|
安全 Java 编译器
了解final关键字在Java并发编程领域的作用吗?
【10月更文挑战第8天】在Java并发编程中,`final`关键字具有重要作用,包括保证变量的可见性和不可变性,防止对象引用被意外修改,并帮助编译器优化读取操作及消除不必要的同步。通过确保变量不可变,`final`增强了多线程环境下的安全性与性能。
|
11天前
|
Java
JAVA并发编程系列(13)Future、FutureTask异步小王子
本文详细解析了Future及其相关类FutureTask的工作原理与应用场景。首先介绍了Future的基本概念和接口方法,强调其异步计算特性。接着通过FutureTask实现了一个模拟外卖订单处理的示例,展示了如何并发查询外卖信息并汇总结果。最后深入分析了FutureTask的源码,包括其内部状态转换机制及关键方法的实现原理。通过本文,读者可以全面理解Future在并发编程中的作用及其实现细节。
|
14天前
|
Java 数据处理 调度
Java中的多线程编程:从基础到实践
本文深入探讨了Java中多线程编程的基本概念、实现方式及其在实际项目中的应用。首先,我们将了解什么是线程以及为何需要多线程编程。接着,文章将详细介绍如何在Java中创建和管理线程,包括继承Thread类、实现Runnable接口以及使用Executor框架等方法。此外,我们还将讨论线程同步和通信的问题,如互斥锁、信号量、条件变量等。最后,通过具体的示例展示了如何在实际项目中有效地利用多线程提高程序的性能和响应能力。
|
15天前
|
安全 算法 Java
Java中的多线程编程:从基础到高级应用
本文深入探讨了Java中的多线程编程,从最基础的概念入手,逐步引导读者了解并掌握多线程开发的核心技术。无论是初学者还是有一定经验的开发者,都能从中获益。通过实例和代码示例,本文详细讲解了线程的创建与管理、同步与锁机制、线程间通信以及高级并发工具等主题。此外,还讨论了多线程编程中常见的问题及其解决方案,帮助读者编写出高效、安全的多线程应用程序。