JNI/NDK开发指南(一)—— JNI开发流程及HelloWorld
转载请注明出处:http://blog.csdn.net/xyang81/article/details/41777471
JNI全称是Java Native Interface(Java本地接口)单词首字母的缩写,本地接口就是指用C和C++开发的接口。由于JNI是JVM规范中的一部份,因此可以将我们写的JNI程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++写的大量代码。
开发JNI程序会受到系统环境的限制,因为用C/C++语言写出来的代码或模块,编译过程当中要依赖当前操作系统环境所提供的一些库函数,并和本地库链接在一起。而且编译后生成的二进制代码只能在本地操作系统环境下运行,因为不同的操作系统环境,有自己的本地库和CPU指令集,而且各个平台对标准C/C++的规范和标准库函数实现方式也有所区别。这就造成使用了JNI接口的JAVA程序,不再像以前那样自由的跨平台。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。
JNI开发流程主要分为以下6步:
1、编写声明了native方法的Java类
2、将Java源代码编译成class字节码文件
3、用javah -jni命令生成.h头文件(javah是jdk自带的一个命令,-jni参数表示将class中用native声明的函数生成jni规则的函数)
4、用本地代码实现.h头文件中的函数
5、将本地代码编译成动态库(windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
6、拷贝动态库至 java.library.path 本地库搜索目录下,并运行Java程序
通过上面的介绍,相信大家对JNI及开发流程有了一个整体的认识,下面通过一个HelloWorld的示例,再深入了解JNI开发的各个环节及注意事项。
PS:本人的开发环境为Mac os x 10.10.1 ,Eclipse 3.8(Juno),如果在其它操作系统下开发也是一样,只需将本地代码编译成当前操作系统所支持的动态库即可。
这个案例用命令行的方式介绍开发流程,这样大家对JNI开发流程的印象会更加深刻,后面的案例都采用eclipse+cdt来开发。
第一步、并新建一个HelloWorld.java源文件
package com.study.jnilearn; public class HelloWorld { public static native String sayHello(String name); // 1.声明这是一个native函数,由本地代码实现 public static void main(String[] args) {
String text = sayHello("yangxin"); // 3.调用本地函数
System.out.println(text);
} static {
System.loadLibrary("HelloWorld"); // 2.加载实现了native函数的动态库,只需要写动态库的名字
} }
第二步、用javac命令将.java源文件编译成.class字节码文件
注意:HelloWorld放在com.study.jnilearn包下面
javac src/com/study/jnilearn/HelloWorld.java -d ./bin
-d 表示将编译后的class文件放到指定的目录下,这里我把它放到和src同级的bin目录下
第三步、用javah -jni命令,根据class字节码文件生成.h头文件(-jni参数是可选的)
javah -jni -classpath ./bin -d ./jni com.study.jnilearn.HelloWorld
默认生成的.h头文件名为:com_study_jnilearn_HelloWorld.h(包名+类名.h),也可以通过-o参数指定生成头文件名称:
javah -jni -classpath ./bin -o HelloWorld.h com.study.jnilearn.HelloWorld
参数说明:
-classpath :类搜索路径,这里表示从当前的bin目录下查找
-d :将生成的头文件放到当前的jni目录下
-o : 指定生成的头文件名称,默认以类全路径名生成(包名+类名.h)
注意:-d和-o只能使用其中一个参数。
第四步、用本地代码实现.h头文件中的函数
com_study_jnilearn_HelloWorld.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_HelloWorld */ #ifndef _Included_com_study_jnilearn_HelloWorld
#define _Included_com_study_jnilearn_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_study_jnilearn_HelloWorld
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello
(JNIEnv *, jclass, jstring); #ifdef __cplusplus
}
#endif
#endif
HelloWorld.c:
// HelloWorld.c #include "com_study_jnilearn_HelloWorld.h" #ifdef __cplusplus
extern "C"
{
#endif /*
* Class: com_study_jnilearn_HelloWorld
* Method: sayHello
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(
JNIEnv *env, jclass cls, jstring j_str)
{
const char *c_str = NULL;
char buff[128] = { 0 };
c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
if (c_str == NULL)
{
printf("out of memory.\n");
return NULL;
}
(*env)->ReleaseStringUTFChars(env, j_str, c_str);
printf("Java Str:%s\n", c_str);
sprintf(buff, "hello %s", c_str);
return (*env)->NewStringUTF(env, buff);
} #ifdef __cplusplus
}
#endif
第五步、将C/C++代码编译成本地动态库文件
动态库文件名命名规则:lib+动态库文件名+后缀(操作系统不一样,后缀名也不一样)如:
Mac OS X : libHelloWorld.jnilib
Windows :HelloWorld.dll(不需要lib前缀)
Linux/Unix:libHelloWorld.so
1> Mac OS X
gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin
我的$JAVA_HOME目录在:/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home
参数选项说明:
-dynamiclib:表示编译成动态链接库
-o:指定动态链接库编译后生成的路径及文件名
-framework JavaVM -I:编译JNI需要用到JVM的头文件(jni.h),第一个目录是平台无关的,第二个目录是与操作系统平台相关的头文件
2> Windows(以Windows7下VS2012为例)
开始菜单-->所有程序-->Microsoft Visual Studio 2012-->打开VS2012 X64本机工具命令提示,用cl命令编译成dll动态库:
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloWorld.c -FeHelloWorld.dll
参数选项说明:
-I : 和mac os x一样,包含编译JNI必要的头文件
-LD:标识将指定的文件编译成动态链接库
-Fe:指定编译后生成的动态链接库的路径及文件名
3> Linux/Unix
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloWorld.c -o libHelloWorld.so
参数说明:
-I: 包含编译JNI必要的头文件
-fPIC: 编译成与位置无关的独立代码
-shared:编译成动态库
-o: 指定编译后动态库生成的路径和文件名
第六步、运行Java程序
Java在调用native(本地)方法之前,需要先加载动态库。如果在未加载动态之前就调用native方法,会抛出找不到动态链接库文件的异常。如下所示:
Exception in thread "main" java.lang.UnsatisfiedLinkError: com.study.jnilearn.HelloWorld.sayHello(Ljava/lang/String;)Ljava/lang/String;
at com.study.jnilearn.HelloWorld.sayHello(Native Method)
at com.study.jnilearn.HelloWorld.main(HelloWorld.java:9)
一般在类的静态(static)代码块中加载动态库最合适,因为在创建类的实例时,类会被ClassLoader先加载到虚拟机,随后立马调用类的static静态代码块。这时再去调用native方法就万无一失了。加载动态库的两种方式:
System.loadLibrary("HelloWorld");
System.load("/Users/yangxin/Desktop/libHelloWorld.jnilib");
方式1:只需要指定动态库的名字即可,不需要加lib前缀,也不要加.so、.dll和.jnilib后缀
方式2:指定动态库的绝对路径名,需要加上前缀和后缀
如果使用方式1,java会去java.library.path系统属性指定的目录下查找动态库文件,如果没有找到会抛出java.lang.UnsatisfiedLinkError异常。
Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld2 in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
at java.lang.Runtime.loadLibrary0(Runtime.java:845)
at java.lang.System.loadLibrary(System.java:1084)
at com.study.jnilearn.HelloWorld.<clinit>(HelloWorld.java:13)
大家从异常中可以看出来,他是在java.library.path中查找该名称对应的动态库,如果在mac下找libHelloWorld.jnilib文件,linux下找libHelloWorld.so文件,windows下找libHelloWorld.dll文件,可以通过调用System.getProperties("java.library.path")方法获取查找的目录列表,下面是我本机mac os x 系统下的查找目录:
String libraryDirs = System.getProperties("java.library.path");
System.out.println(libraryDirs);
// 输出结果如下:
/Users/yangxin/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
有两种方式可以让java从java.library.path找到动态链接库文件,聪明的你应该已经想到了。
方式1:将动态链接库拷贝到java.library.path目录下
方式2:给jvm添加“-Djava.library.path=动态链接库搜索目录”参数,指定系统属性java.library.path的值
java -Djava.library.path=/Users/yangxin/Desktop
Linux/Unix环境下可以通过设置LD_LIBRARY_PATH环境变量,指定库的搜索目录。
费了那么大劲,终于可以运行写好的Java程序了,结果如下:
yangxin-MacBook-Pro:JNILearn yangxin$ java -classpath ./bin com.study.jnilearn.HelloWorld
Java Str:yangxin
hello yangxin
如果没有将动态库拷贝到本地库搜索目录下,执行java命令,可通过添加系统属性java.library.path来指定动态库的目录,如下所示:
yangxin-MacBook-Pro:JNILearn yangxin$ java -Djava.library.path=/Users/yangxin/Desktop -classpath ./bin com.study.jnilearn.HelloWorld
Java Str:yangxin
hello yangxin
JNI/NDK开发指南(一)—— JNI开发流程及HelloWorld的更多相关文章
- Android jni/ndk编程一:jni初级认识与实战体验
Android平台很多地方都可以看到jni的身影,比如之前接触到一个投屏的项目,主要的代码是c/c++写的,然后通过Jni供Java层调用;另外,就拿Android系统中的Service来说,很多的S ...
- Android jni/ndk编程二:jni数据类型转换(primitive,String,array)
一.数据类型映射概述 从我们开始jni编程起,就不可能避开函数的参数与返回值的问题.java语言的数据类型和c/c++有很多不同的地方,所以我们必须考虑当在java层调用c/c++函数时,怎么正确的把 ...
- Android jni/ndk编程五:jni异常处理
在Java的编程中,我们经常会遇到各种的异常,也会处理各种的异常.处理异常在java中非常简单,我们通常会使用try-catch-finally来处理,也可以使用throw简单抛出一个异常.那么在jn ...
- Android jni/ndk编程四:jni引用类型
一.JNI引用类型 JNI支持三种类型的 opaque reference:local references, global references,和weak global references,下面 ...
- Jetty 开发指南:嵌入式开发示例
Jetty具有嵌入各种应用程序的丰富历史. 在本节中,我们将向您介绍我们的git存储库中的embedded-jetty-examples项目下的一些简单示例. 重要:生成此文档时,将直接从我们的git ...
- Jetty 开发指南:Jetty 内嵌开发
Jetty的口号是“不要在Jetty中部署你的应用程序,在你的应用程序中部署Jetty!” 这意味着,作为将应用程序捆绑为要部署在Jetty中的标准WAR的替代方案,Jetty旨在成为一个软件组件,可 ...
- Blazor 事件处理开发指南
翻译自 Waqas Anwar 2021年3月25日的文章 <A Developer's Guide To Blazor Event Handling> [1] 如果您正在开发交互式 We ...
- Blazor 路由及导航开发指南
翻译自 Waqas Anwar 2021年4月2日的文章 <A Developer's Guide To Blazor Routing and Navigation> [1] 检查传入的请 ...
- Blazor 模板化组件开发指南
翻译自 Waqas Anwar 2021年4月15日的文章 <A Developer's Guide To Blazor Templated Components> [1] 在我之前的一篇 ...
- Blazor 组件库开发指南
翻译自 Waqas Anwar 2021年5月21日的文章 <A Developer's Guide To Blazor Component Libraries> [1] Blazor 的 ...
随机推荐
- ant来历
看看<ant权威指南>或者ant的网站吧,里面介绍得非常详细ant的来历,目的.ant是tomcat的一个副产品,是在开发tomcat过程中,开发人员需要一个整合编译工具,因为在一个大项目 ...
- WPF的ListBox中的RadioButton不能单选问题
WPF不知道是微软故意弄的还是真的匆忙的推出的产品,在实际开发过程中有很多小问题解决很麻烦. 今天主要说一下ListBox中使用RadioButton的时候里面的RadioButton不能单选!居然成 ...
- 判断URL是否存在
判断URL是否存在 判断网址是否存在,可以使用下面方面处理.如果HttpStatusCode为OK,说明网址存在.
- Docker container network configuration
http://xmodulo.com/networking-between-docker-containers.html How to set up networking between Docker ...
- mac install php dev
环境开发配置 mysql 下载mysql压缩包.tar.gz 解压 mv mysql_5.7.10_osx... /usr/local/mysql sudo chown -R root:wheel / ...
- uboot之at91sam9g45移植
一.第一阶段,无修改 二.第二阶段 u-boot-1.3.4\lib_arm\board.c 1.增加头文件 2.增加版本号 3.start_armboot中初始化部分 板级初始化部分init_seq ...
- PHP计算2点经纬度之间的距离
hp] view plaincopy function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longit ...
- webrtc学习——RTCPeerConnection
The RTCPeerConnection interface represents a WebRTC connection and handles efficient streaming of da ...
- 谨以此错误警醒自己---java.lang.ClassNotFoundException:
今天在做一个小型的实例,用了面向接口编程,采用了工厂来获取数据源和实例,在从配置文件读取实例时,报了个错误如下图: 于是一个一个的类去找错误,最后也没找到,以为工厂代码出错了,看了好几次也没看出来错误 ...
- Android adapter适配器的使用
说起Adapter的使用,首先想到的就是listview或各种各样的Adapter.下面我们对常用的一些Adapter进行简单的使用讲解. 这是Adapter的关系图: 下面的所有例子均使用同一个布局 ...