JNI调用C++规则

  • Java方法前要用native关键字修饰,并且只有方法声明,没有实现,因为实现代码在C++里面
  • C++方法前要用JNIEXPORT和JNICALL宏修饰
  • C++方法中的数据类型,要使用与Java数据类型相对应的JNI类型
  • C++方法名必须完全符合Java_JavaPackageName_JavaClassName_JavaFunctionName的格式
  • Java中的native方法可以是static的,也可以是static的,只要方法名匹配即可

使用IntelliJ Idea和CLion开发JNI

从零开始搭建并运行一个JNI项目并不难
但对于新手来说,每个小细节都可能出错,靠自己尝试出来可能要浪费大量时间
所以这里还是不辞繁琐,细讲一遍

本次使用的开发工具是IntelliJ Idea和CLion,它们分别是Java和C++的开发环境
我们以后的NDK开发,和这个流程基本一致,只不过NDK所有工作都可以在AndroidStudio中完成
Android项目比较复杂,而我们现在演示的JNI项目则十分简单,除了必需的代码文件,几乎没什么多余的干扰信息
这样更方便我们弄清Java/C++混编背后的运作原理
虽然dll开发不是安卓开发者必须掌握的,但这其中包含了很多共通性的原理,对大家长远发展很有帮助

工作中时常会看到很多人,只要一接触到复杂点的开发工具,编译环境,或是编译失败,就开始烦躁放弃
其实这就是平时只关心业务功能实现,对软件的编译、运行、协作原理了解太少的原因
由于不了解通用原理,对新事物的分析解决能力很弱,更别提自己去对开发环境进行设计或优化了

回到正题,我们开始讲解,如何从零开始,搭建并运行一个JNI项目

在IntelliJ Idea中创建一个空的Java项目,创建一个类文件,在其中添加我们想要的native方法
IntelliJ Idea中的蓝色文件夹表示源码目录,黄色文件夹表示编译配置等非代码目录
我们可以看到,这个项目就一个src文件夹放源码,一个libs文件夹放dll库,结构非常简单
在这里插入图片描述
由于JNI对C++文件格式的要求比较复杂,我们自己生成比较麻烦且容易出错,所以需要借助工具来完成
JDK默认就提供了一个javah指令,用于根据Java文件中的native接口,直接生成C++头文件

我们在src文件夹处右键,打开Terminal控制台,然后执行javah指令
Terminal是IntelliJ Idea内战的快捷控制台,可以在里面执行cmd指令
我们在src处打开Terminal,cmd默认就会自动切换到src目录处,十分方便
注意javah指令的执行目录,还有类名要包括包名,这两点都不能错
在这里插入图片描述
在这里插入图片描述
我们已经得到了C++头文件,接下来我们要实现头文件中的接口方法,并编译成dll文件

由于头文件中用到的数据类型都是JNI类型,这在原生C++中是没有的,所以我们要把定义JNI数据类型的文件一起拷贝到C++工程中
JNI数据类型的定义需要使用jni.h和jni_md.h这两个文件,可以在jdk/include目录下找到

在CLion中创建一个新的C++ Libarary工程,将生成的JNI头文件和JNI数据类型定义文件一起拷入
创建一个cpp文件,实现JNI头文件中定义的方法
在这里插入图片描述
如图所示,C++工程和上面的Java工程一样,结构非常简单
到此为止,C++代码已经编写完成了,接着我们要将C++代码编译为dll文件
由于我们添加了新的代码文件,所以需要修改编译脚本,即CMakeLists.txt中的编译文件列表
在这里插入图片描述
Build - Rebuild Project,编译dll文件
在这里插入图片描述
现在只要把dll文件拷到Java项目里面就可以运行了
dll存放的位置是和工程配置有关系的,如果没有修改过配置,默认放到工程根目录下就行了
在这里插入图片描述
dll全部放在外面看起来很乱,如果我们想把dll全部放到libs文件夹下面该怎么设置呢?
网上有的教程又会叫我们把dll放到JDK或C盘下面,为什么这样也可以?
为什么有时明明按照网上教程来了,但就是dll加载失败?
为什么明明项目可以运行,打包成jar包后,dll就加载失败了?
其实我们刚刚已经说了,dll存放位置是取决于工程配置的
网上很多dll使用教程是按他自己环境来的,所以到了别人机子上就不合适了
所以我们需要弄清楚dll的加载原理,下一篇博客我们会专门来讲解这个事情

其实这个事情本身比较简单,之所以我要讲这个,是看到有些40多岁的工程师,还会出现dll找不到不知道该怎么办的问题,说明不求甚解的人实在是不少,所以决定专门写个博客来讲下,如果有人正好看到,以后就可以少走弯路

源代码


	//Hello.java

	package com.easing.java;
	
	public class Hello {
	
	    static {
	        System.loadLibrary("libhello");
	    }
	
	    public static native int sum(int a, int b);
	
	    public static void main(String[] args) {
	        Hello instance = new Hello();
	        int sum = instance.sum(100, 200);
	        System.out.println("sum result is : " + sum);
	    }
	}


	//com_easing_java_Hello.h

	#ifndef _Included_com_easing_java_Hello
	#define _Included_com_easing_java_Hello
	
	#include "jni.h"
	
	#ifdef __cplusplus
	    extern "C" {
	#endif
	        JNIEXPORT jint JNICALL Java_com_easing_java_Hello_sum(JNIEnv *, jobject, jint, jint);
	#ifdef __cplusplus
	    }
	#endif

	#endif


	//com_easing_java_Hello.cpp

	#include "com_easing_java_Hello.h"
	
	JNIEXPORT jint JNICALL Java_com_easing_java_Hello_sum(JNIEnv *env, jobject obj, jint a, jint b) {
	    return a + b;
	}


	//CMakeLists.txt

	set(CMAKE_CXX_STANDARD 17)
	
	project(hello)
	
	add_library(
	        hello SHARED
	        jni.h
	        jni_md.h
	        com_easing_java_Hello.h
	        com_easing_java_Hello.cpp
	)


JNI数据类型

Java TypeNative Type
intjint
longjong
doublejdouble
booleanjboolean
bytejbyte
charjchar
Stringjstring
Objectjobject
Classjclass
Object[]jobjectArray
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐