本文转载自:http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/workflow.html

开发流程

JNI 全称是 Java Native Interface(Java 本地接口)单词首字母的缩写,本地接口就是指用 C 和 C++ 开发的接口。由于 JNI 是 JVM 规范中的一部份,因此可以将我们写的 JNI 程序在任何实现了 JNI 规范的 Java 虚拟机中运行。同时,这个特性使我们可以复用以前用 C/C++ 写的大量代码。

开发 JNI 程序会受到系统环境的限制,因为用 C/C++ 语言写出来的代码或模块,编译过程当中要依赖当前操作系统环境所提供的一些库函数,并和本地库链接在一起。而且编译后生成的二进制代码只能在本地操作系统环境下运行,因为不同的操作系统环境,有自己的本地库和 CPU 指令集,而且各个平台对标准 C/C++ 的规范和标准库函数实现方式也有所区别。这就造成使用了 JNI 接口的 JAVA 程序,不再像以前那样自由的跨平台。如果要实现跨平台,就必须将本地代码在不同的操作系统平台下编译出相应的动态库。

JNI 开发流程主要分为以下 6 步:

  • 编写声明了 native 方法的 Java 类
  • 将 Java 源代码编译成 class 字节码文件
  • 用 javah -jni 命令生成.h头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)
  • 用本地代码实现.h头文件中的函数
  • 将本地代码编译成动态库(Windows:\*.dll,linux/unix:\*.so,mac os x:\*.jnilib)
  • 拷贝动态库至 java.library.path 本地库搜索目录下,并运行 Java 程序

通过上面的介绍,相信大家对 JNI 及开发流程有了一个整体的认识,下面通过一个 HelloWorld 的示例,再深入了解 JNI 开发的各个环节及注意事项。

HelloWorld

注意:这个案例用命令行的方式介绍开发流程,这样大家对 JNI 开发流程的印象会更加深刻,后面的案例都采用eclipse+cdt 来开发。

第一步,新建一个 HelloWorld.java 源文件


public class HelloWorld { 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.getProperty("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开发流程-JNI/NDK【转】的更多相关文章

  1. JNI/NDK开发指南(一)—— JNI开发流程及HelloWorld

    转载请注明出处:http://blog.csdn.net/xyang81/article/details/41777471 JNI全称是Java Native Interface(Java本地接口)单 ...

  2. JNI开发流程

    交叉编译 在一个平台上去编译另一个平台上可以执行的本地代码 cpu平台 arm x86 mips 操作系统平台 windows linux mac os 原理 模拟不同平台的特性去编译代码 jni开发 ...

  3. NDK学习笔记-JNI开发流程

    JNI(Java Native Interface)Java本地化接口,Java调用C/C++,C/C++调用Java的一套API接口 实现步骤 在Java源文件中编写native方法 public ...

  4. 编程基础知识——Java JNI开发流程(2)

    android中使用jni调用本地C++库 android平台上的本地库文件后缀 .so.类似windows上的dll文件. 要在android上使用jni.首先须要下载android ndk. 操作 ...

  5. Android的JNI开发

    变量的定义 int i; typedef int x;//定义一个int x数据类型 x a=10; printf("size=%d",sizeof(i));//获取int类型长度 ...

  6. JNI开发(2)——开发实战

    JNI开发(1)--概述.环境搭建.必要知识点 JNI开发(2)--开发实战 本篇是重头戏:JNI实战开发.假设你对于 JNI.NDK 还没概念的话 那么观看本篇 也是没有太大难度的 ,哈哈哈哈! ! ...

  7. android下JNI开发

    android下JNI开发 what 什么是JNI JNI java native interface native本地 java本地接口 通过JNI可以实现java和本地代码之间相互调用 jni可以 ...

  8. Android游戏开发实践(1)之NDK与JNI开发02

    Android游戏开发实践(1)之NDK与JNI开发02 承接上篇Android游戏开发实践(1)之NDK与JNI开发01分享完JNI的基础和简要开发流程之后,再来分享下在Android环境下的JNI ...

  9. Android jni简便开发流程

    <Android jni helloworld>中介绍了开发jni helloworld的步骤,本文将介绍jni简便开发流程 ① 写java代码 native 声明本地方法 ② 添加本地支 ...

随机推荐

  1. grok

    http://udn.yyuap.com/doc/logstash-best-practice-cn/filter/grok.html

  2. AD834+表面声波滤波器调试小结

    AD834:带宽较宽,300M无压力,但是输出幅度小,当输入2VPPX2VPP的时候最大输出400mVPP.一个特别要注意的地方是输入走线尽量短,敷铜间距至少满足3倍线宽,不然隔直之后还会耦合信号进去 ...

  3. LeetCode Basic Calculator II

    原题链接在这里:https://leetcode.com/problems/basic-calculator-ii/ Implement a basic calculator to evaluate ...

  4. 配置文件后面的rc的由来

    配置文件后面的rc的由来 配置文件比较正规的叫法是:运行控制文件  run control Linux就这个范儿 4.5.3 配置文件 配置文件比较文绉绉的称呼是“运行控制文件”,存放与具体程序相关的 ...

  5. Android 自定义TimePickerDialog

    学习Android半年了,一直想写点东西,今天开始写第一篇,目前在弄的一个小项目,是关于课程表智能闹钟,今天做这个TimePickerDialog查了很多资料,做成了下面这个效果, 这里timepic ...

  6. javascript:void(0)和javascript:;的用法

    一.JavaScript:void(0) 我们经常会使用到 javascript:void(0) 这样的代码,那么在 JavaScript 中 javascript:void(0) 代表的是什么意思呢 ...

  7. 在Matlab中编译c/c++代码需要安装mex

    >> mex -setup Welcome to mex -setup. This utility will help you set up a default compiler. For ...

  8. XP下安装MAC OS虚拟系统

    参考baidu经验: http://jingyan.baidu.com/article/e5c39bf5876c8b39d760331a.html 工具: 1.虚拟机软件:vmware worksta ...

  9. 安装GRID时跑root.sh脚本报错(ORA-27091: unable to queue I/O)

    在安装GRID过程中,运行root.sh脚本时报如下信息: Adding Clusterware entries to upstart CRS-2672: Attempting to start 'o ...

  10. Jquery系列教程

    最近想重新温习下Jquery,发现园子里有个很棒的系列教材,所以Mark下来. 转载自:http://www.cnblogs.com/zhangziqiu/tag/jQuery%E6%95%99%E7 ...