[HOW-TO]JNI on Windows Using GCC(MinGW-w64)

God knows why I am using GCC on Windows to do JNI for calling Win32 API.

0. Overall instruction

  1. 写一个Java方法声明, 加上native关键字
  2. javah为含native方法的类生成一个头文件
  3. 根据头文件用c实现该方法并用gcc编译出链接库(-shared)
  4. 将链接库放在代码运行时同样的目录, 并在Java类方法加上静态的类加载代码
  5. 运行即可

1. 基本概念 What is JNI?

Java Native Interface(JNI) 是一种外部方法接口编程框架. JNI赋予了Java调用本地方法(如用c/c++调用操作系统的接口)的能力.

使用JNI 会导致性下降, 因为编译好的本地方法是操作系统独有不可移植的, 但是也可以通过分别编译多个操作系统的库, 在java代码中判断应该加载哪个库来曲线救国.

2. 编写Java中的native方法

JNI的形式是先在java代码定下你需要的方法, 然后在用C/C++代码去实现该方法.

如:

package pkg; 

class Cls {
    // 定义一个native方法
    native double f(int i, String s);
    static {
        System.loadLibrary(pkg_Cls);
    }
}

代码来源:[The Java Native Interface Overview ] (https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html)

上面的代码定义了一个f方法, native关键字声明这个方法是一个native方法, 不在java里是实现, 而是用其他的语言(C/C++/assembly,…)实现.

System.loadLibrary(“pkg_Cls”);是在加载包含本地方法(native methods)的动态库. 这个动态库就是用gcc带-shared 编译出来的库一样的.

System.loadLibrary(“pkg_Cls”)的参数是库的名称, 这个名称和实际选用的动态库文件名之间有不是直接对应而是有标准且平台独立的. 同一个参数pkg_Cls, 在windows上对应的是pkg_Cls.dll 这个库, 而在unix系上是libpkg_Cls.so.

3. 生成C/C++所需的头文件

Java方法写好后是没有实现的, 而实在C代码中实现, 而Java代码的native方法名和C的方法名由规定好的应关系对应, JVM会根据相应的对应关系去找相应的native方法.

这个对应关系可以直接用工具生成. JDK自带这个工具: javah.

例如我们有如下代码:

java/demo/App.java

package demo;
public class App {
    public native int  fun() ;
    public static void main(String[] args) {
        App app = new App();
        app.fun();
    }
}

那么我们就在java目录下(demo文件夹的上层目录)执行

javah demo.App

这个工具会在java目录下生成一个头文件:

java/demo_App.h

/* DO NOT EDIT THIS FILE - it is machine generated */
// 下面这行可能会报找不到头文件的错误, 见下一节
#include <jni.h>
/* Header for class demo_App */

#ifndef _Included_demo_App
#define _Included_demo_App
#ifdef __cplusplus
extern "C" {
#endif
/*  写c代码实现这个方法即可
 * Class:     demo_App
 * Method:    fun
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_demo_App_fun
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

拿着这个文件去实现然后生成dll即可

4. 编译共享库

首先你要获取你当前系统jdk的位置, 因为要用到其中的头文件jni.hjni_md.h 我的是:C:\Program Files\Java\jdk1.8.0_171\

那么上面两个头文件的位置就是C:\Program Files\Java\jdk1.8.0_171\includeC:\Program Files\Java\jdk1.8.0_171\include\win32

然后要有一个实现了的C/C++源码:

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

JNIEXPORT jint JNICALL Java_demo_App_fun(JNIEnv *, jobject){

    printf("This is jni!");
    return (jint) 100;
  }

然后用g++编译:

 g++ -shared -Wl,--kill-at \
 -I'C:\Program Files\Java\jdk1.8.0_171\include' \
 -I'C:\Program Files\Java\jdk1.8.0_171\include\win32'  \
 -o demo_App.dll demo_App.cc

没有错误的话就可以得到一个动态链接库demo_App.dll了.

这里的-shared是指编译生成一个共享库大概就是dll, -wl,--kill-at是为了和windows兼容的设定, 第二三行是加入jni头文件的位置让g++能够找到 jni.hjni_md.h, 可以根据自己的系统环境修改. 最后一行指明输入文件和输出文件.

5. 编译运行java代码

拿到动态链接库demo_App.dll之后, 就可以在刚刚Java文件上加上这个库的加载代码, 因为这个库是外部的所以要特别的代码来加载:

package demo;
public class App {
    // 指定加载的库位demo_App(不用dll后缀)
    static{
        System.loadLibrary("demo_App");
    }
    public native int  fun() ;
    public static void main(String[] args) {
        App app = new App();
        app.fun();

    }
}

让编译java代码,并将代码放在运行目录的同目录即可:

这里我们将dll放在java目录下App.java位于java\demo\App.java下

cd java # 切换到java目录
javac demo/App.java # 编译java文件
java demo.App

获得输出:

$ java demo.App
This is jni!

关于本地库的加载及文件位置

System.loadLibrary(“pkg_Cls”)加载库的路径是由java的系统属性( System property)java.library.path的值决定的. 可以用-Djava.library.path=<路径>参数指定.

java.library.path默认值测试

用了下面的代码进行测试:

public class App {
    public static void main(String[] args) throws Exception {
        String paths = System.getProperty("java.library.path");
        System.out.println(paths);
    }
}

测试结果:

Win10 Oracle JDK 1.8.0_171

包含当前目录, 以及path, 和一些奇怪的目录

C:\Program Files\Java\jdk1.8.0_171\bin; # jdk路径
C:\WINDOWS\Sun\Java\bin; # 不存在
C:\WINDOWS\system32;C:\WINDOWS;
%PATH%;
. # 当前目录
Debian 9.6 openjdk 1.8.0_212
  • 不包含当前目录, 也不包含path
/usr/java/packages/lib/amd64:
/usr/lib/x86_64-linux-gnu/jni:
/lib/x86_64-linux-gnu:
/usr/lib/x86_64-linux-gnu:
/usr/lib/jni:/lib:/usr/lib

Last modified on 2019-05-04