JNI介绍

JNI是Java Native Interface的缩写,中文为JAVA本地调用。从Java 1.1 开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。

JNI的使用场景

  1. Java应用需要与本地其它非Java应用交互
  2. 遇到性能瓶颈,通常本地代码执行效率要优于Java代码
  3. JDK提供的功能不足以帮我们实现需要的功能

JNI的问题

  1. 因为JNI有一个Native这个特点,一点有项目用了JNI,也就说明这个项目基本不能跨平台了。
  2. JNI调用是相当慢的,在实际使用的之前一定要先想明白是否有这个必要。
  3. 因为C++和C这样的语言非常灵活,一不小心就容易出错,比如代码就没有写析构字符串释放内存,就存在内存泄漏的问题,对于java developer来说因为有了GC 垃圾回收机制,所以大多数人没有写析构函数这样的概念。所以JNI也会增加程序中的风险,增大程序的不稳定性

JNI实战

主要分为以下几个步骤

  • 编写带有native声明的方法的java类
  • 使用javac命令编译所编写的java类
  • 使用javah -jni 类的全限定名 生成扩展名为h的头文件
  • 使用C/C++实现本地方法
  • 将C/C++编译的文件生成动态连接库(dll文件)
  • 在java项目中引入生成的C/C++库文件

1.编写java程序

1.这里以HelloWorld为例,java代码如下:

package com.aliencat.javabase.jni;

public class HelloJni {

    public native String sayHello(String name);

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

    public static void main(String[] args) {
        System.out.println(new HelloJni().sayHello("hello"));
    }
}

声明native方法:如果你想将一个方法做为一个本地方法的话,那么你就必须声明改方法为 native的,并且不能实现。

Load动态库:System.loadLibrary(“hello”);加载动态库(我们可以这样理解:我们的方法 displayHelloWorld()没有实现,但是我们在下面就直接使用了,所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是System.loadLibrary();的参数“hello”是动态库的名字。

2.然后进行编译

打开控制台,进入java目录下输入以下指令:

javah -jni com.aliencat.javabase.jni.HelloJni

即可生成一个一个头文件com_aliencat_javabase_jni_HelloJni.h,内容如下:

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

#ifndef _Included_com_aliencat_javabase_jni_HelloJni
#define _Included_com_aliencat_javabase_jni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
	/*
	 * Class:     com_aliencat_javabase_jni_HelloJni
	 * Method:    sayHello
	 * Signature: (Ljava/lang/String;)V
	 */
	JNIEXPORT jstring JNICALL Java_com_aliencat_javabase_jni_HelloJni_sayHello
	(JNIEnv*, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

这个h文件相当于我们在java里面的接口,这里声明了一个Java_com_aliencat_javabase_jni_HelloJni_sayHello
(JNIEnv*, jobject, jstring);方法,然后在我们的本地方法里面实现这个方法,也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致。其中jobject为java的this引用,而jstring即是传入的字符串参数。

3.使用Visual Studio编写c/c++本地方法实现

下载安装Visual studi

下载地址:https://visualstudio.microsoft.com/zh-hant/vs/whatsnew/

然后安装选择以下组件即可:

新建项目

选择创建动态链接库

java jni jna 调用go jni调用java方法 效率_java jni jna 调用go

引入头文件

JDK目录的include目录下有一个jni.h的文件,include的win32目录下有个jni_md.h文件,还有java类生成C头文件HelloJni.h,也一起拷贝到DLL工程的目录下。主要在目录的头文件上右键选择添加->现有项:

java jni jna 调用go jni调用java方法 效率_c++_02

用visual studio编写C代码

hello.cpp即为我创建的c++文件。

本地方法和javah命令生成的头文件里面声明的方法名相同。
代码如下:

#include "pch.h"
#include "com_aliencat_javabase_jni_HelloJni.h"
#include <iostream>
#include <stdio.h>


JNIEXPORT jstring JNICALL Java_com_aliencat_javabase_jni_HelloJni_sayHello
(JNIEnv* env, jobject, jstring jstr) {

    //将jstring类型转换成char*类型
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("GB2312");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0)
    {
        rtn = (char*)malloc(alen + 1); //new char[alen+1];  
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);

    printf("%s ", rtn);
    printf("this is C++ print");
    const char* str = "this is come from C++ ";
    return env->NewStringUTF(str);
}

4.生成动态库

首先选中项目根目录后右键点击属性,选择配置管理器,配置如下:

java jni jna 调用go jni调用java方法 效率_c++_03

选中项目根目录后右键点击生成即可,控制台输出如下即表示成功:

java jni jna 调用go jni调用java方法 效率_java jni jna 调用go_04

5.在java项目中引入本地库

以idea为例:

java jni jna 调用go jni调用java方法 效率_c++_05

选择你的vs编译输出的文件夹(看你的vs控制台输出的位置)即可,我的是在以下面路径下:


java jni jna 调用go jni调用java方法 效率_native_06


6.运行程序 java HelloJni.

加载本地库后即可使用

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

执行main方法后输出如下:

this is come from C++ 
hello this is C++ print

JNI数据类型

Java类型

本地类型(JNI)

描述

boolean(布尔型)

jboolean

无符号8比特

byte(字节型)

jbyte

有符号8比特

char(字符型)

jchar

无符号16比特

short(短整型)

jshort

有符号16比特

int(整型)

jint

有符号32比特

long(长整型)

jlong

有符号64比特

float(浮点型)

jfloat

32比特

double(双精度浮点型)

jdouble

64比特

void(空型)

void

N/A