在Windows下使用JNI技术.md

Java Native Interface技术用来调用本地接口。在Windows系统中,使用的是dll动态链接库技术。

TIPS:静态库和动态库区别

静态库在link时会被整合到二进制文件中,而动态库则不会,它在运行时独立存在,独立地被调用。
想想Java是一种半编译半解释型的语言,编译的最终产物是class文件,我们肯定不能将C++的库文件编译到class中去,所以一定使用的是动态库。

一、定义接口

首先我们需要建立Java工程,并且编写java类,该Java类用来定义接口,即需要C++为自己提供什么样的服务,这样C++编程人员才能根据接口为你编写相应的功能。

package fcom.elite.jni;
public class AddService {
    public native int add(int a, int b);
}

定义接口之后,编译当前类,并且使用javah命令生成头文件。

cd <parent-to-package>
javac fcom/elite/jni/AddService
javah -classpath . fcom.elite.jni.AddService

至此,C++编程人员就可以拿着你的头文件去愉快的开发了。
这里有一点需要注意的是,生成的头文件中的函数声明都是包含包名的,也就是说当在Java中调用时,会依据包名和方法名来确定函数在库中位置,因此如果我们将生成的库文件拿到另外的项目中使用,由于包名不一致,也就无法使用了。

二、C++程序的编写与编译

第一步之后,我们拿到的头文件如下:

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

#ifndef _Included_fcom_elite_jni_AddService
#define _Included_fcom_elite_jni_AddService
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     fcom_elite_jni_AddService
 * Method:    add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_fcom_elite_jni_AddService_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

可以看到,其中声明了一个函数Java_fcom_elite_jni_AddService_add,组成规则为:Java_包名_类名_方法名,该函数有四个参数,
分别是JNI的环境变量,jobject当前调用jni的java对象,AddService#add()方法的参数a和参数b。

知道了这些,程序就不难写了。

#include "fcom_elite_jni_AddService.h"

JNIEXPORT jint JNICALL Java_fcom_elite_jni_AddService_add
  (JNIEnv *env, jobject obj, jint a, jint b){
      return a+b;
  }

很简单的过程,毕竟只是演示。
程序都写完了,我们该想想如何编译的问题了,我们当然可以自己去创建一个VS工程,然后使用VS编译。但是这样对于像我们这样的简单的工程来说,实在是太麻烦了,而且如果我以后想要把这些程序拿到Linux下去编译一个so库,跨平台的问题也是需要解决一段时间的。
所以我在这里使用cmake来进行编译。
关于Cmake的详细介绍,请移步官网
当然,如果想要快速上手,也可以看一下这篇文章,说的很简练。

首先我们要写CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(HEWO)
set(BUILD_USE_64BITS on)
FIND_PACKAGE(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})
SET(LIBHELLO_SRC fcom_elite_jni_AddService.c)
ADD_LIBRARY(addlib SHARED ${LIBHELLO_SRC})

然后,我在项目目录下新建了一个build目录,进入build目录

cd build
cmake -G "Visual Studio 13 2015 Win64" ..
cmake --build .

由于我使用windows10 64位的系统,所以需要编译64位的dll,否则在jni进行调用时将会报安全错误,无法运行。更多问题具体详见《在Windows上使用CMake编译64位dll》

三、将DLL导入到Java工程

我们将生成的addlib.dll文件放入到java工程的resources目录下,这样编译后,addlib.dll将位于classpath的根路径下。
有人可能会在Java工程中使用System.loadLibrary()方法来加载库文件,但这种方法在实际生产中却不怎么方便,所以一般的思路都是先将库文件复制到临时目录中,然后获取文件路径,再通过System.load()加载库文件。
代码如下:

public class AddService {
    static {
        InputStream in = null;
        FileOutputStream out = null;
        String path = null;
        try {
            ClassLoader cl = AddService.class.getClassLoader();
            in = cl.getResourceAsStream("addlib.dll");
            File file = File.createTempFile("addlib", ".dll");
            file.deleteOnExit();
            out = new FileOutputStream(file);
            int i;
            byte[] buf = new byte[1024];
            while ((i = in.read(buf, 0, buf.length)) > 0) {
                out.write(buf, 0, i);
            }
            path = file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null)
                    in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.load(path);
    }

    public native int add(int a, int b);
}

再写一个Client类来调用AddService:

public class Client {
    public static void main(String[] args) {
        AddService service = new AddService();
        int count = service.add(4, 5);
        System.out.println("a+b=" + count);
    }
}

运行后,结果如下:

a+b=9

Process finished with exit code 0

DONE.

四、常见问题汇总

1. javah命令相关

(1)javah命令使用后,报错:

Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: fcom/elite/jni/AddService
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:129)
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:107)
        at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:64)
        at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)
        at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)
        at com.sun.tools.javah.Main.main(Main.java:46)

原因可能是你指定的是类文件的路径,而非类的包路径。

javah fcom.elite.jni.AddService  #正确,包路径
javah fcom/elite/jni/AddService   #错误,文件路径

(2)找不到 ‘AddService’ 的类文件

错误: 找不到 'AddService' 的类文件。

原因是你没有指定完整的包路径,或者是你没有指定正确的classpath。解决方法:
包含正确的classpath,建议在包所在目录的父目录进行javah的操作。
(3)无法访问AddService

javah AddService.java
错误: 无法访问AddService
  错误的类文件: .\AddService.class
    类文件包含错误的类: fcom.elite.jni.AddService
    请删除该文件或确保该文件位于正确的类路径子目录中。

问题出在javah时只需要指定类即可,不需要指定java文件。

2.cmake相关

(1)编译器错误

CMake Error: Error: generator : Visual Studio 14 2015 Win64
Does not match the generator used previously: Visual Studio 14 2015
Either remove the CMakeCache.txt file and CMakeFiles directory or choose a diffe
rent binary directory.

因为你之前已经使用Visual Studio 14 2015的编译器编译过了,编译后的结果会缓存在CMakeCache.txt文件中,如果你需要使用不同的编译器重新编译,那么必须删除CMakeCache.txt文件。
(2)Error: could not load cache
没有cache,这是因为指定的build目录下没有CMakeCache.txt造成的。通常是指定错了build目录,或者是没有进行首次编译造成的。只有在使用cmake --build命令时才会出现该问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值