您的位置:首页 > 运维架构 > Linux

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

2015-02-08 18:14 387 查看

前言

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

前些日子工作中使用到了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程序已经开发完成。

参考

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jni java