前面已经介绍了在 Windows 上快速的搭建 JNI 开发环境本节将会实现一个简单的 hello world 版的程序,目的是掌握 JNI 开发的流程,具体的实现细节会在后续进行介绍。

JNI 开发流程梳理:

  1. 在 java 文件,这里的 java 文件我们以 HelloJNI.java 举例。在其中定义 native 方法
  2. 使用 javah HelloJNI 命令生成 HelloJNI.h 头文件(注意该命令后只跟文件名,不要带.java 后缀)
  3. 使用 codeblocks 创建动态链接库工程,并将 HelloJNI.h 添加到该工程
  4. 实现 HelloJNI.h 中声明的方法
  5. 使用 codeblocks build 该工程,得到 HelloJNI.dll 动态链接库
  6. 将 HelloJNI.dll 拷贝到 java 工程中,直接使用即可。

接下来对每一个步骤进行详细介绍。

定义 native 方法

首先我们创建一个 Java 工程,并创建一个 java 文件

1
2
3
4
5
public class HelloJNI {
public native String getStr();

public native static String sGetStr();
}

此时只需定义方法即可,和普通方法类似,只不过前面多了一个 native 的关键字。可以看到我们定义了两个方法,一个非静态方法,一个静态方法,二者都是从 c 代码中获取一个字符串。

生成 HelloJNI.h 头文件

进入命令行,定位到 HelloJNI.java 所在的路径,使用javah HelloJNI命令生成 HelloJNI.h 头文件(注意该命令后只跟文件名,不要带.java 后缀)

其实这个 javah 命令会读取目标文件,这里就是 HelloJNI.java,然后扫名里面的 native 方法,拿到 native 方法的签名,而对于其它非 native 的方法,javah 则不关心。

使用 codeblocks 创建动态链接库工程

打开 codeblocks,创建工程,选择工程类型为:Dynamic Link Library,然后起一个工程名,这个名字随意,但是最后生成的 dll 文件的名字是和工程名是一样的。

新创建的工程会默认有一个 main.cpp 和 main.h 文件,但我们用不到,忽略它或者删除它。
然后把上一步得到的 HelloJNI.h 文件拷贝到当前工程下,不过只拷贝当前工程的目录下,当前工程也不能识别,我们还需要在当前工程右键,add files,选择我们刚刚复制过来的 HelloJNI.h 文件,这样才会被 codeblocks 纳入到当前工程。

但是此时还要再做一步,注意看生成的 HelloJNI.h 文件的内容是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 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: getStr
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJNI_getStr
(JNIEnv *, jobject);

/*
* Class: HelloJNI
* Method: sGetStr
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJNI_sGetStr
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

可以看到其首先就包含了jni.h文件,如果我们不做任何配置的话,codeblocks 在编译代码时是找不到这个头文件的。那么这个jni.h文件在哪里呢?

首先明确一点:你的 windows 上安装了 JDK,因为jni.h就在 JDK 的安装路径的 include 目录下,eg:我的电脑上的路径为:D:\Program Files\Java\jdk1.8.0_20\include\,同时jni.h 文件中还引入了 jni_md.h 文件,所以还要找到 jni_md.h 文件,其位于本机 jdk 的 include\win32 文件夹下:D:\Program Files\Java\jdk1.8.0_20\include\win32\

明确了要准备的两个 h 文件的路径,接下来就要让 codeblocks 在编译时能找到对应的头文件。

我们将这两个头文件都拷贝到当前动态链接库工程的代码文件夹下,同样的,使用 add files,将这两个文件添加到工程中。同时要注意,此时要将 HelloJNI.h 中的#include <jni.h>改为#include "jni.h"

实现 native 方法

接下来就要真正用 c 来实现之前定义的方法了,实现如下,现在可以先不用管具体的语法,只要有一个感性的认识即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "HelloJNI.h"

/*
* Class: HelloJNI
* Method: getStr
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJNI_getStr(JNIEnv *env, jobject obj){
return (*env)->NewStringUTF(env,"Hello");
}

/*
* Class: HelloJNI
* Method: sGetStr
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_HelloJNI_sGetStr(JNIEnv * env, jclass clazz){
return (*env)->NewStringUTF(env,"world");
}

生成 dll 文件

在 codeblocks 左侧列表,鼠标当前 project 右键 选择 build,生成对应的 dll 文件,其路径为当前 project 路径下的:bin\debug中。

在 Java 工程中使用 dll

将上一步生成的 dll 文件拷贝到目标 java 工程的根路径,不需其它配置,在 java 文件的静态代码块中加载对应的动态链接库,不需要 dll 后缀。然后运行该 java 程序,即可看到对应结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloJNI {
public native String getStr();

public native static String sGetStr();

static {
System.loadLibrary("HelloJNI");
}

public static void main(String[] args) {
System.out.println(new HelloJNI().getStr());
System.out.println(sGetStr());
}
}

优化

在上一步中,直接把 dll 拷贝到 java 工程目录下了,但是这样有一个问题,那就是你每改动一点 c 的代码,都要重新生成 dll,然后在把该 dll 拷贝到 java 工程目录下,这样是很麻烦的。

这里的 java 代码所用的 ide 是 idea,我们可以在 idea 中为当前工程设置一个环境变量, 让该变量直接定位到 codeblocks 工程生成 dll 的路径,这样就免去了不断拷贝 dll 文件的麻烦。具体做法是:在 idea 中点击Run > Edit Configurations,将需要加载dll文件的Java文件的 在VM options选项中加入java.library.path,即 dll(或so)文件所在的目录,比如本文中 dll 放在 codeblocks 项目目录中的 E:\Csrc\HelloJNI\bin\Debug 中,
idea设置librarypath

同时注意到:按照如图的配置,只能是为AccessMethod.java文件配置了路径,也就是说在其它文件要使用 dll 文件时,依然找不到 dll。所以要为每一个需要使用 dll 的 java 文件都配置VM options,虽然还是有些麻烦,但是已经比我们修改 c 代码后,不断的拷贝 dll 文件方便多了。我们的目的还是说快速的了解 JNI 的开发,真正使用 Android Studio 进行 NDK 开发时,就不需要老配置环境了。