本文转载自: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. 深入理解BootStrap之栅格系统(布局)

    1.栅格系统(布局) Bootstrap内置了一套响应式.移动设备优先的流式栅格系统,随着屏幕设备或视口(viewport)尺寸的增加,系统会自动分为最多12列. 我在这里是把Bootstrap中的栅 ...

  2. channelartlist添加栏目链接

    {dede:channelartlist} <a href='{dede:field name='typeurl'/}'></a> {/dede:channelartlist}

  3. 输出Java的GC信息

    -verbose:gc -XX:+printGC 可以打印GC的简要信息 [GC 4790K->374K(15872K), 0.0001606 secs] [GC 4790K->374K( ...

  4. POJ1326问题描述

    Description Mileage program of ACM (Airline of Charming Merlion) is really nice for the travelers fl ...

  5. 56个睿智帅气貌美的CTO大牛陪你叨逼叨

    技术领域门槛高,苦读十年有时不如大牛一指点 可CTO级别的大牛何处找? 别急,别急 他们1月12日都去参加APICloud新品发布会了! (没图说啥子) 各路大牛们云集,不仅是为APICloud新品发 ...

  6. MVC 读书笔记

    一.路由 1.HttpApplication中的ASP.NET MVC .Net 3.5 引入了System.Web.Routing程序集,通过Url Routing的机制,可以实现将一个虚拟路径的请 ...

  7. Android百度地图开发(一)之初体验

    转载请注明出处:http://blog.csdn.net/crazy1235/article/details/42614603 做关于位置或者定位的app的时候免不了使用地图功能,本人最近由于项目的需 ...

  8. 9G10内核时钟tick实现

    9G10中PIT(Periodic Interval Timer)提供OS调度中断,它提供了最高精度和最有效的管理(即使系统长时间响应).一. 硬件PIT目标是提供OS的周期中断.PIT提供一个可编程 ...

  9. eclipse中运行Selenium遇到的问题

    1.   java.lang.NoClassDefFoundError: 解决方法:eclipse的java工程中导入selenium-java-2.44.0\selenium-2.44.0\libs ...

  10. ThinkPHP 自动验证与自动填充无效可能的原因(转)

    自动验证与自动填充是在使用ThinkPHP时经常用到的功能,但偶尔会遇到自动验证与自动填充无效的情况,本文就ThinkPHP 自动验证与自动填充无效可能的原因做一些分析. create() Think ...