Linux下使用JNI的常见问题及解决方案

Posted IT小不点

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux下使用JNI的常见问题及解决方案相关的知识,希望对你有一定的参考价值。

JNI是java和C/C++混合编程的接口,可以很方便地实现java调用C/C++语言。具体的使用方法,网上有很多教程,在此不做过多介绍。本博客只关注在使用JNI的过程中的常见问题。

1.     生成头文件用命令:javah*.class

     这是错误的。运行上述命令会提示:java.lang.IllegalArgumentException: Not a valid class name:SegNative.class错误。错误原因和利用java命令运行程序一样,只需要指出前缀即可,无需给出.class后缀。

2.     版本问题

    jdk6和jdk7中某些JNI方法稍有不同,注意转换。例如,C中获取字符串的方法GetStringUTFChars在两个jdk版本中就不同。老的jdk6版本使用方法为:

[cpp] view plain copy
 
 print?
  1. char* name=(char*)(*env)->GetStringUTFChars(env,Name,NULL);  

而在jdk7中,方法调用变为:

 

[cpp] view plain copy
 
 print?
  1. const char* name=env->GetStringUTFChars(Name,0);  

其他的版本问题及函数参数含义可以通过查看API获得更全面的资料。

 

 

3.     利用g++编译源文件找不到jni.h头文件

可以在编译时利用-I选项指定jni.h头文件所在目录:

 

[cpp] view plain copy
 
 print?
  1. g++ -I/usr/local/jdk1.7.0_25/include/ ……  

4.     利用g++编译源文件找不到jni_md.h

 

这是因为在jni.h中引用了jni_md.h头文件,该头文件和jni.h不在一个目录下,所以我们还需要再指定jni_md.h的目录:

 

[cpp] view plain copy
 
 print?
  1. g++ -I/usr/local/jdk1.7.0_25/include/  -I/usr/local/jdk1.7.0_25/include/linux/……  

可以看出jni_md.h放在和jni.h同级的目录linux下。

 

5.     不会生成动态链接库

生成动态链接库,需要在编译时声明-shared选项:

 

[cpp] view plain copy
 
 print?
  1. g++ -I /usr/local/jdk1.7.0_25/include/  -I /usr/local/jdk1.7.0_25/include/linux/SegNative.cpp –shared –o lib***.so  

此外,我们也无需先生成相应的.o文件,直接指定动态链接库的名字即可。

 

6.     编译动态链接库报错:couldnot read symbols: Bad value

需要在编译的时候指定选项:-fPIC。

 

[cpp] view plain copy
 
 print?
  1. g++ -I /usr/local/jdk1.7.0_25/include/  -I /usr/local/jdk1.7.0_25/include/linux/SegNative.cpp –shared –o lib***.so -fPIC  

7.     运行的时候找不到动态链接库

 

这个问题主要有两个原因:

 

  • 生成的动态链接库名字不对:我们在java语言中声明的动态链接库如果名为A,则我们在编译时则需要将动态链接库的名字声明为libA.so,否则会报错。
  • 路径不对,java找不到动态链接库。java会在特定的目录寻找动态链接库,可以通过打印java.library.path查看java会在哪些目录查找动态链接库:

 

[cpp] view plain copy
 
 print?
  1. System.out.println(System.getProperty("java.library.path"));  

我的电脑打印结果为:

 

.:/opt/intel/impi/3.2.1.009/lib/:/usr/local/cuda/lib/:/root/NVIDIA_CUDA_SDK/lib/:/root/NVIDIA_CUDA_SDK/common/lib/:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib

我们可以看到相关lib都包括在该路径下。特别注意一点是第一个路径是.,这表示java会在当前路径下寻找相关动态链接库。因而只要我们将动态链接库和.class文件放在一起就不会存在找不到动态链接库的问题。如果打印中不包括当前目录,我们可以通过修改LD_LIBRARY_PATH指定当前目录。

        此外,我们也可以在运行的过程中指定:

 

[cpp] view plain copy
 
 print?
  1. java –Djava.library.path=”/home/savedlib/”executablefile  

利用这种方法,程序可以指定不在当前目录的动态链接库。

8.     如何返回String数组

我们采用本地化的目的不仅仅是为了计算,我们也需要返回结果。如果返回的结果是一个String数组,则可以仿照下面的代码实现:

[cpp] view plain copy
 
 print?
  1. JNIEXPORT jobjectArray JNICALL Java_Segment_segment(JNIEnv *env, jobject obj, jstring Name, jint seg_len, jint stride)  
  2. {  
  3.     const char* name=env->GetStringUTFChars(Name,0);  
  4.     int clip_num;  
  5.     name_list* head,*temp;//结构体是只包含一个char数组的链表  
  6.   
  7.     CReadWav wavFile;  
  8.     head=wavFile.segment(name,seg_len,stride,&clip_num);  
  9.   
  10.     //1 获得字符串的类  
  11.     jclass str_cls=env->FindClass("java/lang/String");  
  12.     if(str_cls==NULL) return NULL;  
  13.     //2 将返回结果的元素设置为String类型  
  14.     jobjectArray  result=env->NewObjectArray(clip_num,str_cls,NULL);  
  15.     if(result==NULL) return NULL;  
  16.   
  17.     temp=head;  
  18.     for(int i=0;i<clip_num;i++)  
  19.     {  
  20.     //3 由char*获得一个String  
  21.         jstring sn=env->NewStringUTF(temp->name);  
  22.     //4 将String赋值给返回结果  
  23.         env->SetObjectArrayElement(result,i,sn);  
  24.         temp=temp->next;  
  25.     }  
  26.     return result;  
  27. }  

由代码可以看出,返回一个String数组的方法很简单:首先声明返回结果为一个数组,数组的每一个元素都是String,然后为每一个元素赋值。此外我们也要注意版本问题,在编写的时候按照自己的jdk版本选择相应的函数,可以通过查看jni.h来了解相关函数的参数。

9.     读取String数组

假设我们需要从String数组中读取一系列字符串,则本地函数参数有一个为:jobjectArrayNames,类型是object的数组。在本地代码访问中需要首先将其转换成jstring,然后在转成constchar*的本地字符串数组,代码如下:

[cpp] view plain copy
 
 print?
  1. int file_num=env->GetArrayLength(Names);  
  2.   
  3. for(int i=0;i<file_num;i++)  
  4. {  
  5.     jstring Name=(jstring)env->GetObjectArrayElement(Names,i);  
  6.     const char* name=env->GetStringUTFChars(Name,0);  
  7. }  

我们可以看出,访问字符串数组的流程很简单:首先获得字符串数组的长度;然后获得每一个字符串,由于返回的结果是object类型,我们需要将其强转成jstring类型;最后再将jstring类型转换成constchar*类型即可在本地函数中使用。

10.     返回自定义对象数组

这可能是jni应用中最复杂的情况了。返回自定义对象就非常麻烦,加上数组又使情况复杂化。我们用一个实例来讲解如何返回自定义对象数组。首先定义一个java类表示要返回的对象:

[java] view plain copy
 
 print?
  1. public class Feature  
  2. {  
  3.     private String name;  
  4.     private int length;  
  5.     private float[] data;  
  6.   
  7.     public Feature()  
  8.     {  
  9.     }  
  10.   
  11.     public String getName()  
  12.     {  
  13.         return this.name;  
  14.     }  
  15.   
  16.     public int getLength()  
  17.     {  
  18.         return this.length;  
  19.     }  
  20.   
  21.     public float[] getData()  
  22.     {  
  23.         return this.data;  
  24.     }  
  25. }  

该对象有三个成员变量,一个是String,一个是int,一个是float数组。还有一个默认的构造函数和三个get函数分别返回相关成员变量的值。接下来给出本地函数的实际操作过程:

[cpp] view plain copy
 
 print?
  1. //1 获得Feature类  
  2. jclass feature_cls=env->FindClass("Feature");  
  3. if(feature_cls==NULL) return NULL;  
  4.   
  5. //2 创建一个元素类型为Feature的数组  
  6. jobjectArray  result=env->NewObjectArray(file_num,feature_cls,NULL);  
  7. if(result==NULL) return NULL;  
  8.   
  9. //3 获得Feature类的默认构造函数  
  10. jmethodID constructor=env->GetMethodID(feature_cls,"<init>","()V");  
  11. if(constructor==NULL) return NULL;  
  12.   
  13. //4 获得Feature的三个成员变量  
  14. jfieldID nameid=env->GetFieldID(feature_cls,"name","Ljava/lang/String;");   
  15. if(nameid==NULL) return NULL;  
  16. jfieldID lengthid=env->GetFieldID(feature_cls,"length","I");//int  
  17. if(lengthid==NULL) return NULL;  
  18. jfieldID dataid=env->GetFieldID(feature_cls,"data","[F");//float array  
  19. if(dataid==NULL) return NULL;  
  20.   
  21. for(int i=0;i<file_num;i++)  
  22. {  
  23.     //5 创建一个新的Feature对象  
  24.     jobject feature_obj=env->NewObject(feature_cls,constructor);  
  25.   
  26.     //6 设置对象的成员变量值  
  27.     env->SetObjectField(feature_obj,nameid,env->NewStringUTF(head->name));  
  28.     env->SetIntField(feature_obj,lengthid,head->length);  
  29.     jfloatArray floatarray=env->NewFloatArray(head->length);  
  30.     env->SetFloatArrayRegion(floatarray,0,head->length,head->data);  
  31.     env->SetObjectField(feature_obj,dataid,floatarray);  
  32.   
  33.  //7 为数组的每个元素赋值  
  34.     env->SetObjectArrayElement(result,i,feature_obj);  
  35.       
  36. }  

其实关键操作还是在自定义对象的创建和赋值上,数组的操作和之前类似。为了创建一个自定义对象,我们需要利用该对象的构造函数,因为构造函数的目的即是创建一个新对象。为了给对象的成员变量赋值,我们需要获得这些成员变量,然后根据其类型赋值。更详细的解释可参考:http://dolphin-ygj.iteye.com/blog/519489

以上是关于Linux下使用JNI的常见问题及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Linux下的tar归档及解压缩功能详解

Linux系统压缩及解压缩

Linux 使用 tar 命令打包压缩及解压缩文件夹

linux命令行打包压缩及解压缩

Linux命令:压缩及解压缩

linux命令之压缩及解压缩