Java中JNI的使用详解第四篇:C/C++中创建Java对象和String字符串对象及对字符串的操作方法

首先来看一下C/C++中怎么创建Java对象:在JNIEnv中有两种方法是用来创建Java对象的:

第一种方法:

jobject  NewObject(jclass clazz  , jmethodID methodID, ....):

参数解释:

clazz:这个很简单,就是需要创建的Java对象的Class对象

methodID:这个是传递一个方法的ID,想一想Java对象在创建的时候,需要执行什么方法呢?对,没错那就是构造方法

第三个参数:是构造函数需要传入的参数值(默认的构造方法是不需要传入这个参数的)

所以我们在创建Java对象的时候之前要做的工作就是要获取这个对象的class对象,然后再获取该对象的构造方法,想要获取方法的id,就需要方法的签名,因为构造方法没有返回值,所以我们认为类的默认构造方法的返回值类型的签名始终是“()V”(因为默认的构造方法是没有参数的),方法的名称始终为“<init>”

例子:在C++中构造Java中的Date对象,并且调用它的getTime()方法打印当前时间

Java中的代码不需要改变,主要是C++代码中的改写:

  1. #include<iostream.h>
  2. #include "com_jni_demo_JNIDemo.h"
  3. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
  4. {
  5. //获取Java中Date对象的Class对象
  6. jclass clazz_date = env->FindClass("java/util/Date");
  7. //获取构造方法的id
  8. jmethodID mid_date = env->GetMethodID(clazz_date,"<init>","()V");
  9. //生成Date对象
  10. jobject now = env->NewObject(clazz_date,mid_date);
  11. //获取Date对象中的getTime方法的id
  12. jmethodID mid_date_getTime = env->GetMethodID(clazz_date,"getTime","()J");
  13. //调用getTime方法返回时间
  14. jlong time = env->CallLongMethod(now,mid_date_getTime);
  15. //打印时间,这里要注意的是不能使用cout输出了,因为cout并没有对__int64的输出进行重载,要输出的话用printf("%I64d",time);
  16. printf("%I64d",time);
  17. }

编译成.dll文件,在Eclipse中运行结果如下:

Java中JNI的使用详解第四篇:C/C++中创建Java对象和String字符串对象及对字符串的操作方法

这个输出的时间没有进行格式化处理。

第二种方法:

创建一个对象:就是用AllocObject

使用函数AllocObject可以根据传入的jclass创建一个Java对象,但是他的状态时非初始化的,在是哟个这个对象之前绝对要用CallNonvirtualVoidMethod来调用该jclass的构造函数,这样就可以延迟构造函数的调用,这一部分用的很少,只做简单的说明:

Java中的代码不做任何修改,C++代码中修改成如下:

  1. #include<iostream.h>
  2. #include "com_jni_demo_JNIDemo.h"
  3. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
  4. {
  5. //获取java中的Date对象
  6. jclass clazz_date = env->FindClass("java/util/Date");
  7. jmethodID methodID_str = env->GetMethodID(clazz_date,"<init>","()V");
  8. jobject now = env->AllocObject(clazz_date);
  9. //调用构造方法
  10. env->CallNonvirtualVoidMethod(now,clazz_date,methodID_str);
  11. //获取Date对象中的getTime方法的id
  12. jmethodID mid_date_getTime = env->GetMethodID(clazz_date,"getTime","()J");
  13. //调用getTime方法返回时间
  14. jlong time = env->CallLongMethod(now,mid_date_getTime);
  15. //打印时间,这里要注意的是不能使用cout输出了,因为cout并没有对__int64的输出进行重载,要输出的话用printf("%I64d",time);
  16. printf("%I64d",time);
  17. }

这种方式是很少用的!

下面来看一下C/C++中如何操作Java中的字符串


首先来了解一下Java和C/C++中字符串的却别:
(1). 在Java中,使用的字符串String对象是Unicode(UTF-16)码,即每个字符不论是中文还是英文还是符号,一个字符总是占两个字节
(2). Java通过JNI接口可以将Java的字符串转换到C/C++中的宽字符串(wchar_t *),或是传回一个UTF-8的字符串(char*)到C/C++,反过来,C/C++可以通过一个宽字符串,或是一个UTF-8编码的字符串来创建一个Java端的String对象

接下来看一下JNIEnv中的一些C++方法:



1.获取字符串的长度:

jsize GetStringLength(jstring j_msg);

参数:j_msg:是一个jstring对象

2.将jstring对象拷贝到const jchar*指针字符串

//这个方法是:拷贝Java字符串并以UTF-8编码传入jstr

env->GetStringRegion(jstring j_msg , jsize start , jsize len , jchar* jstr);

//这个方法是:拷贝Java字符串并以UTF-16编码传入jstr

env->GetStringUTFRegion(jstring j_msg , jsize start , jsize len , char* jstr);

在Java1.2出来的函数,这个函数的动作时把Java字符串的内容直接拷贝到C/C++的字符串数组中,在呼叫这个函数之前必须有一个C/C++分配出来的字符串(具体看下面的例子),然后传入到这个函数中进行字符串的拷贝

由于C/C++中分配内存开销相对小,而且Java中的String内容拷贝的开销可以忽略,更好的一点是此函数不分配内存,不会抛出OutOfMemoryError异常

参数:j_msg:是一个jstring对象,start是拷贝字符串的开始位置,len是拷贝字符串的长度,jstr是目标指针字符串

3.生成一个jstring对象

jobject NewString(const jchar* jstr , int size);

参数:jstr是字符串指针,size是字符串长度

这个方法可以认为是将字符串指针jstr转化成字符串对象jstring

4.将jstring对象转化成const jchar*字符串指针

(1) const* jchar* GetStringChars(jstring j_msg , jboolean* copied);

返回一个UTF-16编码的宽字符串(jchar*)

参数:

j_msg是字符串对象

copied是指传入的是一个jboolean指针,用来标识是否对Java的String对象进行了拷贝的,如果传入的这个jboolean指针不是NULL,则它会给该指针所指向的内存传入JNI_TRUE或JNI_FALSE标识是否进行了拷贝,传入NULL表示不关心是否拷贝字符串,它就不会给jboolean* 指向的内存赋值

其对应的释放内存指针的方法:

ReleaseStringChars(jstring  j_msg , const jchar* jstr);

参数:j_msg是jstring对象,jstr是字符串指针

在这里还有一个方法就是:

(2) const char* GetStringUTFChars(jstring str , jboolean* copied)

这个方法是可以取得UTF-8编码的字符串(char*)

参数的含义和GetStringChars方法是一样的

这个方法也有对应的一个释放内存的方法:

ReleaseStringUTFChars(jstring jstr , const char*str);

参数的含义和上面的ReleaseStringChars方法的参数的含义是一样的

需要注意的是:

这两个函数分别都会有两个不同的动作:

(1) 开辟一个新内存,然后在Java中的String拷贝到这个内存中,然后返回指向这个内存地址的指针

(2) 直接返回指向Java中String的内存的指针,这个时候千万不要改变这个内存的内容,这个将会破坏String在Java中始终是常量的这个原则

5.将jstring对象转化成const jchar*字符串指针

const jchar* GetStringCritical(jstring j_msg , jboolean* copied);

参数:j_msg是字符串对象,copied上面的方法已经做了解释了,这里就不多说了

这个方法的作用是为了增加直接传回指向Java字符串的指针的可能性(而不是拷贝),JDK1.2出来了新的函数GetStringCritical/ReleaseStringCritical,

在GetStringCritical/ReleaseStringCritical之间是一个关键区,在这个关键区域之间不能呼叫JNI的其他函数和会造成当前线程中断,或是会让当前线程等待的任何本地代码,否则将造成关键区代码执行期间垃圾回收器停止运作,任何触发垃圾回收器的线程也会暂停,其他的触发垃圾回收器的线程不能前进直到当前线程结束而激活垃圾回收器

在关键区域中千万不要出现中断操作,或是在JVM中分配任何新对象,否则会造成JVM死锁

虽说这个函数会增加直接传回指向Java字符串的指针的可能性,不过还是会根据情况传回拷贝过的字符串

不支持GetStringUTFCritical,没有这样的函数,由于Java字符串用的是UTF-16,要转成UTF-8编码的字符串始终需要进行一次拷贝,所以没有这样的函

这个方法和第四个方法是一样的功能

其对应的释放内存指针的方法:

env->ReleaseStringCritical(jstring j_msg , const jchar* jstr);

下面来看一下实例:在Java中定义一个String属性,通过控制台输入值,然后定义一个本地方法callCppFunction,在C++中这个方法的实现就是:获取到Java中这个字符串属性,将其进行倒序操作然后,然后再Java中输出:

先来看一下Java中的代码:

[java] view
plain
copyJava中JNI的使用详解第四篇:C/C++中创建Java对象和String字符串对象及对字符串的操作方法Java中JNI的使用详解第四篇:C/C++中创建Java对象和String字符串对象及对字符串的操作方法
  1. package com.jni.demo;
  2. import java.io.BufferedReader;
  3. import java.io.InputStreamReader;
  4. public class JNIDemo {
  5. //定义一个本地方法
  6. public native void callCppFunction();
  7. //定义一个String属性
  8. public String msg = null;
  9. public static void main(String[] args)throws Exception{
  10. //调用动态链接库
  11. System.loadLibrary("JNIDemo");
  12. //从控制台中获取值
  13. BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
  14. String str = reader.readLine();
  15. JNIDemo jniDemo = new JNIDemo();
  16. jniDemo.msg = str;
  17. jniDemo.callCppFunction();
  18. System.out.println(jniDemo.msg);
  19. }
  20. }

在来看一下C++代码:

  1. #include<iostream>
  2. #include"com_jni_demo_JNIDemo.h"
  3. #include"windows.h"
  4. #include<string>
  5. #include<algorithm>
  6. using namespace std;
  7. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_callCppFunction (JNIEnv * env, jobject obj)
  8. {
  9. //获取java中的属性:msg
  10. jfieldID fid_msg = env->GetFieldID(env->GetObjectClass(obj),"msg","Ljava/lang/String;");
  11. //获取属性msg的对象
  12. jstring j_msg = (jstring)env->GetObjectField(obj,fid_msg);
  13. /**第一种方式START*/
  14. /*
  15. //获得字符串指针
  16. const jchar* jstr = env->GetStringChars(j_msg,NULL);
  17. //转换成宽字符串
  18. wstring wstr((const wchar_t*)jstr);
  19. //释放指针
  20. env->ReleaseStringChars(j_msg,jstr);
  21. */
  22. /**第一种方式END*/
  23. /**第二种方式START*/
  24. /*
  25. //获取字符串指针
  26. const jchar* jstr = env->GetStringCritical(j_msg,NULL);
  27. //转换成宽字符串
  28. wstring wstr((const wchar_t*)jstr);
  29. //释放指针
  30. env->ReleaseStringCritical(j_msg,jstr);
  31. */
  32. /**第二种方式END*/
  33. /**第三种方式START*/
  34. //获取字符串的长度
  35. jsize len = env->GetStringLength(j_msg);
  36. //生成长度为len的字符串指针
  37. jchar* jstr = new jchar[len+1];
  38. //C++中字符串以'\0'结尾,不然会输出意想不到的字符
  39. jstr[len] = L'\0';
  40. //将字符串j_msg复制到jstr中
  41. env->GetStringRegion(j_msg,0,len,jstr);
  42. //转换成宽字符串
  43. wstring wstr((const wchar_t*)jstr);
  44. //释放指针
  45. delete[] jstr;
  46. /**第三种方式END*/
  47. //将字符串进行倒序
  48. reverse(wstr.begin(),wstr.end());
  49. //获取倒序后新的字符串
  50. jstring j_new_str = env->NewString((const jchar*)wstr.c_str(),(jint)wstr.size());
  51. //将新的字符串设置变量中
  52. env->SetObjectField(obj,fid_msg,j_new_str);
  53. }

这里使用了三种方式实现功能。这里要注意的是还有一个方法就是将const jchar*转换成wstring,因为reverse方法接受的参数是wstring。在Eclipse中运行结果如下:

Java中JNI的使用详解第四篇:C/C++中创建Java对象和String字符串对象及对字符串的操作方法

到这里这一篇的内容就讲到着了,后面的内容更精彩!

上一篇:codeforces C. Arithmetic Progression 解题报告


下一篇:JAVA 第五周学习总结