JNI与NDK入门之一

whitewolf 发布于4天前 阅读92次
0 条评论

Android 系统架构中包含了 Applications (应用程序层)、Application framework(应用程序框架层)、Libraries + Android runtime(系统运行库层)以及 Linux Kernel(Linux核心层)。从编程语言的角度看,每层的功能模块都是使用相应的语言编写的,在此过程中,C/C++ 与 Java相互通信时就需要一个媒介来联系起来,JNI(Java Native Interface) 就充当了这一角色,它允许 Java 代码和 基于 C/C++ 编写的应用程序、模块、库进行交互操作。

JNI与NDK入门之一

使用情形

通常在下列几种情形下使用 JNI:

  • 追求运行速度

    不管是 Dalvik 还是 Art 虚拟机上,Java 代码的运行速度在一些情况下还是无法媲美使用 C/C++ 来开发的应用程序,特别是在开发图形处理或信号处理这类对 CPU 处理速度有较高要求的程序。我们可以使用 C/C++ 这类本地语言开发,再在 Java 中 借助JNI 将 Java 程序与 C/C++ 模块连接在一起,从而开发出一个执行效率更高的程序。

  • 复用 C/C++代码

    由于历史积累的问题,我们可能需要重新使用一些已经编写好且通过测试的 C/C++代码,减少了重复工作又确保了程序的安全性与健壮性。

  • 控制硬件

    硬件控制代码或者设备驱动程序通常使用 C 语言编写,借助 JNI 将设备驱动程序映射为 Java API,就可以在 Java 层实现对硬件的控制。

基本原理

在 Java 中调用 C 库函数,流程基本分为以下六个:

  1. 编写 Java 代码
  2. 编译 Java 代码
  3. 生成 C 语言头文件
  4. 编写 C 代码
  5. 生成 C 共享库
  6. 运行 Java 程序

下面我们来通过编写一个简单的 Java 程序来演示如何通过 JNI 来调用 C 函数。被调用的 C 函数是一个向控制台输出字符串的简单函数。

编写 Java 代码

首先编写 Java 端的调用代码,其中声明本地方法,而方法的具体实现则通过之后的 C/C++来编写。

public class HelloJNI{
	// 使用 native 关键字声明本地方法,该方法与用 C/C++ 编写的 JNI
	// 函数相对应, Java 代码中只声明没有具体实现
	native void printHello();
	native void printStr(String content);
	
	// 使用静态块加载具体实现本地方法的 C 运行库
	static {
		System.loadLibrary("hellojni");
	}
	
	public static void main(String args[]){
		HelloJNI jni = new HelloJNI();
		
		// 调用本地方法(实际调用的是使用 C 语言编写的 JNI 本地函数)
		jni.printHello();
		jni.printStr("The content from Java to C");
	}
	
}

编译 Java 代码

使用 Java 编译器 (javac)编译 Java 源代码:

javac HelloJNI.java

此时直接运行 HelloJNI 会抛出

java.lang.UnsatisfiedLinkError 的异常。

JNI与NDK入门之一

生成 C 语言头文件

下面我们生成数原型将运行库中的 C 函数与Java 代码中的本地方法映射在一起。

函数原型存在于 C/C++ 的头文件中。我们使用 javah 来生成头文件:

javah <包含以 native 关键字声明的方法的 Java 类名称>

JNI与NDK入门之一

运行 javah 命令后,会在当前目录下生成与 Java 类名(即 javah 命令的参数)相同名称的 C 语言头文件。在这个 C 头文件中,定义了与 Java 本地方法相链接的 C 函数原型。

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

#ifndef_Included_HelloJNI
#define_Included_HelloJNI
#ifdef__cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    printHello
 * Signature: ()V
 */
JNIEXPORT voidJNICALLJava_HelloJNI_printHello
  (JNIEnv *, jobject);

/*
 * Class:     HelloJNI
 * Method:    printStr
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT voidJNICALLJava_HelloJNI_printStr
  (JNIEnv *, jobject, jstring);

#ifdef__cplusplus
}
#endif
#endif

上述文件内容中, JNIEXPORT 、 JNICALL 都是 JNI 关键字,表示此函数要被 JNI 调用。反过来说, JNI 如果要正常调用函数,那么函数原型中必须要有这两个关键字。其实,这两个够关键字都是宏定义,具体请查看 JDK/include 下的 jni_md.h 文件。

同时可以看到生成的函数原型名称遵循「Java 类名 本地方法名」的命名规则,即可推断出 JNI 本地函数与 Java 类的哪个本地方法相对应。

现在查看函数原型中的参数,带有两个默认参数,第一个、第二个分别为 JNIEnv * 、 jobject ,前者是 JNI 接口的指针,用来调用 JNI 表中的各类 JNI 函数。后者是 JNI 提供的 Java 本地类型,保存着调用本地方法的对象的一个引用,用来在 C 代码中访问 Java 对象。

编写 C/C++ 代码

在 C 函数原型生成后,我们编写 hellojni.c 文件来具体实现 JNI 本地函数。首先把定义在 HelloJNI.h 头文件中的函数原型复制到 hellojni.c 文件中。

生成 C 共享库

#include"HelloJNI.h"
#include<stdio.h>

// 从头文件中复制后,记得添加参数名
JNIEXPORT voidJNICALLJava_HelloJNI_printHello
  (JNIEnv *env, jobject obj) {
  	printf("Hello World!\n");
  }


JNIEXPORT voidJNICALLJava_HelloJNI_printStr
  (JNIEnv *env, jobject obj, jstringstring) {
  	// C 语言中字符串占用8位, Java 中字符串占用16位,因此
  	// 需要将 jstring 类型的字符串转换成 C 语言字符串
  	const char *str = (*env)->GetStringUTFChars(env, string, 0);
  	printf("%s!\n", str);
  }

在命令行中,输入如下编译命令:

# 本机是 mac 环境,生成的是 .jnilib 文件,linux 改成 .so,windows 下则是 .dll。
# 至于为什么 unix/linux 下的动态链接库命名规则需要加上 .lib 的原因请参考 http://www.swig.org/Doc1.3/Java.html#dynamic_linking_problems
gcc hellojni.c -I /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/include/darwin -shared -o libhellojni.jnilib

注意这里几个 gcc 的参数, -shared 说明要生成动态库,对于两个 -I 的选项,前者指定了头文件 <jni.h> 的存放路径,后者指定了头文件 <jni_md.h> 的存放路径,请根据你的环境配置更改。

JNI与NDK入门之一

运行 Java 程序

到这里所有步骤准备完成,执行 java 命令,运行 HelloJNI 类后,查看运行结果。

请注意 java.library.path 用来指定当前动态链接库地址。

java -Djava.library.path=. HelloJNI

JNI与NDK入门之一

小结

最后,我们总结一下 Java 本地方法如何通过 JNI 链接至 C 函数的几个步骤:

JNI与NDK入门之一

  1. 在 Java 类中声明本地方法
  2. 使用 javah 命令,生成包含 JNI 本地函数原型的头文件
  3. 实现 JNI 本地函数
  4. 生成 C 共享库
  5. 通过 JNI 调用 JNI 本地函数

查看原文: JNI与NDK入门之一

  • bigbear
  • bluemeercat
  • purplebird
  • smallbird
  • blueswan
  • lazypeacock
  • silverdog
  • whitepanda
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。