• 【再探JNI__2】


    昨天,我写了一下JNI的基本组成和功能。今天继续完善以下几个部分

      0:关于实例引用和类引用(昨天遗留的关于第二个参数的问题)

    其实这个有点想太多了。今天验证了一下,写了两个不同的native方法。

    //Demo.java
    class Demo { //略... private static native void StaticMethod(String userInput);//静态方法 private native void InstaceMethod();//实例方法
    public static void main(String args[]) { //省略... } }

     对应编译出来的头文件是这样的

    //略...
    JNIEXPORT void JNICALL Java_Demo_StaticMethod
      (JNIEnv *, jclass, jstring);
    //...
    JNIEXPORT void JNICALL Java_Demo_InstaceMethod (JNIEnv *, jobject); //略...

     一些简单的例子都不会用到jclass,但是一旦有用到静态的方法去操作一些东西,就会用到jclass这个类引用了。他们唯独的区别其实也就在这了。

      1:JNI的传参

      在上一篇博文里已经提到了JAVA层的对象传递给了JNI层。它是通过一个C指针类型,指向了JAVA VM的内部结构,因此千万不可以为了效率或者方便起见而绕过JNI的内部函数直接操作这个指针。同时JNI中的前面两个参数也在上篇文章已经介绍过,如果涉及到在native方法内传递参数,那么就涉及到第三个参数了。比如需要传递一个字符串,那么第三个参数就会是jstring。这里的jstring是一种类型。在JNI中,数据类型基本上可以这么认为,在原有的大家所熟悉的数据类型前面加了j。比如int类型为jint、boolean为jboolean。对应的方法也一应俱全,相信老前辈们已经将它完善的很好了。况且如今IDE这么强大(我目前都是用sublime写java,命令行编译,更别说别的IDE了),再加上网络。用多了自然就熟悉啦。下面举个小例子介绍一下。

        1.1字符串

    先看java代码:

    //Prompt.java
    class Prompt { static { System.loadLibrary("Prompt");//提示加载名字为Prompt.dll库 } private native String getLine(String prompt);//声明native方法 public static void main(String argv[]) { Prompt p = new Prompt(); String input = p.getLine("Type a line:");//字符串作为参数 System.out.println("User typed:" + input); } }

       

    //Prompt.c
    #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <jni.h> #include "Prompt.h" JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt) { char buf[128]; const jbyte *str; //将字符串转换成UTF-8 str = (*env)->GetStringUTFChars(env, prompt, NULL); if (str == NULL) { return NULL; } printf("%s", str); //释放转换时候的内存 (*env)->ReleaseStringUTFChars(env, prompt, str); scanf("%s", buf); //构建一个新的字符串返回 return (*env)->NewStringUTF(env, buf); }

       如果直接打印printf("%s", prompt);jstring和char*是不同类型。需要转换成C能识别的char*,JNI支持Unicode/UTF-8字符编码互转的。这里的 几个方法要记住,字符串操作很普遍哦。这里看记得调用ReleaseStringUTFChars释放GetStringUTFChars中分配的内存。其中“str = (*env)->GetStringUTFChars(env, prompt, NULL);”中第三个参数原型是jboolean *。为JNI_TRUE会返回一个拷贝,不关心的时一般设置为NULL。因此这里不确定是否会分配内存。由于GC(垃圾回收机制)的原因,这里可以理解是回收内存,或者回收了一个内存占用标记。如果是后者,那么string的内存呢暂时未被及时回收,并且又有大量的操作,这样就很有可能导致程序崩溃,这是不安全的,所以要及时释放内存。这里也体现了一点,JNI让JAVA丢失了一部分安全性,需要程序员自己去考虑了。

        1.2数组

        这里代码就不贴了,上一篇文章中有一段关于二维数组的代码。

        数组是由基本数据类型构成的集合,先调用FindClass获得一个一维某类型数组。可以调用NewObjectArray分配一个对象数组。也是Stirng的操作方式。

        数组类型就不再是基本数据类型的传递了,它大致和下面要说的类成员和方法的访问方式类似。这里不多说了。

      2:类的成员和方法的访问

        2.1成员的访问方式

          有静态成员和实例成员两种访问方式。其实过程都一样,这里贴出静态成员访问方法,实例成员只要把实现方法中的static去掉就可以了,其实这都是VS会自动提示你的。只管放心大胆的用。

    //访问类的静态成员
    JNIEXPORT void JNICALL Java_StaticField_accessField
    (JNIEnv *env, jobject obj)
    {
    	jclass jcls;
    	jint num;
    	jfieldID jfid;
    
    	//得到jclass
    	jcls = (*env)->GetObjectClass(env, obj);
          //GetObjectClass和FindClass一样,都能得到jclass。前者需要jni传入的一个引用,后者需要完整类名 if (NULL == jcls) { return; } //得到jfield jfid = (*env)->GetStaticFieldID(env, jcls, "num", "I");//和取得实例成员ID的不同的是加了static if (NULL == jfid) { return; } //得到成员值 num = (*env)->GetStaticIntField(env, jcls, jfid); printf("in C: "); printf("StaticField.num = %d ", num); //构建并修改值 (*env)->SetStaticIntField(env, jcls, jfid, 9999); }

        2.2类方法的访问方式

      

    JNIEXPORT void JNICALL Java_StaticMethod_StaticMethod
    (JNIEnv *env, jobject obj)
    {
    	jclass jcls;
    	jmethodID jmid;
    	
    	jcls = (*env)->GetObjectClass(env, obj);
    	if (NULL == jcls)
    	{
    		return;
    	}
    
    	jmid = (*env)->GetStaticMethodID(env,jcls,"callback","()V");
    	if (NULL == jmid)
    	{
    		return;
    	}
    
    	//改进方法
    	static jmethodID jmid_s;
    	if (NULL == jmid_s)
    	{
    		jmid_s = (*env)->GetStaticMethodID(env, jcls, "callback", "()V");
    		if (NULL == jmid_s)
    		{
    			return;
    		}
    	}
    		//再将下边的jmid改成jmid_s,这样便可以实现加载一次之后,知道被卸载都不再对名称和符号表进行查询操作
    		//可以将初始化的field和method一起缓存。这样再次装载就会更新值。
    
    		//改进二:可以首次加载的时候将一个类的所有field和method的信息加载进Hash表。
    		//以后的操作只要最这个表进行操作就OK了。用名字和类型做键值
    
    	printf("In C call the Java instance method:
    -------------
    ");
    	(*env)->CallStaticVoidMethod(env, jcls, jmid);
    }
    

        2.3 访问优化方式

         见变代码中的注释。同样可以类推到其他方式总

      3:关于全局引用和局部引用

      JNI支持3种引用,全局引用,局部引用,弱全局引用。

      全局引用:和全部变量差不多,和程序共存亡,必须通过NewGlobalRef由程序员主动创建。

      局部引用:每创建一个实例就会返回一个指向这个实例的局部引用,只在本线程中native方法中有效。当native 返回时会自动释放。

      弱全局引用 特性与GlobalRef相同,但该类型的引用不保证不被自动回收

  • 相关阅读:
    1130 host '***' is not allowed to connect to this MySQL server
    签名时出错,未能对....ext签名。SignTool Error: No certificates...
    C# 进制转换(二进制、十六进制、十进制互转)
    在安装32位Oracle客户端组建的情况下以64位模式运行
    Vue中引入jQuery
    sql server数据库分离时,数据库右侧显示(单个用户)
    解决Typora图片显示问题
    Ruby日文手册翻译1
    Boost Graph Library 库小结1
    归并排序
  • 原文地址:https://www.cnblogs.com/ZrBlog/p/4392438.html
Copyright © 2020-2023  润新知