JNI编程笔记一

JNI即Java Native Interface,它允许Java与其它计算机语言的代码进行交互。在这里,我们可以简单的理解为JNI就是为了实现和C或者C++交互而存在的。

一般我们称Java中与C或者C++交互的方法为本地方法,每一个本地方法前面都有一个native的修饰符,并且方法不需要在Java层实现,只需要进行方法声明即可。本地方法与抽象方法很相似,不同的地方在于将abstract替换为了native关键字。

public native long getLongData(long l);
public native String getStrData(String src);

在进行JNI编程之前,首先需要掌握基本的Java语法,其次要对C或者C++语法有一定的熟悉程度。我们可以没有使用C或者C++编写复杂业务场景的经验,但是要求至少可以看懂C或者C++代码的执行流程,并且可以编写JNI层代码,实现Java与C或者C++双向交互。

如果对C或者C++语法不熟悉,可以参看如下几篇博文。这几篇文章对C或者C++语法进行了简单的梳理,一定程度上可以满足对JNI编程的要求。

在C或者C++中与Java交互的代码称为本地代码,即本地代码是用C或者C++编写的,而本地方法是Java中的native方法。

Java利用JNI接口与本地代码进行交互,并不是说直接与C或者C++本地源代码交互,而是需要将C或者C++编写的本地代码打包为特定平台上的可执行文件。例如,Window上需要将本地代码打包为.dll动态链接库,而不是exe可执行文件,在Linux上需要打包为so库。

当一个应用需要使用本地方法时,本地方法一定要简单,而且要尽可能将本地方法封装到一个类中,以Window系统为例,这样包含本地方法的Java类,只需要调用一个.dll库文件即可。

JNI编程书写步骤

一般当涉及到JNI编程时,大致采用以下流程:

  1. 编写带有native声明的方法的Java类;
  2. 使用javac命令编译所编写的Java类;
  3. 然后使用javah + Java类名生成扩展名为h的头文件;
  4. 使用C/C++实现本地方法;
  5. 将C/C++编写的文件生成动态连接库;
  6. 使用Java的System.loadLibrary()方法加载库。

接下来我们在Window上面使用上述步骤实现一个简单示例,该示例基于C语言实现的一个的简单的JNI调用。

如下示例更多的是演示一下JNI的整体操作调用流程,有关步骤中的知识点后续会分点更详细的逐一介绍。

编写native声明的方法的Java类

public class MainTest {

	public native static String getStringFromC();
	
	static{
		System.loadLibrary("JNIHello");
	}
	public static void main(String[] args) {
		System.out.println(getStringFromC());
	}
}

在示例中我们声明了一个native方法getStringFromC,方法返回值是一个String类型的数据类型,方法的实现位于本地代码层,在Java层该方法不许要被实现。

通过静态代码块加载了动态库,loadLibrary方法中的入参是动态链接库的名称,注意是不需要携带扩展名的。有挂loadLibrary()方法的使用在后面介绍。

使用javac命令编译所编写的Java类

这一步不需要过多介绍,直接使用命令生成一个class文件即可。当然了多数时候我们使用的可能是IDE工具,比如,如果使用的是eclipse,这时候只需要找到项目的根目录,然后在bin子目录下就可以找到想要的Java类生成的class字节码文件,直接拿来使用即可,就不需要使用javac进行编译了。

使用javah + Java类名生成扩展名为h的头文件

初次接触JNI编程时,我们是不是有这样一个疑问,在C或者C++中JNI使用的头文件好复杂。其实这样头文件不需要开发人员自己手动编写,只需要使用命令直接就可以生成了。

javah命令使用方式如下:

用法:
  javah [options] <classes>
其中, [options] 包括:
  -o <file>                输出文件 (只能使用 -d 或 -o 之一)
  -d <dir>                 输出目录
  -v  -verbose             启用详细输出
  -h  --help  -?           输出此消息
  -version                 输出版本信息
  -jni                     生成 JNI 样式的标头文件 (默认值)
  -force                   始终写入输出文件
  -classpath <path>        从中加载类的路径
  -cp <path>               从中加载类的路径
  -bootclasspath <path>    从中加载引导类的路径
 是使用其全限定名称指定的
(例如, java.lang.Object)。

这里我们首先将目录切换到项目的src目录,然后使用javah -classpath . com.sunny.demo.MainTest命令,生成的代码格式如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_sunny_demo_MainTest */

#ifndef _Included_com_sunny_demo_MainTest
#define _Included_com_sunny_demo_MainTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_sunny_demo_MainTest
 * Method:    getStringFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_sunny_demo_MainTest_getStringFromC
  (JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif

使用C语言实现本地方法

#include "com_sunny_demo_MainTest.h"

JNIEXPORT jstring JNICALL Java_com_sunny_demo_MainTest_getStringFromC
(JNIEnv *env, jclass jcls){
	return (*env)->NewStringUTF(env,"I'm from C");
}

将C编写的文件生成动态连接库

在本示例中,我们编写C代码使用的IDE是Visual Studio 2013,由于操作系统是64位的,所以这里我们导出的.dll库文件也是64位的。首先在如下图所示,点击配置管理器

然后在弹出框的活动解决方案项目上下文-平台都选择x64。

接下来在项目解决方案上右键菜单中选择属性

在弹出框的配置类型的下拉框中选择动态库(..dll)

最后点击VS2013工具栏生成-重新生成解决方案即可生成Window动态链接库文件。

使用Java的System.loadLibrary()方法加载库

在上一步生成的.dll动态链接库文件就位于项目根目录的x64文件夹下,直接copy过来放在Eclipse项目的根目录下即可。

System类有两种方式用于加载动态库文件,一种是使用示例中的loadLibrary()方法,另外一种方式是使用load()方法,这两种方式的传参类型时有很大差异的。

第一种方式loadLibrary(String libname ),该方法入参是一个String类型的字符串,只需要传入动态链接库.dll的文件名即可。但是要求.dll文件必须位于eclipse项目的根目录下,否则就需要将动态库文件位置配置成一个环节变量。

第二种方式load(String filename),该方法入参也是一个String类型的字符串,但是参数要求必须是完整路径名。

最后执行Java代码,输出结果就是"I'm from C",上面就是一个JNI调用的helloworld示例。

C++实现本地方法

在上述示例中,我们实现本地方法是使用的C语言,如果使用C++,在代码的调用方式上面会有差异。C++实现相对简洁些,因为C++支持this指针,所以在JNIEnv的定义以及实现上面跟C语言不同,虽然最后C++的实现也是调用的C语言中的实现方式。

JNIEXPORT jstring JNICALL Java_com_sunny_demo_MainTest_getStringFromC
(JNIEnv *env, jclass jcls){
	return (*env)->NewStringUTF(env,"I'm from C");
}

//C++实现
JNIEXPORT jstring JNICALL Java_com_sunny_demo_MainTest_getStringFromCPlusPlus
(JNIEnv * env, jobject jobj){
	return env->NewStringUTF("I'm from C ++");
}

小结

本文重点介绍了JNI的概念,并通过一个简单的helloworld示例,演示了如何通过JNI实现Java和C/C++的通信。在本文示例中演示的只是一个简单的Java如何调动C/C++的方法,后续我们还会介绍如何在C/C++调用Java方法。

为什么本地代码在C或者C++中实现方式上有所不同,因为C++中JNIEnv就是一个结构体,而C语言中JNIEnv是一个结构体指针,更多详细的讨论我们在下一篇中结合jni.h的源代码再做深入分析。

对于JNI语法规则,比如方法名的定义规则,JNI支持的数据类型,如何返回一个基础数据类型,如何返回对象或者数组类型,在C/C++中获取Java中的字段,如何创建Java中类型的对象或者调用Java方法等等,我们在后续文章中再继续深入介绍。

评论

您确定要删除吗?删除之后不可恢复