JNI浅尝

什么是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工程.png

创建完成之后的目录结构是这样子的

C++Library目录结构.png

可以看到,创建的时候会默认生成一个library.h和一个library.cpp文件。这里我们是要自己引入.h头文件、再实现这个头文件的方法,所以可以删掉这个这两个文件。

现在我们找到上面生成的.h头文件,复制粘贴到libaray目录下,
然后修改CMakeLists.txt文件

配置.h文件.png

切换到Register.h文件,发现有报错


找不到依赖的头文件.png

那么这个依赖在哪里呢?我们需要找到依赖的.h文件,复制到libaray项目下面。
这个jni.h在jdk里面/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/include/jni.h
复制完之后,看到还报错,需要把尖括号改成双引号

.h引用方式不对,需要改成双引号引用.png

未识别JNIEXPORT.png

这里需要再引入一个头文件jni_md.hjni.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文件:

动态库文件.png

这个文件就是生成的动态库了。

引用动态库

我们复制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函数:


大功告成.png

问题

  1. Android开发平时用到的动态库不是.so结尾的吗,.dylib是什么鬼?

  2. 什么是静态注册?什么是动态注册?

  3. C/C++中如何调用java代码?

  4. jni在编写和使用的过程中,有什么需要特别注意的地方吗?

待续...

  1. 本人是个初学者,本篇文章记录了自已编写代码的过程以及思路,可能有些模棱两可的情况,欢迎批评指正。
  2. 上面只说的都只是JNI的静态注册,计划后续更新动态注册代码编写过程。

你可能感兴趣的:(JNI浅尝)