JNI 开发 1 环境搭建

其实我原本准备把本文命名为

不知道为什么最近看到很多关于 JNI 的东西刚好最近又在搞这方面的东西踩了不少坑所以说就写篇入门教程吧 第一章

的,但是好像不能低于 7 个字,所以,唉。

JNI 是啥

JNI , Java Naive Native Interface , 即 Java 原生代码接口,它将一个动态链接库(*.so/*.dll)中的函数和 Java 函数映射起来,使你可以通过 Java 代码调用一些 C/C++/C#/D/Go/Rust 等原生语言写的函数。

为什么要用 JNI

  • 因为 Java 不是世界上最快的语言
  • 因为有些东西已经拿 C++ 写好了
  • 因为不会 Java
  • 对绕过 JVM 的 GC 手动管理内存的渴望导致欲火焚身(雾

为什么要用 Dev Cpp 这种垃圾

  • 因为我是 OIer
  • 因为我博客很多读者都是 OIer
  • 因为打开速度快

我又没说过以后不会讲基于 CMake 的编译教程,急啥 = =

为什么不教 JNA

为了追求运行效率,我们已经选择了放弃抽象,向 C++ 势力低头。 因此,再为了抽象而选择丢弃一定性能的 JNA ,<del>你还写个JB的JNI啊</del>是不划算的。

为什么不用 Go/Rust/D 写动态链接库

<del>因为不会</del>

依赖

  • JDK 1.8(忍不了低版本)
  • Dev C++ 5.11

知识储备

  • 一定的 Java or Kotlin 技能
  • 一定的 C++ 技能
  • 会用命令行
  • 会用键盘鼠标

准备工作

首先安装 JDK 和 Dev Cpp ,并将[jdk path]/bin/这个目录添加到环境变量。注意,这个目录下有很多重要的可执行文件,比如 javac javap javah 等。等会我们就需要用到 javah 。

然后 JDK 安装目录下有一个叫 include 的目录,里面有几个 C++ 头文件,这个 include 目录还有一个子目录,似乎叫 win32 。 现在把这些文件全部复制出来,放到一个地方。注意,把 include/win32 目录和 include 目录下的文件拷进同一个外部目录,这个外部目录是你的 MinGW 所在位置。 对于 Dev Cpp 用户,它在 [dev安装目录]/MinGW64/x86_64-w64-mingw32/include下。 也就是说,把刚才那几个头文件拷入这里。再次强调,把 include/win32 和 include 目录下的头文件放到一起。 (感觉像是个 flatmap , 233 )

开始吧

打开 Dev Cpp , 文件 -> 新建 -> 项目,像下图这样填写信息。 强烈建议选上 C++,这样你就可以使用模板了。项目名随便写,建议使用全 ASCII 。

点击确定后它会让你选个目录保存 [name].dev 这个文件,这个文件是 Dev Cpp 的工程开发的配置文件,建议专门新建一个文件夹来存放这个小宝贝。

然后 Dev Cpp 就打开了一个新工程,里面有一堆 C# 风格的谜之 WinAPI 代码。 在左上角通过右键 -> 移除文件的方式把这些微软的傻逼删了。

这时候可以看到世界一片令人毛骨悚然的苍白,先不慌做任何事情。 我们先来写一段 Java 来缓解一下刚才看到 WinAPI 时内心的惆怅。这里处于懒,我直接抄了手上一个项目的代码。


package org.algo4j.win;

/**
 * For Windows only
 *
 * @author ice1000
 */
public final class WinAPI {
	public static native void beep(int frequency, int duration);
}

如果你是 Kotlin 厨,那么可以这样:


@file:JvmName("WinAPI")
package org.algo4j.win

/**
 * For Windows only
 *
 * @author ice1000
 */
external fun beep(frequency: Int, duration: Int)

然后使用各自语言的编译器编译这段代码,得到一个 class 文件。假定现在你的文件树是这样的结构:


root:
  - src/org/algo4j/win:
    - WinAPI.java(或 WinAPI.kt)
  - out/org/algo4j/win:
    - WinAPI.class
  - jni:
    - jni.dev

其中, src 是 Java 源码, out 是 Java 目标文件, jni 是 JNI 的 C++ 端代码的根据地。请严格遵守这样的文件结构。

下面是核心内容:

命令行移动到 out 目录下,执行以下指令:


javah org.algo4j.win.WinAPI

然后你不会看到任何回显。如果出现了以下字样:


'javah' 不是内部或外部命令,也不是可运行的程序或批处理文件。

请重新配置环境变量。

好我们继续。如果一切顺利,那么此时你应该在 out 目录下看到一个名字巨长的 C++ 头文件。这是你现在的文件树:


root:
  - src/org/algo4j/win:
    - WinAPI.java(或 WinAPI.kt)
  - out:
    - org_algo4j_win_WinAPI.h
    - org/algo4j/win:
      - WinAPI.class
  - jni:
    - jni.dev

然后把那个名字巨长的头文件拷进 jni 目录,并在 Dev 工程中添加那个文件。此时你可以看看这个文件的内容,它拥有非常猥琐、令人难以直视的缩进。


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

#ifndef _Included_org_algo4j_win_WinAPI
#define _Included_org_algo4j_win_WinAPI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_algo4j_win_WinAPI
 * Method:    beep
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_org_algo4j_win_WinAPI_beep
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

此时我们看到了一个方法的定义。接下来我们就需要再新建一个 cpp 文件,里面写上以下实现代码。方便起见,头文件被我重命名为 WinAPI.h 。

注意, JNI 开发中,为了使 Java 的跨平台数据长度也能用于 C++ ,请使用 jni.h 中提供的对应的 Java 类型。下面这个表希望能对你有一定的帮助(手打的):

C++ 类型对应的 Java 类型jintintjlonglongjshortshortjcharcharjbytebytejbooleanbooleanjintArrayint[]jlongArraylong[]jshortArrayshort[]jcharArraychar[]jbyteArraybyte[]jbooleanArray boolean[]jobjectjava.lang.Object

特别说下那个奇怪的 jsize ,其实是对应的 C++ 的 size_t ,在 Java 中没有。

下面是 WinAPI 的实现文件。


#include "WinAPI.h"
#include <windows.h>

JNIEXPORT auto JNICALL Java_org_algo4j_win_WinAPI_beep(
		JNIEnv *,
		jclass,
		jint freq,
		jint duration) -> void {
	Beep(
			static_cast<unsigned long>(freq),
			static_cast<unsigned long>(duration)
	);
}

然后点击 Dev 的编译按钮。此时你会等待一段时间,千万不要去喝咖啡了。编译完成之后你的文件树是这样的:


root:
  - src/org/algo4j/win:
    - WinAPI.java(或 WinAPI.kt)
  - out/org/algo4j/win:
      - WinAPI.class
  - jni:
    - jni.dev
    - jni.dll
    - jni.layout
    - libjni.a
    - libjni.def
    - Makefile.win
    - WinAPI.h
    - WinAPI.cpp
    - WinAPI.o

可以看到多了一堆自动生成的文件。。。

这里给一个很重要的建议,如果你要把这个项目上传到版本控制平台,请把所有的 *.exe *.a *.win *.def *.dll *.o *.layout 添加到 gitignore 里,因为这些都是目标文件的一部分

这时把 jni.dll 拷贝到 JVM 的运行目录去,写上一个 psvm :


public final class WinAPI {
	public static native void beep(int frequency, int duration);
	public static void main(String[] args) {
		System.loadLibrary("jni"); // 这里这个 jni 是你 dll 的名字
		beep(1000, 1000);
	}
}

如果你厨 Kotlin :


fun main(args: Array<String>): Unit {
	System.loadLibrary("jni")
	beep(1000, 1000)
}

运行它。假设你没有搞错目录,并且现在使用的电脑里面有蜂鸣器,那么此时你会听到一阵悦(jing)耳(song)的蜂鸣器声,持续 1s 。 1s 结束以后程序结束。

如果你搞错目录了的话,你会遇到 UnsatisfiedLinkError 。那么请换个地方放 jni.dll 试试 = =

本次最基本最简单的教程到这里 = =

祝你愉快。 JNI 的实际使用还可以参照我的算法库,这是一个活生生的例子 DAZE ,请点击GitHub传送门。喜欢的话给个 star 哟。

你学到了什么

  • JNI 开发环境搭建
  • Dev Cpp 配置
编辑于 2017-01-30

文章被以下专栏收录