首页 > Coding > 多语言编程之 C and Java:JNI 30 分钟入门

多语言编程之 C and Java:JNI 30 分钟入门

2016年6月29日 发表评论 阅读评论

1 引言

1.1 简介

在实际项目中,很多时候是以多种语言混合开发的。常用的模式是使用 C/C++ 做底层支撑,然后上层辅之以高级语言。 JNI可以使 native 代码与 Java 对象交互,而不会像 Java 代码中的功能那样受到诸多限制, 往往充作 Java 与 C/C++/Assembly 语言的粘合剂。

JNI 明确分开了 Java 代码与 native 代码的执行,为两者的通信定义了一套清晰的API.避免 native 代码对 JVM 的直接内存引用。

2 JNI 入门

2.1 JNI 调用 C

2.1.1  java 中的 native 方法声明

HelloJNI.java

当HelloJNI类加载的时候,静态初始化调用 System.loadLibrary() 来加载本地库 hello.dll(或libhello.so)。如果不能加载 hello 库,会抛出 UnsatisfiedLinkError 异常。
然后我们使用 native 关键字声明了 native 方法 sayHello() ,这表明该方法是使用其它语言实现的。
最后 main() 方法实例化了 HelloJNI 并调用了 sayHello().
然后我们编译这个类:

2.1.2  生成 C/C++ 头文件

使用 javah 创建头文件:

会得到如下 .h 文件 HelloJNI.h

这份自动生成的头文件里声明一个 C 函数 Java_HelloJNI_sayHello:

这个方法名是由javah转换来的,转换规则为:

包名中的 点 会被转换为 下划线
函数中带有两个参数(虽然我们在本例中并没有使用这两个参数)

  • JNIEnv*: 指向 JNI environment ,提供对 JNI 方法的访问
  • jobject: 指向 this Java 对象

2.1.3 C 代码的实现

HelloJNI.c

这个实现将在控制台打印万能的 "Hello World!"
文件中包含了 jni.h 头文件。这个头文件由 java 提供,位于 %JAVA_HOME%/include 路径下(Windows下还需要引用 %JAVA_HOME%/include/win32).

2.1.4 编译 C 库

接下来,编译这个 c 文件:

2.1.5 检查成果

3 在 Java 和 Native 间传递参数

3.1 传递原始类型参数

Java 原始参数可以在 Java 和 Native 单直接传递,只不过这些类型在JNI中的名称不一样。JNI 定义了 jxxx 类型来对应相应的原始类型。如 jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean 分别对应 Java 中的 int, byte, short, long, float, double, char, boolean. 下例中,将演示如何传递 Java 原始参数。这一次,我们编辑的顺序将从 native 到 Java。

3.1.1 编辑 C 头文件

打开 HelloJNI.h,在文件中添加函数的声明:

这里用到了 jint 。我们可以在 jni.h 和 jni_md.h 中找到各原始类型的定义:

3.1.2 实现函数

在 HelloJNI.c 中实现上一步声明的函数:

3.1.3 编辑 Java 文件

在Java中声明 native 方法并调用:

3.1.4 编译并执行

像上一章那样,编译 HelloJNI.c 和 HelloJNI.java 并执行 Java 程序,结果如下:

3.2 传递 String 参数

3.2.1 示例

Java 代码

C 函数声明

JNI 使用 jstring 来表示 Java String. 最后一个参数 jstring 是 Java 传递到 native 的 String 参数

C 函数实现

一般使用 GetStringUTFChars() 将接收到的 jstring 转换为 C-string ; 使用 NewStringUTF 将 C-string 转换为 jstring 返回给 Java

Java String 和 C-string 不同,是一个复杂的引用类型,在传递过程中需要将字符串在这两种类型间转换。JNIEnv*(JNI Enviroment) 提供了转换的方法:

GetStringUTFChars() 函数将从第二个参数 jstring 参数中返回 C-string(char*)。如果有内存分配错误,则返回NULL. 函数的第三个参数 isCopy 可以设置为 JNI_TRUE 或 JNI_FALSE . 如果是 JNI_TRUE, 则返回Java String 的副本的 C-string. 如果是 JNI_FALSE,则直接返回 Java String 参数的 C-string(char*) 指针,此时不要改变返回值的内容。
ReleaseStringUTFChars() 函数将用来释放 GetStringUTFChars() 的返回值。只要 GetStringUTFChars() 的返回值不再被使用时,它应该被立即释放以便垃圾回收器介入处理。
NewStringUTF() 用来从 C-string 创建 jstring.

3.2.2 JNI String 操作函数

更多的 JNI 函数可参考这里

3.3 传递原始类型数组

3.3.1 示例

Java 代码

JNI C 函数声明

JNI C 函数实现

3.3.2 JNI 原始类型数组操作函数

JNI 原始数据类型的数组(jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray and jbooleanArray)操作函数如下:

  • Get<PrimitiveType>ArrayElements() 从 Java 数组创建 C 数组
  • Get|Set<PrimitiveType>ArrayRegion()将从 Java 数组和 C 数组中来回拷贝数据
  • New<PrimitiveType>Array() 分配新的 Java 数组内存
  • GetPrimitiveArrayCritical() 得到原始数据类型内容的指针,可能使得垃圾回收器不回收此内存,直到 ReleasePrimitiveArrayCritical() 调用

4 附录

4.1 本文源码

  • HelloJNI.java

  • HelloJNI.h

  • HelloJNI.c

4.2 参考