用 Dev Cpp 开发 JNI :第一章 环境搭建

2017/01/23 JNI

文章已经发到知乎,点击链接可以去围观 DAZE 。

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 ,你还写个 JB 的 JNI 啊是不划算的。

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

因为劳资不会

依赖

  • 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 。

dev creating

点击确定后它会让你选个目录保存 [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 类型
jint int
jlong long
jshort short
jchar char
jbyte byte
jboolean boolean
jintArray int[]
jlongArray long[]
jshortArray short[]
jcharArray char[]
jbyteArray byte[]
jbooleanArray boolean[]
jobject java.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 配置

Search

    Post Directory



    如果觉得这篇文章给您带来了收获或者说它值得您这么做,您可以选择对我进行捐助。
    下面是微信支付哟