文章目录
  1. 1. 程序的流程
  2. 2. 首先编写Hello.java
  3. 3. 编译该文件,生成 Hello.class
  4. 4. 运行javah,产生头文件Hello.h
  5. 5. 然后按照Hello.h中的声明方法,编写本地方法的实现文件Hello.c:
  6. 6. 编译和加载库文件

首先要声明的是,这个例子的代码是在网上到处都有,我在Fedora Core 6, JDK1.5.0_04下调试成功。写这篇文章的目的是记录一下JNI程序的编译和运行步骤。

程序的流程

在JAVA程序中输入用户名“John”,然后在调用C程序中的方法,显示“Hello, John”.

首先编写Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Hello
{
static
{
try
{
//此处即为本地方法所在链接库名
System.loadLibrary("hello");
}
catch(UnsatisfiedLinkError e)
{
System.err.println( "Cannot load hello library: "+ e.toString() );
}
}
public Hello(){}
//声明的本地方法
public native void SayHello(String strName);
}

编译该文件,生成 Hello.class

javac Hello.java

运行javah,产生头文件Hello.h

javah Hello

Hello.h中的内容不要做修改,先打开它,看看本地方法是怎么声明的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: SayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_SayHello
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif

然后按照Hello.h中的声明方法,编写本地方法的实现文件Hello.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <jni.h>
#include "Hello.h"
#include <stdio.h>
//与Hello.h中函数声明相同
JNIEXPORT void JNICALL Java_Hello_SayHello (JNIEnv * env, jobject arg, jstring instring)
{
//从instring字符串取得指向字符串UTF编码的指针
char *str = (char *)(*env)->GetStringUTFChars( env, instring, NULL);
printf("Hello, %s ", str);
//通知虚拟机本地代码不再需要通过str访问Java字符串。
(*env)->ReleaseStringUTFChars( env, instring, NULL);
}

编译和加载库文件

其实以上的步骤并没有什么难度,只是一个模板流程而已,我认为对于初学者来说,让人头疼的部分还是编译和加载库文件,下面就是这一关键的步骤。为了编译方便,我先写了一个makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 最后生成的库文件:libhello.so依赖于Hello.o和makefile
libhello.so:Hello.o makefile
# 这句就是gcc下编译共享库文件的方法
gcc -Wall -rdynamic -shared -o libhello.so Hello.o
# 目标文件Hello.o是由Hello.c和Hello.h编译生成的
Hello.o:Hello.c Hello.h
# 这就是编译c文件的方法,“-I”选项指出了编译时需要使用JDK提供的一些文件,
# 在我的机器上JDK的目录是/usr/java/jdk1.5.0_04,你可以根据具体情况来改变它
gcc -Wall -c Hello.c -I./ -I/usr/java/jdk1.5.0_04/include -I/usr/java/jdk1.5.0_04/include/linux
# 这是清理标志位,如果你使用的命令是make cl,则会删除所有产生的目标文件*.o和库文件*.so,
# 这通常用来要重新编译所有文件的情况下,先执行make cl,然后执行 make
cl:
rm -rf *.o *.so

(注意:以“#”开头的部分是注释,你可以全部删除之, gcc和rm等命令的开头必须用tab键做一个空格)

运行一下命令来编译我们的库文件:

#sudo chmod +x makefile
#make
gcc -Wall -c Hello.c -I./ -I/usr/java/jdk1.5.0_04/include -I/usr/java/jdk1.5.0_04/include/linux
Hello.c:15:2: warning: no newline at end of file
gcc -Wall -rdynamic -shared -o libhello.so Hello.o

那个警告信息我们可以不管,这样的话就在当前目录下生成了库文件:libhello.so

再写一个类来调用我们的本地方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ToSay
{
public static void main(String argv[])
{
ToSay say = new ToSay();
}
public ToSay()
{
Hello h = new Hello();
//调用本地方法向John问好
h.SayHello("John");
}
}

编译该文件:

javac ToSay.java

最后就是配置java的运行环境了,使用JNI加载库文件时,必须修改环境变量:LD_LIBRARY_PATH,这样java程序才能准确的找到库文件的位置,在网上有很多关于修改LD_LIBRARY_PATH的方法,但是到我这里都行不通。

我的处理方法是这样的,为运行方便我们先写一个运行脚本,不妨就叫它run.sh:

1
2
3
4
5
#!/bin/bash
export LD_LIBRARY_PATH=.:$PATH:$LD_LIBRARY_PATH
java ToSay
注意,我把PATH也加进来了。这样库文件的路径就包括:当前目录,系统的PATH路径以及原来的LD_LIBRARY_PATH路径,在程序执行时,它就会按照以上的顺序来搜索需要的库文件。

用以下的命令来执行这个脚本:

sudo chmod +x run.sh
./run.sh

运行的结果是:

# ./run.sh
Hello, John
#

Have a nice day!!!

文章目录
  1. 1. 程序的流程
  2. 2. 首先编写Hello.java
  3. 3. 编译该文件,生成 Hello.class
  4. 4. 运行javah,产生头文件Hello.h
  5. 5. 然后按照Hello.h中的声明方法,编写本地方法的实现文件Hello.c:
  6. 6. 编译和加载库文件