zoukankan      html  css  js  c++  java
  • 在Ubuntu上体验一下JNI开发

    系统环境

    软件 版本 备注
    VMware 16.1.2 build-17966106 16 Pro,虚拟机
    Ubuntu 20.04.2 LTS 安装在虚拟机中的操作系统
    CLion 2021.1.2 运行C++项目的软件

    1.安装SDK

    1、安装JDK(编译运行Java语言的)

    sudo apt install openjdk-8-jdk-headless
    

    2、安装gcc(编译运行C语言的)

    sudo apt install gcc
    

    3、安装g++ (编译运行C++语言的)

    sudo apt install g++
    

    4、安装make

    sudo apt install make
    
    Software Version
    gcc 9.3.0
    g++ 9.3.0
    make 4.2.1

    2.CLion创建一个C++项目

    1、创建一个可运行的C++项目:

    2、由于是第一次创建,它要求我配置gcc,g++,make的路径:

    3. idea创建一个Java项目

    1、New -> Project...: 我们这里选择创建一个简单的Java项目,然后点下一步

    第一次使用时 Project SDK 可能需要你选择一下你的 JDK 的安装路径。

    2、这步直接点击 Next: 不去选择模板

    3、最后,给你的Java项目取一个名字就可以完成创建了

    4. 编写使用JNI的Java项目

    4.1 动态链接库

    为了简单,我们在 Idea 中创建一个 HelloWorld 的 Java类:

    package org.coderead.jvm.example.jni;
    
    public class HelloWorld {
      public static native void hi();
    
      public static void main(String[] args) {
        hi();
      }
    }
    

    此时,直接运行程序,我们会遇到第一个错误:

    Exception in thread "main" java.lang.UnsatisfiedLinkError: org.coderead.jvm.example.jni.HelloWorld.hi()V
    	at org.coderead.jvm.example.jni.HelloWorld.hi(Native Method)
    	at org.coderead.jvm.example.jni.HelloWorld.main(HelloWorld.java:7)
    

    出现这个错误的主要原因:org.coderead.jvm.example.jni.HelloWorld.hi() 首先不是一个Java方法,而是一个 Java Native Interface,而 native 方法会在程序加载 HelloWorld 这个类时,需要去加载动态链接库

    Java程序中,加载动态链接库的函数是 System.loadLibrary

    4.2 Java程序从哪里加载动态链接库

    我们对程序进行改写:

    package org.coderead.jvm.example.jni;
    
    public class HelloWorld {
        public static native void hi();
    
        static {
            System.loadLibrary("jni");
        }
        public static void main(String[] args) {
            hi();
        }
    }
    

    △ 文件名格式: lib + 文件名 + .so,因此,在linux系统上运行时,真正寻找的文件实际上是 libjni.so
    抛出以下错误:

    Exception in thread "main" java.lang.UnsatisfiedLinkError: no jni in java.library.path
    	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
    	at java.lang.Runtime.loadLibrary0(Runtime.java:871)
    	at java.lang.System.loadLibrary(System.java:1124)
    	at org.coderead.jvm.example.jni.HelloWorld.<clinit>(HelloWorld.java:7)
    

    java.library.path 是一个JVM环境变量,我们写个程序,打印它当前的值:

    package org.coderead.jvm.example.jni;
    
    public class PrintLibraryPath {
    
        public static void main(String[] args) {
            System.out.println(System.getProperty("java.library.path"));
        }
    }
    

    我们得到以下输出:

    /usr/java/packages/lib/amd64:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib
    

    所以我们把动态链接库文件,放在上面的任意一个文件夹中,就可以被找到了,个人比较倾向于放在 /lib 中。
    △ 所以,System.loadLibaray 会在上面打印出来的目录中寻找 lib + 文件名 + .so ,比如 libjni.so。

    5 JNI 头文件

    虽然,我们现在需要的是动态链接库的文件(C/C++程序库),但是先不要着急,我们还需要先准备好 JNI 头文件。

    5.1 生成 JNI 文件

    javac 编译命令,可以帮助我们生成 JNI 文件。命令格式如下:

    javac [-encoding utf8] -h targetDir sourceFile
    

    例如我在 Idea 的 Terminal 中输出以下命令进行编译:

    javac -h /home/geekziyu/IdeaProjects/MainJava/jni/ src/org/coderead/jvm/example/jni/HelloWorld.java
    
    • /home/geekziyu/IdeaProjects/MainJava 是我的项目根目录;
    • sourceFile 文件的路径用的是 / 且结尾是 .java,否则会出现找不到文件的情况;
    • 我在这条命令中,使用的是相对路径而不是绝对路径;

    如图所示,org_coderead_jvm_example_jni_HelloWorld.h 就是我们生成的 JNI 文件。

    5.2 注意事项

    ★ 只有当Java文件中有native方法时,才会生成JNI头文件,否则,就不会有JNI文件。

    package org.coderead.jvm.example.jni;
    
    public class HelloWorld2 {
    
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    

    这样一个程序,你用以下命令编译,也不会/jni/ 下生成 JNI 文件的!

    javac -h /home/geekziyu/IdeaProjects/MainJava/jni/ src/org/coderead/jvm/example/jni/HelloWorld2.java
    

    ★ 如果Java程序中引用了其他的类,比如extends,需要关联引用的文件才能生成JNI头文件

    比如说,这个例子中,Child 继承自 Parent:

    package org.coderead.jvm.example.jni;
    
    public class Child extends Parent {
    
        static {
            System.loadLibrary("jni");
        }
    
        public native void sayHi();
    
        @Override
        public void introduce() {
            sayHi();
        }
    
        public static void main(String[] args) {
            Child child = new Child();
            child.introduce();
        }
    }
    

    Parent 类也十分简单:

    package org.coderead.jvm.example.jni;
    
    public class Parent {
    
        public void introduce() {
            System.out.println("I am a father");
        }
    }
    

    执行编译的情况如下:

    正确的命令应该是:

    javac -h /home/geekziyu/IdeaProjects/MainJava/jni/ src/org/coderead/jvm/example/jni/Parent.java src/org/coderead/jvm/example/jni/Child.java
    
    • 前一条命令显然是执行失败了,报出了 error: cannot find symbol
    • 后一条命令把 src/org/coderead/jvm/example/jni/Parent.java 带着一起编译就能正常生成 JNI 文件了。

    5.3 函数原型解释

    我们再来看一下 Child 和 HelloWorld 生成的 JNI 文件有什么不同之处:
    以下截取自 org_coderead_jvm_example_jni_Child.h 文件:

    /*
     * Class:     org_coderead_jvm_example_jni_Child
     * Method:    sayHi
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_org_coderead_jvm_example_jni_Child_sayHi
      (JNIEnv *, jobject);
    

    以下截取自 org_coderead_jvm_example_jni_HelloWorld.h 文件:

    /*
     * Class:     org_coderead_jvm_example_jni_HelloWorld
     * Method:    hi
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_org_coderead_jvm_example_jni_HelloWorld_hi
      (JNIEnv *, jclass);
    
    • Child.h 中的 native 方法是非静态方法,所以 Java_org_coderead_jvm_example_jni_Child_sayHi 的第二个参数是 jobject 类型,即 Java语言中的对象本身的引用this;
    • HelloWorld.h 中的 native 方法是静态方法,所以 Java_org_coderead_jvm_example_jni_HelloWorld_hi 的第二个参数是 jclass 类型。

    6. 回CLion编辑C/C++项目

    1. 创建 src/jniinclude/jni 文件夹;
    2. 把刚才 Idea 中生成的 org_coderead_jvm_example_jni_HelloWorld.horg_coderead_jvm_example_jni_Child.h 拷贝到 Clion项目的 include/jni 目录中;

    经过上面两步之后,我们的 Clion 中的项目的文件夹如图所示:

    打开拷贝过来的HelloWorld.h,你会看到如下错误:

    此时,你需要在你的 CMakeLists.txt 加上以下代码:

    include_directories("/usr/lib/jvm/java-8-openjdk-amd64/include")
    include_directories("/usr/lib/jvm/java-8-openjdk-amd64/include/linux")
    

    需要注意的是,/usr/lib/jvm/java-8-openjdk-amd64 是你的 JDK 安装目录,
    你可以在Terminal中使用命令 sudo find /usr/ -name 'jni.h' 进行搜索

    你可能还需要点击 Reload Changes... 才能让你不再报错:

    1. 接着我们新建 C/C++ Source File: src/jni/org_coderead_jvm_example_jni_Child.cpporg_coderead_jvm_example_jni_HelloWorld.cpp文件

    src/jni/org_coderead_jvm_example_jni_Child.cpp 文件内容如下:

    #include "../../include/jni/org_coderead_jvm_example_jni_Child.h"
    #include <iostream>
    /*
     * Class:     org_coderead_jvm_example_jni_Child
     * Method:    sayHi
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_org_coderead_jvm_example_jni_Child_sayHi
            (JNIEnv *, jobject) {
        std::cout << "C say hi to you!" << std::endl;
    }
    

    src/jni/org_coderead_jvm_example_jni_HelloWorld.cpp 文件内容如下:

    #include "../../include/jni/org_coderead_jvm_example_jni_HelloWorld.h"
    #include <iostream>
    /*
     * Class:     org_coderead_jvm_example_jni_HelloWorld
     * Method:    hi
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_org_coderead_jvm_example_jni_HelloWorld_hi
            (JNIEnv *env, jclass clazz) {
        std::cout << "C Hello World!" << std::endl;
    }
    

    6.1 编译so文件

    sudo g++ -shared -fPIC -I /usr/lib/jvm/java-8-openjdk-amd64/include -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux -o /lib/libjni.so src/jni/org_coderead_jvm_example_jni_Child.cpp src/jni/org_coderead_jvm_example_jni_HelloWorld.cpp 
    

    现在可以找到生成的 /lib/libjni.so:


    我的 /lib 文件是软连接文件,它指向 /usr/lib,所以 /lib 中的文件和 /usr/lib 的文件是同一批文件

    7 回到IDEA运行Java程序

    我们运行 HelloWorld.java 程序,这下正常输出结果:

    再试试 Child.java 程序,输出结果:

    参考文档

    《UBUNTU 中 PYCHARM 添加启动图标(桌面快捷方式)》阅读

    我自己在文件夹 /usr/share/applications 创建的 Desktop Entry 文件,看不到桌面图标,头痛...

    《在ubuntu下安装openjdk》阅读

  • 相关阅读:
    写给实习生的第一天
    写给实习生的第一天
    写给实习生的第一天
    老师不能把你怎样,但外面的世界可以!
    老师不能把你怎样,但外面的世界可以!
    adjA=(detA)A-1
    如果它仅对输入0才得到输出0
    isotropic trace
    detAB=detAdetB
    解释 纯量矩阵
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/14906268.html
Copyright © 2011-2022 走看看