Java通过JNI调用C之HelloWorld入门
扫描二维码
随时随地手机看文章
首先我实在 ubantu12.04 下进行操作的,还要有 eclipse ~~
1.JNI的工作原理
JNI : Java Native Interface 即JAVA本地调用
(1)java的本质
想搞明白jni的本质,还要从java的本质说起.从本质上来说,java这门语言就是一门脚本语言(这是偶的个人理解,希望java大侠们不要用板砖拍我),它的运行完全依赖于脚本引擎对java的代码进行解释和执行(当然了,现代的java已经先进许多,可以从源代码编译成.class之类的中间格式的二进制文件,这种处理会大大地加快java脚本的运行速度,但是基本的执行方式仍然不变,由脚本引擎(我们称之为JVM)来执行,与python、perl之类的纯脚本相比,它只是把脚本变成了二进制格式而已.另外就是java本身对面向对象的概念支持得很好,拥有完善的功能库可供调用,把这个脚本引擎移植到所有平台上,那么这个脚本自然就实现所谓的“跨平台”了).绝大多数的脚本引擎都支持一个很显著的特性,就是可以通过c/c++编写模块,在脚本中调用这些模块,以此来类比java,也是一样的,java一定要提供一种在脚本中调用c/c++编写的模块的机制,才能称得上是一个相对完善的脚本引擎.
(2)android中的java
android平台从本质上是 由arm-linux操作系统 和一个叫做dalvik的java虚拟机组成的.所有在android模拟器上面看到的那些华丽的界面,都是用java语言编写的(参见android平台源代码的frameworks/base目录).目前看来dalvik只是提供了一个标准的支持jni调用的java虚拟机环境.android平台中所有的硬件相关的操作均是采用jni技术进行了封装,由java去调用jni模块,而jni模块使用c/c++调用android本身的arm-linux底层驱动.
例如,frameworks/base/libs/ui目录下面有一个叫做“EGLDisplaySurface.cpp”的文件,里面的:
status_t EGLDisplaySurface::mapFrameBuffer()函数中,就有直接对android的arm-linux中的framebuffer的初始化代码.
这也更加印证了,android其实是依靠java+jni建立起来的王国.hoho,如此一来,就凸显出jni在Android开发中的重要性(当然,一些简单的小程序是完全可以只用java就搞定的).
“jni”的子目录,这个目录将用来存放.c的文件.
2.使用JNI两个原因
1、运行JAVA程序的虚拟机是用Native语言编写的,而虚拟机运行在具体的平台上,所以虚拟机本身无法做到平台无关,而利用JNI技术即可对JAVA层屏蔽不同操作系统平台之间的差异,如file,socket等
2、在JAVA语言诞生前,很多程序使用Native语言编写,JAVA直接利用JNI使用,避免造重复轮子的坏名声。而且JNI的运行效率和速度会更高
3.JNI 之 HelloWorld
在Ubantu 中打开 eclipse ,写 java 代码:
HelloWorld.java
public class HelloWorld { static{ System.loadLibrary("hello"); } public native void DisplayHello(); public static void main(String[] args) { // TODO Auto-generated method stub new HelloWorld().DisplayHello(); //System.out.println( System.getProperty("java.library.path")); } }
进入src目录下,编译该JAVA类,(我是用默认包,不用写包名,有创建包的同鞋们要自己加包名)
命令:javac HelloWorld.java
在该 HelloWorld.java 所在目录下生成 HelloWorld.class
然后使用javah生成头文件,
命令:javah -jni HelloWorld
在当前目录下生成 HelloWorld.h 头文件,此文件供C、C++程序来引用并实现其中的函数
HelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include "jni.h" /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: DisplayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_DisplayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
注:1)、此头文件是不需要用户编译的,直接供其它C、C++程序引用。
2)、此头文件中的JNIEXPORT void JNICALL Java_HelloWorld_DisplayHello(JNIEnv *, jobject);方法,是将来与动态链接库交互的接口,并需要名字保持一致。
创建HelloWorld.cpp 实现头文件中的方法:
#include "jni.h" #include "HelloWorld.h" #includeJNIEXPORT void JNICALL Java_HelloWorld_DisplayHello (JNIEnv *env, jobject obj) { printf("From HelloWorld.cpp :"); printf("Hello world ! n"); return; }
此C++文件实现了上述头文件中的函数,注意方法函数名要保持一致。
编译生成动态库libHello.so
编译c程序:
gcc -shared -fpic XXX.c -o XXX.so
编译c++程序:
g++ -shared -fpic XXX.cpp -o XXX.so
我是用下面这种方法:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -c Hello.cpp
生成Hello.o
g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o
生成libhello.so.1.0
接下来将生成的共享库拷贝为标准文件名
cp libhello.so.1.0 libhello.so
或者使用:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o libLexical.so Lexical.cpp
编译好 .so接下来的任务是要让 java library path 能找到 .so
第一种方法:
现在HelloWorld.java 用 System.out.println( System.getProperty("java.library.path")); 打印出 java library 的路径, 然后把 libhello.so 复制到 java library path 的文件夹下面,如果没有权限的话用 sudo mv ./x ...(我是用这种方法)
第二种方法:
设置当前的 .so 的路径为 java library path 可以访问的路径
export LD_LIBRARY_PATH=...../(路径):$LD_LIBRARY_PATH
设置好路径点运行成功
From HelloWorld.cpp :Hello world !
附:gcc 参数解释(转载):
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真 正代码段共享的目的。
l -L.:表示要连接的库在当前目录中
l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
注意
1. 在linux下,动态链接库的名字必须是 lib****.so,必须以lib开头
2. 加载.so 文件时 ,我的c同事给我的.so 文件名为libswdes.so 我在java类里面调用时 需要这样写System.loadLibrary("swdes"); 不能带前面的lib和后缀名.so!
3. 调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。