陈中正的网络日志

JNI编程—— Linux下编写一个最简单的JNI程序

前言

好久没有更新博客了,自从实习之后就没更新过,忙成狗了。。

前些日子工作中使用到了Linux下的JNI编程,找到网上一篇文章,进行了一些改写,贴在这里以作记录。

JNI介绍

JNI其实是Java Native Interface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C&C++)。也许不少人觉得Java已经足够强大,为什么要需要JNI这种东西呢?我们知道Java是一种平台无关性的语言,平台对于上层的java代码来说是透明的,所以在多数时间我们是不需要JNI的,但是假如你遇到了如下的三种情况之一呢?

  • 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
    在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
  • 你的Java代码,需要得到一个文件的属性。但是你找遍了JDK帮助文档也找不到相关的API。
    在本地还有一个别的系统,不过他不是Java语言实现的,这个时候你的老板要求你把两套系统整合到一起。
  • 你的Java代码中需要用到某种算法,不过算法是用C实现并封装在动态链接库文件(so文件)当中的。

对于上述的三种情况,如果没有JNI的话,那就会变得异常棘手了。就算找到解决方案了,也是费时费力。其实说到底还是会增加开发和维护的成本。

JNI示例程序

说了那么多一通废话,现在进入正题。看过JDK源代码的人肯定会注意到在源码里有很多标记成native的方法。这些个方法只有方法签名但是没有方法体。其实这些naive方法就是我们说的 java native interface。他提供了一个调用(invoke)的接口,然后用C或者C++去实现。我们首先来编写这个“桥梁”.我自己的开发环境是 JDK1.6 + GCC + VIM + Makefile,先用VIM编写下面的代码。

// Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  
    public JniTest(){  
    }  
    public native void sayHello(String name);  
}

我的native本地方法有一个String的参数。会传递一个name到后台去。本地方法已经完成,现在来介绍下javah这个命令,接下来就要用javah敏玲来生成一个相对应的.h头文件。

javah是一个专门为JNI生成头文件的一个命令。打开Shell之后输入javah回车就能看到javah的一些参数。在这里就不多介绍我们要用的是 -jni这个参数,这个参数也是默认的参数,他会生成一个JNI式的.h头文件。在控制台进入到工程的根目录,然后输入命令。

javac com/jni/JniTest.java
javah -jni com.jni.JniTest

命令执行完之后在工程的根目录就会发现com_jni_JniTest.h 这个头文件。在这里有必要多句嘴,在执行javah的时候,要输入完整的包名+类名。否则在以后的测试调用过程中会发生java.lang.UnsatisfiedLinkError这个异常。到这里java部分算是基本完成了,接下来我们来编写后端的C代码(C++也可以)。

打开com_jni_JniTest.h 这个头文件,仔细观察一下这个方法,

//Cpp代码
/* * Class: com_jni_JniTest * Method: sayHello * Signature: (Ljava/lang/String;)V */  
JNIEXPORT void JNICALL Java_com_jni_JniTest_sayHello  
  (JNIEnv *, jobject, jstring);

在注释上标注类名、方法名、签名,至于这个签名是做什么用的,我们以后再说。在这里最重要的是 Java_com_ni_JniTest_sayHello这个方法。在Java端我们执行 sayHello(String name)这个方法之后,JVM就会帮我们唤醒在so文件里的Java_com_jni_JniTest_sayHello这个方法。因此我们新建一个C++ source file来实现这个方法。

//Cpp代码
// com_jni_JniTest.c
#include <iostream> 
#include "com_jni_JniTest.h" 

JNIEXPORT void JNICALL Java_jni_JniTest_sayHello 
    (JNIEnv* env, jobject obj, jstring name)  
{  
    const char* pname = env->GetStringUTFChars(name, NULL);  
    cout << "Hello, " << pname << endl;  
}

对应的Makefile如下:

# Makefile
libcom_jni_JniTest.so: com_jni_JniTest.o
    g++ -shared -fpic -O2 -o $@ $^
com_jni_JniTest.o: com_jni_JniTest.c
    g++ -fpic -O2 -c $^ -I.
clean:
    rm *.o
    rm *.so

make编译会提示找不到jni.h和jni_md.h,这两个文件在 $JDK_HOME/include 目录下,复制到本文件夹即可。

这个时候后端的C++代码也已经完成,接下来的任务就是怎么把他们连接在一起了,要让前端的java程序“认识并找到”这个动态链接库。加入System.loadLibrary(“Hello”);这句到静态初始化块里。

//Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  

    static{  
        System.loadLibrary("com_jni_JniTest");  
    }  
    public JniTest(){  
    }  
    public native void sayHello(String name);        
}

这样我们的代码就能认识并加载这个动态链接库文件了。万事俱备,只欠测试代码了,接下来在JniTest.java中添加测试代码。

//Java代码
// com/jni/JniTest.java
package com.jni;  

public class JniTest {  

    static{  
        System.loadLibrary("com_jni_JniTest");  
    }  
    public JniTest(){  
    }  
    public native void sayHello(String name);      
    public static void main(String[] args) {
        JniTest jt = new JniTest();
        jt.sayHello("World");
}

执行代码

java -Djava.library.path=. com.jni.JniTest

发现控制台打印出来Hello, World这句话。就此一个最简单的JNI程序已经开发完成。

参考

本文章迁移自http://blog.csdn.net/timberwolf_2012/article/details/43639159

Categories:  JavaWeb 

« Python:在指定目录下查找满足条件的文件 LeetCode: Restore IP Address »