曾几何时,我们抱怨自己没有得到重用,曾几何时,我们羡慕他人创业成功,曾几何时我们看着别人高升眼红。
也许你的今日就是你期待已久的美好未来?一起观摩南美阿根廷带来的一个短剧“El empleo(雇工人生)”。
曾几何时,我们抱怨自己没有得到重用,曾几何时,我们羡慕他人创业成功,曾几何时我们看着别人高升眼红。
也许你的今日就是你期待已久的美好未来?一起观摩南美阿根廷带来的一个短剧“El empleo(雇工人生)”。
愿已去的人们安息。
世界和平,空气清新!
我们首先来看一枚业界新闻:
我们看到了Java语言生命力的旺盛,特别是在Java
2之后的版本,也就是JDK5(Tiger)发布之后,Java语言的应用更是爆炸式的增长,时值互联网飞速成长迅猛发展之际,Java语言与生俱来的跨平台特性以及良好且可控的安全性成为了互联网技术企业首选的开发语言。借着Sun公司当年在服务器市场上强劲的表现,Java语言顿时成为TIOBE排行榜上的新贵。
Java应用如此成熟与广泛,在Google.com上搜索Java得到的结果条数是“Results 1 – 10 of about 196,000,000 for Java”,搜索C++得到的结果条数是“ Results 1 – 10 of about 53,300,000 for C++
”,那么我们再来看看关于JNI的搜索结果“ Results 1 – 10 of about 1,980,000 for Java Native Interface
”和Java 2D做一个对比“ Results 1 – 10 of about 2,710,000 for Java 2D”。从这些结果中我们能看出一些东西,Java
Native Interface(JNI)相较于Java中并不常用的Java 2D API的关注程度还低,也就是说JNI其实跟Java主体上的流行半毛钱关系都没有,那么JNI为什么要存在呢,JNI又能给我们带来什么呢?
Java语言的成功很大程度上得于API的丰富和设计的良好,Java API为广大开发人员节省了大量的时间提高了开发效率,使得项目的交付更为敏捷。但是API注定存在其局限性,那么我们在什么时候需要使用到JNI呢?
1. 所开发的应用程序要使用到与平台相关的属性,而Java 标准类库不支持对这些属性的处理;
2. 已经拥有了用其他编程语言实现的应用程序或库,希望用Java 直接调用这些实现;
3. 程序的某个模块对运行的时间效率要求很高,从而希望用较低级的语言(如汇编)来实现,同时希望在Java 应用程序中使用这个模块。
从目前市场上对JNI应用的案例来看,最为主要的应用场景一般是源于上面的第二点原因,在已有类库的基础上进行开发平台的迁移。近来比较热门的Android移动终端OS平台就是基于JNI技术来实现的,本质上Android的核是Linux内核的子集,基于该内核使用Java平台进行了良好的封装,提供给开发人员一个高可用性的SDK
Platform。开发人员只需了解Java平台的特性,根据API
Documentation就完全可以开始编码了,并不需要知道其内部的实现机制,着实是为开发人员谋福利啊,当然此举更是从根本上劫持了诸多原本不属于该开发阵营中的开发人员,任何熟悉Java平台开发的开发人员顿时全成为了Android平台开发的潜在用户。技术也是可以带来市场的。
这就是JNI给我们带来的,借助Java语言的跨平台特性,以及开发阵营的强大,各大企业和机构基于已有的类库和程序进行二次封装,不仅能延续产品的持续发展,还能扩大二次开发人员的阵营,而不落于IT技术发展的潮流。说了这么多废话,那么如何使用JNI技术来进行二次封装呢?
1. 编写并编译Java代码
使用称手的工具(IDE)编写Java代码,例如保存在” E:\JavaWorkspace\Laputa\com\laputa\jni\test\HelloJNIWorld.java”
package com.laputa.jni.test
public class HelloJNIWorld{ public native void sayHello(); public static void main(String[] args){ HelloJNIWorld hello = new HelloJNIWorld(); hello.sayHello(); } } |
进入” E:\JavaWorkspace\Laputa\”,使用命令” javac com\laputa\jni\test\HelloJNIWorld.java”编译该文件,由于未指定编译结果路径,生成的class文件将保存在源文件同一目录下,得到”
e:\JavaWorkspace\Laputa\com\laputa\jni\test\HelloJNIWorld.class”。
2. 生成JNI头文件
使用javah命令生成头文件,”javah –jni com.laputa.jni.test.HelloWorld”,将会生成一个”
e:\JavaWorkspace\Laputa\com_laputa_jni_test_HelloJNIWorld.h”。在生成头文件的时候有两点需要注意,一是”javah –jni”
后面需要写类的全限定名(自行查阅资料了解全限定名相关知识),并且执行该命令的当前目录下要能找到全限定名中的路径,也就是说执行命令的路径应该在包路径的上一层目录,这里我们将com.laputa.jni.test包保存在”E:\JavaWorkspace\Laputa”下,那么我们执行javah命令的路径也应该是”
E:\JavaWorkspace\Laputa”;二是未指定生成头文件的目标目录时,默认将头文件生成在当前目录下。头文件的样式如下:
/* DO NOT EDIT THIS FILE – it is machine generated */
#include <jni.h> /* Header for class com_laputa_jni_test_HelloJNIWorld */ #ifndef _Included_com_laputa_jni_test_HelloJNIWorld #define _Included_com_laputa_jni_test_HelloJNIWorld #ifdef __cplusplus extern “C” { #endif /* * Class: com_laputa_jni_test_HelloJNIWorld * Method: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_com_laputa_jni_test_HelloJNIWorld_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif |
3. 实现头文件对应的方法
关于JNI头文件中的的类型以及相关的内容可以参考Java Tutorial中Java Native
Interface一章。现在我们就来实现刚才生成的头文件中的sayHello方法,至于方法名为何这么长,主要是因为JNI就是通过这样的机制来识别方法签名的,开发过程中需要注意的是,对于Java文件中的native方法最好不要使用重载,因为重载后的native方法生成的头文件中的方法将会非常之长,重载的方法名中将会包含其参数信息,例如在HelloJNIWorld.java中添加一个public
native void sayHello(String str);方法,其生成的头文件中对应方法将会是(下面标红的文字):
/*
* Class: com_laputa_jni_test_HelloJNIWorld * Method: sayHello * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_laputa_jni_test_HelloJNIWorld_sayHello__Ljava_lang_String_2 (JNIEnv *, jobject, jstring); |
这会让我们在查看代码的时候迷惑,所以建议在需要重载的时候直接使用其他的名称,放弃重载为后期维护提供可读性更高的代码。
接下来就可以开始实现我们需要的方法了,例如我的Java代码中需要调用sayHello这个本地方法,那么我们新建一个对应的cpp文件,将方法签名拷贝至cpp文件中,实现该方法。
#include “com_laputa_jni_test_HelloJNIWorld.h”
#include <iostream> using namespace /* * Class: com_laputa_jni_test_HelloJNIWorld * Method: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_com_laputa_jni_test_HelloJNIWorld_sayHello ( { } |
4. 编译链接库并添加至系统Path变量中
使用合适的编译器(VC/GCC)进行编译和链接,生成合适的链接库(dll或者so文件)。在编译过程中可能出现头文件包含出现错误的问题,主要是因为在生成的JNI头文件中有”#include
<jni.h>”语句,编译器会到系统路径下去查找外部头文件,需要我们在编译的IDE或者脚本中指定该文件所在的目录为编译时Include路径,另外”jni.h”文件中可能需要使用到与平台相关的一些文件,例如在Windows平台下需要使用到”jni_md.h”文件,该文件处于JDK安装目录的include目录下的win32路径下(其他平台也处于include的相应目录下),需要将这个文件所在目录也添加到编译时Include路径中,保证编译通过。将该文件加入系统Path目录下(不同的平台使用其特定方式进行添加,在非Windows平台下使用export命令设置名为LD_LIBRARY_PATH的路径参数值为库文件所在目录),以便Java程序能加载该库文件(Java加载库文件时,需要库文件在系统的Path下)。
5. 在Java程序中加载库文件,执行代码
在Java程序中使用System.loadLibrary(String libName)加载系统路径下的库文件(忽略后缀名)。加载库文件的代码可以使用静态域进行加载,使得类在加载之初就加载库文件,并且之后在当前JVM实例中共享使用,不需重复加载库文件。在代码中添加一段代码:
static{
System.loadLibrary(“HelloJNIWorld”); } |
重新编译,执行代码,程序将在控制台输出“Hello JNI World”
以上就是HelloWorld的整个编码过程了,流程如下:
这就是JNI编码和调用的整个流程和步骤了,以上是一个非常简单的例程,实际的产品设计中会有更多的一些技巧和设计原则在里面。
前日购进一本《Unix编程艺术》,读来甚是喜欢。此书被云风多次奉为经典,极力推荐,我个人对于云风还是存在不大不小的个人崇拜的,而且从云风一贯负责任的作风来看,云风所推必属精品咯。难得当当网有货,遂当即下单购进一本。
目前刚刚读完第一章《哲学》,觉得读来真是浑身通透直爽啊,虽然我曾听过一些论调,关于此书适合编码五年以上的主儿用来进修,不过在我这个使用电脑刚刚满五年的人看来,这也是一个用来给自己打预防针和点滴的不错选择。
以下将是针对每一章的读后感:
该系列文章可能会持续一年的时间,因为自己阅读和消化的时间可能就会相对较长,例如第一章虽然读完了,但是目前并不能很好地说出心中的感触,很多的东西必须在实践中得到不同程度的印证才会有更为深入的理解。谨以此篇博文向我们伟大的Unix先驱,以及后继的Linux新贵致以我个人最为崇高的敬意。是你们让我看到了原来世界还可以如此美好,编码其实是一件多么幸福和快乐的事情。我想我是爱这个世界的,因为这个世界上有很多很多的自由和压迫。
趁着Android SDK正在更新的时候,写点东西。今天偶尔听到领导要离职了,其实之前已经知晓他提交辞呈了,不过由于公司办离职手续一向效率非常的低(我舍友之前离职就花了整一个月的时间),一直以为至少也得等到这个月末吧,殊不知同事今天就提出了请一周假的请求了。也许是之前找工作中发现目前自己所掌握的技能在外面的公司并不是那么的通用吧(我们是做JNI开发的,主要工作是负责封装C++类库),他说前面有几个面试,有些比较顺利,但是自己并不是非常想去,有一个自己比较向往的,但是面试并不顺利。也许是认识到了找工作需要认真对待,花些时间再充下电吧,也许从明天开始我就再也见不到这个几乎每天都在指导我的领导了。说来有些伤感,不过世事无常,谁又能说自己不会走呢?谁又能说自己什么时候走呢?
入职至今,已经历经了4位同事的离职了,两位曾是我的指导人,或者说三位,一位是我的大学舍友(迄今依然是舍友)。同事的离职俨然已经不再是一件新鲜事了,可是为何还是心中稍有不爽呢。细细想来,这些人都曾经在我的职业发展路上给了我很多的帮助,他们都曾是我学习的对象,如今已经三三两两地离开了,心中难免有些不快。不过最后回到大家离职的根源上来说,可能对自己的触动会更大一些。也许有的人离职仅仅就是想离开,譬如我的舍友,离开就是为了离开,待遇并不是首要的,主要是对GIS这个行业了无兴趣,全然没有投身伟大的国产GIS软件行业,为之奉献终身的热情。但是回到最通常的原因上,我想最大的原因还是薪资待遇的问题。
我尊重这些同事,我喜欢这些同事,不仅仅是因为他们曾经多或多或少地帮助过我,更多的是我坚信他们有自己的梦。他们能开心一些地去公司上班,每天生活能轻松一些,身体能健康一点,那就是我最高兴的事情了。我跟他们中的大部分人都非亲非故,也许他们离开了之后,我惟一能记住的就是他们的名字和手机中的联系方式(除了我那该死的舍友),工作之余偶尔的闲聊中会谈起他们,在这个偌大的城市中,偶遇的机会并不多。我惟一能做的就是祝福所有已经离开的和即将离开同事们,希望你们每天都能开开心心上班来,健健康康回家去,加班不常有,而加薪常有。