前些日子,有机会接触到一个JNI相关的项目,做项目时遇到一些坑爹的问题,在这里记录下来,供有需要的朋友参考。这些问题之所以坑爹,一方面是因为网上的资料比较少,有些问题说的不是很明白,另一方面也和开发环境运行环境等相关,毕竟是Native语言。
这里假设读者已经初步的了解了JNI是什么,并且可以写出一些JNI的代码。
本文提到的开发环境是:
Jdk6.0+
Windows 2003 +
VS2008 +
一、关于加载、System.loadLibrary
Java的native方法对应着一个由C/C++写的动态链接库,在调用native方法前需要先加载这个动态链接库,这里要注意,dll动态链接库的位置,一定要在 java.library.path 路径下。别问我这个路径是什么,不知道的把它打印出来,选个合适的路径放进去,当然还要把动态链接库所依赖的相关资源也放进去。我比较习惯放在 jre/lib/i386 目录下,这个纯粹是个人习惯。
路径放对了,那就要加载,我习惯在有native调用的这个类里面写一个static块代码进行加载,也就这么一句 System.loadLibrary("XXX");这里要说一下,这个XXX是不要写后缀名的,写了就报错。我估计是Java的设计者们为了跨平台考虑的,毕竟linux环境下是so文件甚至没有后缀名,而在windows环境下就不一样,写java代码时把所有的动态库都以同样的名称命名,这样子在不同平台下系统会自动选择合适的链接文件。
二、关于调试
习惯了在一个语言环境下开发的筒子们突然进入跨语言层开发,估计最不适应的就是代码的调试问题了,程序崩溃了,是Java代码的原因还是C++代码的原因?或者是环境的问题?这些问题的确很蛋疼。而且跨语言调试的确没有啥好的代码调试工具,我只好用最古老的办法,一句一句的打印到输出上来跟踪代码到底运行到什么地方,确定出问题的语句。其实这个过程并不复杂,但是要耐得住烦,不怕烦最后一定可以调出来的。
Android环境下JNI开发好像有一套很好的工具,做Android的同学不妨去找找看。
三、jclass变量
我是真没理解这个变量具体是怎么一回事,按理说jclass 就是描述一个类的定义或者类似的事情,但是在JNI中,有俩种jclass变量,一种是通过 env->FindClass 获得的,而另一种是通过env->GetObjectClass( jobject )获得的。当时这个问题困扰了我很长时间,后来总结出了,如果你想调用某个类中的static方法,那么你就用第一种方式获取这个jclass变量。而如果你是想改变某个object对象中某个属性的值(注意是object)这个时候set方法会让你传入一个jclass值,这时候你就可以通过第二种方式获取这个jclass变量了。
四、在不同的线程里回调Java方法
通常做JNI开发的时候,很有可能会用到观察者模式或者类似的设计,这个时候就要求我们要使用C++代码反过来调用Java的代码。而且绝大多数情况,都是在异步线程中间调用的,如Windows消息处理函数等。
网上很多人的博客都是这么建议的: 加载动态链接库 %JAVA_HOME%/jre/client/jvm.dll 然后调用这个库的很多代码什么的。这里我要说一下,这个方法是一个C++应用程序去调用一个Java库或者class的方法,这种方法下,宿主语言是C++,而我们开发JNI程序,宿主语言任然是Java。
解决方法如下:
虽然JNIEnv* 变量的生存周期是无法跨线程的,但是 JavaVM* 变量是可以在多个线程中公用的。我们可以通过
extern JavaVM *gs_jvm ;
env->GetJavaVM(&gs_jvm) ;
创建一个全局的JavaVM 实例,然后在新的线程中调用:
jvm->AttachCurrentThread( (void**)&env , NULL ) ;
就可以获得一个JNI上下文环境
这样就可以在不同的线程去操控jobject了。这里要提一下,jobject对象也并非全局了,不过你可以通过env->NewGlobalRef( jobject ) 方法获得一个全局的jobject实例。
既然说到观察者模式,这里我给大家一个建议,使用JNI的时候,不要把太多的逻辑放到C++代码中去处理,观察者模式使用的时候可以考虑变通一下,把回调方法设置成一个静态的Java方法,观察者队列的维护和消息分发采用Java代码来处理可以减少很多麻烦。
五、native方法阻塞无法返回。
我曾经遇到一个很诡异的情况,一个native方法调用迟迟无法结束,我以为是我写的C++代码发生了阻塞,于是跟踪了一遍,结果发现没有,我的C++函数是已经return了的,但是我的Java native方法就是在阻塞中。当时这个问题完全超出了我的认知范围,以为大白天的撞见鬼了,吓了一身冷汗。
如果筒子们也遇到这种情况,不要慌张,其实native方法调用,调用的并非只有我们所写的C++代码,调用之前会做一些初始化工作,调用结束后会做一些扫尾工作,这里代码无缘无故的阻塞就是在这扫尾工作中出现了问题。具体什么问题,我也说不上,有可能是内存问题,也有可能是存在未处理的异常等等。最好的方法就是在C++方法结束之前加上这么一句代码
if( env->ExceptionOccurred() )
{
env->ExceptionDescribe() ;
}
看看是否有异常,一般看看虚拟机对异常的描述大概也就清楚问题出现在哪里了。
好了,就是这么多。卖个萌,喵 ~~~~~