1. Java Native Interface

命题作文:对Java Native Interface (JNI) 有一个认识,使用JNI完成一个打印输出的工作。
本文通过回答三个环环相扣的问题,来对JNI的功能,作用做一个说明。

  1. JNI是干什么的?
  2. 面对一个问题,比如打印输出,JNI的工作流程是什么,每个步骤的目的是什么?
  3. 将每个步骤对应的代码写出来,跑出来。

一、JNI是干什么用的

JNI的中文为JAVA本地调用,本机上用其他语言编写并且编译过的代码(在linux上就是so,在windows上就是dll),可以通过这个本地接口(native interface)在java虚拟机(JVM)上运行[1]。

JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。JNI是JDK 的一部分,用于为Java提供一个本地代码的接口。比如:Javah,产生可以调用Java过程的C过程,或建立能被Java程序调用的C过程的头文件。

JNI使得运行在JVM虚拟机上的Java代码能够操作使用其它语言编写的应用程序和库,比如 C/C++以及汇编语言等。此外JNI提供的某些API还允许你把JVM嵌入到本地应用程序中。

JNI标准至少保证本地代码能工作在任何Java 虚拟机下实现。

使用JNI来整合本地代码和Java代码的步骤是确定的,没有再创作的余地,所以读者可以通过本文的步骤来逐步认识到,其实Java也是"没有什么不可以 "的。

二、JNI的工作机制是什么

如果现有一个java类需要通过JNI来运行用c编写的功能,比如打印输出hello world。那么应该进行如下步骤:

  1. 编写一个java类,在这个java类中,使用JNI加载需要调用的库函数和这个库函数中的具体方法。
  2. 编译这个java类。然后运行这个类即可。前提是本地库函数要准备好。但是一般而言,这个本地库函数是没有准备好的,或者说是要现场准备的。所以进入第三步。
  3. 现在介绍怎么准备java类所要调用的用c语言编写编译过的库函数。
  4. 为这个用c编写的库函数准备一个头文件,这样可以这个让c编写的库函数与调用这个库函数的java class进行交互。头函数是早期编程语言,包括C语言特有的,用于函数的声明,意思就是说有这么一个函数在,名字叫啥啥啥。所以,在这里,这个头函数里其实就是放了库函数里的方法的声明。
  5. 开始用C语言编写这个库函数。首先是写成源代码。在这个源代码中要调用为它准备好的头文件。然后编写这个库函数的主要功能,就是打印输出hello world,写好后,编译成库函数。一旦这个库函数准备好后,就可以回到第二步,运行java 类文件,然后就可以打印输出了。

三、以上例子的代码实现

  1. 编写这个java 类
public class HelloJNI 
{ 
    static { System.loadLibrary("hello"); // Load native library at runtime   
                                          // hello.dll (Windows) or libhello.so (Unixes) 
    } 
    // Declare a native method sayHello() that receives nothing and returns void 
    private native void sayHello(); 
    // Test Driver 
    public static void main(String[] args) { new HelloJNI().sayHello(); // invoke the native method 
    }
}
  1. 将这个java 类编译成class文件,命令如下:
    javac HelloJNI.java

    如果库函数已经准备好了,就可以运行这个class文件,得到打印输出的 Hello, World!命令如下:
    java HelloJNI
    否则,进入第3步:

  2. 与第二节的步骤对应起来

  3. 把步骤2得到的class进一步处理,得到库函数所需要的头函数。命令如下:
    javah HelloJNI

  4. 用C语言编写库函数,代码如下:

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h" 
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) { 
        printf("Hello World!\n"); 
        return;
}

2.1. 将这个编写好的源程序编译成o文件,在linux下的代码是:
gcc -fpIC -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -c HelloJNI.c
其中,/usr/lib/jvm/java-8-oracle/include表示头文件jni.h的所在路径,/usr/lib/jvm/java-8-oracle/include/linux表示头文件jni_md.h所在的路径,/usr/lib/jvm/java-8-oracle表示JDK的安装目录。
2.2. 将这个o文件进一步连接成库函数(so文件),在linux下的代码是:
gcc -o libhello.so -shared HelloJNI.o
2.3. 将so文件所在的路径加入环境变量中去,在linux下的代码是:
export LD_LIBRARY_PATH=/home/cxy/test
然后就可以回到第二步,用java HelloJNI命令运行class文件即可。
结果如下图所示:

Paste_Image.png

本文结束

Second Try

step1. 填写一个调用C语言的Java类

public class HelloJNI {
   static {
      System.loadLibrary("hello"); // Load native library at runtime
                                   // hello.dll (Windows) or libhello.so (Unixes)
   }
 
   // Declare a native method sayHello() that receives nothing and returns void
   private native void sayHello();
 
   // Test Driver
   public static void main(String[] args) {
      new HelloJNI().sayHello();  // invoke the native method
   }
}
  • static initializer invokes System.loadLibrary() to load a library.
  • the name of the library is hello/Hello. it is a native library.
  • the native library "Hello" contains a native method "sayHello()"
  • The native librry "Hello" will be mapped to libhello.so in Linux
  • This library should be included in Java's library path, otherwise, the program will throw a UnsatisfiedLinkError. You could include the library into Java Library's path export LD_LIBRARY_PATH=/home/x/Desktop/JNI
  • Next, we declare method "sayHello()" as a native instance method. native means this method is implemented in another language. The sayHello() is contained in the native library loaded.
  • The main() method allocate an instance of HelloJNI and invoke the native method sayHello(). new HelloJNI class's instance,and invoke the native method sayHello() in it.
  • Question? why declare method sayHello as a native instance method? I mean maybe System.loadLibrary("hello"); does not know it is a native library? or native library constain method that is not native?

Compile the "HelloJNI.java" into "HelloJNI.class".
javac HelloJNI.java

  • javac 是java语言编程编译器。全称 javacompilation.javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码class文件。My understanding is exe file.

Step2: Create the C/C++ Header file - HelloJNI.h 创建一个C的头文件,命名为HelloJNI.h

Run javah utility on the class file to create a header file for C/C++ programs: javah HelloJNI

  • the utility means utility program that is "HelloJNI.class"
  • what does headfile use for? The declaration, tell the program the name of the fun. Then, according to the name of the fun, it can find the body.
  • give the C file with headfile "HelloJNI.h", it can be inferred that the c file invokes the function declared in "HelloJNI.h". Thus, to see what does the c file invokes, open it.
  • #include <jni.h> it include a jni.h headfile,
  • JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject); is a function written by c.
  • Java_{package_and_classname}_{function_name}(JNI arguments) the naming convention for C function 命名规则。
  • Arguments is 命令行参数。
    • JNIEnv*: reference to JNI environment, which lets you access all the JNI fucntions.
    • jobject: reference to "this" Java object.
  • The extern "C" is recognized by C++ compiler only. It notifies the C++ compiler that these functions are to be compiled using C's function naming protocol (instead of C++ naming protocol). C and C++ have different function naming protocols as C++ support function overloading and uses a name mangling scheme to differentiate the overloaded functions. Read "Name Mangling".

Step 3: C Implementation - HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
 
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}
  • HelloJNI.c 相当于一个子函数,最终目的还是被调用,不同的是是被Java写的程序调用,不是被C写的程序调用。既然要被调用,就要把HelloJNI.c里面的函数的函数名提取出来,放在头文件里。这个函数名就是Java_HelloJNI_sayHello。所以,JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);是出现在HelloJNI.h这个头文件里面的。
  • void is the data type,表示返回值是空值。
  • JNIEXPORT is a key word, JNIEXPORT :在Jni编程中所有本地语言实现Jni接口的方法前面都有一个"JNIEXPORT",这个可以看做是Jni的一个标志,至今为止没发现它有什么特殊的用处。与被C语言调用做一个区分标识。
  • JNICALL :这个可以理解为Jni 和Call两个部分,和起来的意思就是 Jni调用XXX(后面的XXX就是JAVA的方法名,就是HelloJNI这个类中的方法sayHello())。
  • Java_HelloJNI_sayHello 就是被JNICALL调用的部分。也就是Java中的native 方法名,这里起名字的方式比较特别,是:包名+类名+方法名。

将这段C写的子函数,编译成动态库文件,这样就可以被调用。
step1. gcc -fpic -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -c HelloJNI.c其中,/usr/lib/jvm/java-8-oracle/include表示头文件jni.h的所在路径,/usr/lib/jvm/java-8-oracle/include/linux表示头文件jni_md.h所在的路径,/usr/lib/jvm/java-8-oracle表示JDK的安装目录。
step2. 将这个o文件进一步连接成库函数(so文件),在linux下的代码是:gcc -o libhello.so -shared HelloJNI.o
step3. 将so文件所在的路径加入环境变量中去,在linux下的代码是:export LD_LIBRARY_PATH=/home/cxy/test(注意:路径不一样)

step 4:执行

然后就可以回到第二步,用java HelloJNI命令运行class文件即可。
java HelloJNI
执行java class文件,从主函数进去,new一个HelloJNI类,并调用类中的sayHello方法。这个sayHello并没有像一般的java程序,里面定义了执行的代码语句,没有输入也没有输出。既然这样是不是就断了?不是,这个sayHello给出了关键字native,说明是用其他语言执行的。并且这个sayHello的执行部分已经写在了"hello"这个函数库里面。这个函数库:hello:就对应了linux中的动态库函数“libhello.so”。那么再调用这个“libhello.so”就可以继续执行下去。执行这个动态库函数首先去载入函数声明。发现函数声明JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject); 这个函数声明正好对应了java class “HelloJNI” 中的方法 “sayHello”,也就是说java class中要调用的“sayHello”在这个动态库so中找到了,那就顺着这个声明去找函数body,要给出路径才能找到,找到body就执行这个body,返回空值,java class “HelloJNI” 中的方法 “sayHello”获得空值,执行完毕。

FlowChat

编译顺序

Reference

  1. Jni接口-深入研究参数的传递(一)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • _ 声明: 对原文格式以及内容做了细微的修改和美化, 主要为了方便阅读和理解 _ 一. 基础 Java Nativ...
    元亨利贞o阅读 5,869评论 0 34
  • 一、NDK产生的背景 Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于J...
    Ten_Minutes阅读 3,473评论 1 27
  • 人群,潮水一般涨落 生生死死,街道依旧喧嚣 于潦草的生年 逢着一种不期而遇 辗转,分离 不问世事的归期 于温柔的波...
    慕籽阅读 354评论 2 4
  • 2017年9月16日 周一 晴天 今天一整天都在忙碌的搬家搬东西,因为是一个人所以开会跑了三趟,才把东西搬的差不多...
    娟姐66阅读 107评论 0 0