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;
	}
	printf("Java Str:%s\n", c_str);
	sprintf(buff, "hello %s", c_str);
        (*env)->ReleaseStringUTFChars(env, j_str, 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
时间: 2024-10-29 18:46:38

JNI/NDK开发指南(一)—— JNI开发流程及HelloWorld的相关文章

jBPM-4.0中文开发指南-第6章 流程剖析

第 6 章 流程剖析 上面我们已经简要的接触了两个主要的流程结构: 活动,转移和活动组合. 这一章研究了流程定义结构的全部可能. 这儿基本有两个流程定义方式:基于图形和组合流程语言. 首先,流程支持这两种情况. 每个基于图形的执行和活动组合可以用来组合一些像UML超级状态的实现. 甚至,自动功能活动可以被实现, 所以它们可以使用转移和活动组合. 开发指南-第6章 流程剖析-jbpm开发入门指南"> 图 6.1. 逻辑流程结构的UML类图 下一步我们会显示一系列的实例图形结构, 这可以组成P

jBPM-4.0中文开发指南-第2章 流程虚拟机

第 2 章 流程虚拟机 为了通过插件方式容纳多种流程语言和活动,jBPM基于了流程虚拟机. 本质上,流程虚拟机是一个特定的可执行图形的框架. 一个流程定义表现为一个执行流, 它拥有可以表现为图形的一种结构. 流程虚拟机将流程定义从活动行为中切分了出来. 流程虚拟机从一个活动到下一个获得获取可执行的流程, 并将活动的行为委派给可插拔的Java类. 这里有一个API(ActivityBehaviour)用来作为 流程虚拟机和活动行为代码的接口.像jPDL这类的语言仅仅是 一系列活动行为的实现和解析器

二、Angular 2.0开发指南以及搭建开发环境

自第一章讲Angular2.0的简单开发入门,我们了解到ng2的开发实践是围绕着web-component来展开的,ng2中更加强化了Component的概念,弱化了指令的概念(实际上Component是复杂指令).同时我们学习到web-component中数据和视图是如何互相影响的,即Input和Output的概念,数据通过Input往view以及view里的子组件传递,view及其子组件通过Ouput来改变数据(VM通过数据绑定机制实现互通,View <- data-bindings ->

jBPM-4.0中文开发指南-第15章 流程语言

第 15 章 流程语言 TODO: XML解析器结构 TODO: 继承自ProcessDefinitionImpl, ExecutionImpl TODO: 重写默认的proceed() TODO: 活动类型实现 TODO: 持久化 TODO: 补偿:像bpel和bpnm这样的语言,定义为 一个普通的整合,满足流程结构中的可用性, 在pvm中(选择一个转移和 执行内嵌活动).

jBPM-4.0中文开发指南-目录

jBPM-4.0中文开发指南-第15章 流程语言 jBPM-4.0中文开发指南-第14章 持久化 jBPM-4.0中文开发指南-第13章 执行模式 jBPM-4.0中文开发指南-第12章 其他环境 jBPM-4.0中文开发指南-第11章 环境 jBPM-4.0中文开发指南-第10章 委派类 jBPM-4.0中文开发指南-第9章 历史 jBPM-4.0中文开发指南-第8章 软件日志 jBPM-4.0中文开发指南-第7章 高级图形执行 jBPM-4.0中文开发指南-第6章 流程剖析 jBPM-4.0

AngularJS 中的指令实践开发指南(一)_AngularJS

指令(Directives)是所有AngularJS应用最重要的部分.尽管AngularJS已经提供了非常丰富的指令,但还是经常需要创建应用特定的指令.这篇教程会为你讲述如何自定义指令,以及介绍如何在实际项目中使用.在这篇文章的最后(第二部分),我会指导你如何使用Angular指令来创建一个简单的记事本应用. 概述 一个指令用来引入新的HTML语法.指令是DOM元素上的标记,使元素拥有特定的行为.举例来说,静态的HTML不知道如何来创建和展现一个日期选择器控件.让HTML能识别这个语法,我们需要

AngularJS中的指令实践开发指南(二)_AngularJS

在AngularJS中的指令实践指南(一)中给大家介绍了,如何隔离一个指令的scope.第二部分将承接上一篇继续介绍.首先,我们会看到在使用隔离scope的情况下,如何从指令内部访问到父scope的属性.接着,我们会基于对 controller 函数和 transclusions 讨论如何为指令选择正确的scope.这篇文章的最后会以通过一个完整的记事本应用来实践指令的使用. 隔离scope和父scope之间的数据绑定 通常,隔离指令的scope会带来很多的便利,尤其是在你要操作多个scope模

《Access 2007开发指南(修订版)》一一1.8 Access应用程序的开发过程

1.8 Access应用程序的开发过程 Access 2007开发指南(修订版)许多开发人员认为,Access是一个快速的应用程序开发环境,因此在创建应用程序时,没有必要进行系统分析和系统设计.对于这一点,笔者并不赞同.如本章前面所述,Access应用程序看起来比较容易创建,但如果规划不当,也会带来灾难. 1.8.1 任务分析 Access应用程序开发过程的第一步就是进行任务分析,也就是考虑在用户工作的时候会发生的每一个过程,这是一件麻烦而必要的工作.当笔者第一次受聘于一家大型公司在大型机上作编

JNI/NDK开发指南(八)——调用构造方法和父类实例方法

转载请注明出处:http://blog.csdn.net/xyang81/article/details/44002089 在第6章我们学习到了在Native层如何调用Java静态方法和实例方法,其中调用实例方法的示例代码中也提到了调用构造函数来实始化一个对象,但没有详细介绍,一带而过了.还没有阅读过的同学请移步<JNI/NDK开发指南(六)--C/C++访问Java实例方法和静态方法>阅读.这章详细来介绍下初始一个对象的两种方式,以及如何调用子类对象重写的父类实例方法. 我们先回过一下,在J

JNI/NDK开发指南(五)——访问数组(基本类型数组与对象数组)

          转载请注明出处:http://blog.csdn.net/xyang81/article/details/42346165          JNI中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是JNI的基本数据类型,可以直接访问.而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作一样,不能直接访问Java传递给JNI层的数组,必须选择合适的JNI函数来访问和设置Java层的数组对象.阅读此文假设你已经了解了JNI与J