什么是JNI
百度百科:https://baike.baidu.com/item/JNI/9412164
当然,里面也有关于JNI的实现,懂的看官就不必往下看啦,大致上差不多。
开发环境
- Mac电脑
- IntelliJ IDEA(写java代码)
- Clion(写C/C++代码)
- 配置好java运行环境
开始写代码
第一步,在IDEA里创建一个java工程,创建一个java类
Register.java
public class Register {
public native void staticReg();
public static void main(String[] args) {
Register register = new Register();
register.staticReg();
}
}
说明:public native void staticReg();
native修饰的方法就是C方法,相当于这里定义一个接口,让C代码去实现。
为了让C代码可以知道这个接口,我们需要进行以下两步操作:
1. 使用javac Register.java
命令,把java类编译成.class文件
注意:需要在Register.java文件的目录下执行该命令。
生成的.class文件内容如下:
Register.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Register {
public Register() {
}
public native void staticReg();
public static void main(String[] var0) {
Register var1 = new Register();
var1.staticReg();
}
看起来跟java代码差不多
2. 使用javah Register
命令,根据刚才生成的.class文件,生成一个.h头文件
注意:javah 命令后面的Register是不带后缀的
Register.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class Register */
#ifndef _Included_Register
#define _Included_Register
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Register
* Method: staticReg
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_Register_staticReg
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看到,我们从最开始的java文件,生成了最终的.h头文件。了解C语言的同学大概已经猜到了,这个头文件里定义了一个java文件里public native void staticReg()
对应的方法Java_Register_staticReg (JNIEnv *, jobject)
。
既然已经有了C的头文件,我们是不是在我们的C的library项目里引用这个头文件,并实现其中的方法,就可以生成一个动态库了呢?
说干就干 ~
第二步,在Clion里创建一个C++ Library工程register
创建完成之后的目录结构是这样子的
可以看到,创建的时候会默认生成一个
library.h
和一个library.cpp
文件。这里我们是要自己引入.h头文件、再实现这个头文件的方法,所以可以删掉这个这两个文件。
现在我们找到上面生成的.h头文件,复制粘贴到libaray目录下,
然后修改CMakeLists.txt
文件
切换到Register.h文件,发现有报错
那么这个依赖在哪里呢?我们需要找到依赖的.h文件,复制到libaray项目下面。
这个jni.h
在jdk里面/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/include/jni.h
复制完之后,看到还报错,需要把尖括号改成双引号
这里需要再引入一个头文件jni_md.h
(jni.h
里引用了这个),目录/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jni_md.h
(看官可自行搜索)
把jni_md.h
,jni.h
都复制粘贴之后,可以看到Register.h
文件就已经不报错了。
然后创建一个Register.cpp
文件,并引用Register.h头文件,实现里面的方法
Register.cpp
//
// Created by wh on 2019/9/27.
//
#include "Register.h" //引用头文件
/*
* Class: Register
* Method: staticReg
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_Register_staticReg
(JNIEnv *env, jobject obj) {
printf("this is my first jni program"); //实现头文件中定义的方法,打印一个字符串
}
到这里,代码基本上写完了。
现在我们把Libaray工程build一下,可以看到,生成了一个libregister.dylib
文件:
这个文件就是生成的动态库了。
引用动态库
我们复制libregister.dylib
的绝对路径,切回到java工程,修改Register.java
类
public class Register {
//静态加载库文件
static {
System.load("/Users/wh/CLionProjects/register/cmake-build-debug/libregister.dylib");
}
public native void staticReg();
public static void main(String[] args) {
Register register = new Register();
register.staticReg();
}
}
运行一下main函数:
问题
Android开发平时用到的动态库不是
.so
结尾的吗,.dylib
是什么鬼?什么是静态注册?什么是动态注册?
C/C++中如何调用java代码?
jni在编写和使用的过程中,有什么需要特别注意的地方吗?
待续...
- 本人是个初学者,本篇文章记录了自已编写代码的过程以及思路,可能有些模棱两可的情况,欢迎批评指正。
- 上面只说的都只是JNI的静态注册,计划后续更新动态注册代码编写过程。