Jni操作Java对象

上一篇写了一下java使用jni的入门,这篇写一下如何通过jni操作java对象。用eclipse开发的不一定会开发java,就像上篇例子那样,离开eclipse只能写个helloworld,如果写个大型的java工程可能就不行了。本文也顺带记录一下不用eclipse怎么组织一个java工程。

工程结构

这篇想手敲还挺费劲,由于命令较多,搞不好会有遗漏,还是写个makefile直接粘贴吧。
工程结构很简单,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/root/devel/java/test2
├── classes
│   ├── a
│   │   ├── b
│   │   │   ├── c
│   │   │   │   └── PersonJni.class
│   │   │   └── Person.class
│   │   └── Hello.class
│   ├── a_b_c_PersonJni.h
│   └── libperson.so
└── src
├── a
│   ├── b
│   │   ├── c
│   │   │   └── PersonJni.java
│   │   └── Person.java
│   └── Hello.java
├── makefile
└── PersonJniImpl.c

我这个组织结构不一定合理,我的原则是需要手敲的所谓代码都放到src目录下,所有生成的东西都放到classes目录下。

写makefile

1
2
3
4
5
6
7
8
9
10
11
12
JDKHOME = /opt/jdk1.7.0_79
INCS = -I$(JDKHOME)/include -I$(JDKHOME)/include/linux
all:
#javac a/Hello.java -d ../classes
#javac a/b/Person.java -d ../classes
find . -name '*.java' -exec javac {} -d ../classes \;
cd ../classes && javah a.b.c.PersonJni
gcc $(INCS) -fPIC -shared -o ../classes/libperson.so PersonJniImpl.c
clean:
#find . -name '*.class' | xargs -I{} rm {}
find ../classes -name '*.class' -exec rm {} \;
rm ../classes/libperson.so -f

这里的makefile是为了方便写这篇文章组织的,没有按照规范写,以后再写makefile的东西吧。

写java代码

不多说了,直接贴代码

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package a.b;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public String toString() {
return name + ":" + age;
}
public static String getInfo() {
return "这是一个人";
}
}

PersonJni.java

1
2
3
4
5
6
7
8
9
10
package a.b.c;
import a.b.Person;
public class PersonJni {
public native void test();
public native String getName(Person p);
public native int getAge(Person p);
public native String getInfo();
public native Person constructPerson(String name, int age);
public native void setNameAge(Person p, String name, int age);
}

Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package a;
import a.b.Person;
import a.b.c.PersonJni;
public class Hello {
static {
System.loadLibrary("person");
}
public static void main(String[] args) {
Person p1 = new Person("小明", 22);
System.out.println("hello, " + p1);
PersonJni pj = new PersonJni();
pj.test();
System.out.println(pj.getName(p1));
System.out.println(pj.getAge(p1));
System.out.println(pj.getInfo());
Person p2 = (Person)pj.constructPerson("小红", 20);
System.out.println("hello, " + p2);
pj.setNameAge(p2, "小刚", 33);
System.out.println("hello, " + p2);
}
}

写c代码

写c代码之前要先make一下,这样能生成对应jni的头文件,然后就可以编写PersonJniImpl.c了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "../classes/a_b_c_PersonJni.h"
JNIEXPORT void JNICALL Java_a_b_c_PersonJni_test
(JNIEnv *env, jobject obj)
{
printf("this is jni test.\n");
}
JNIEXPORT jstring JNICALL Java_a_b_c_PersonJni_getName
(JNIEnv *env, jobject o, jobject p)
{
jclass personClass = (*env)->GetObjectClass(env, p);
jmethodID getName = (*env)->GetMethodID(env, personClass, "getName", "()Ljava/lang/String;");
jstring name = (jstring)(*env)->CallObjectMethod(env, p, getName);
//const char *str = (*env)->GetStringUTFChars(env, name, 0);
//return (*env)->NewStringUTF(env, str);
return name;
}
JNIEXPORT jint JNICALL Java_a_b_c_PersonJni_getAge
(JNIEnv *env, jobject o, jobject p)
{
jclass personClass = (*env)->GetObjectClass(env, p);
jmethodID getAge = (*env)->GetMethodID(env, personClass, "getAge", "()I");
jint age = (*env)->CallIntMethod(env, p, getAge);
return age;
}
JNIEXPORT jstring JNICALL Java_a_b_c_PersonJni_getInfo
(JNIEnv *env, jobject o)
{
jclass personClass = (*env)->FindClass(env, "a/b/Person");
jmethodID getInfo = (*env)->GetStaticMethodID(env, personClass, "getInfo", "()Ljava/lang/String;");
jstring info = (jstring)(*env)->CallStaticObjectMethod(env, personClass, getInfo);
return info;
}
JNIEXPORT jobject JNICALL Java_a_b_c_PersonJni_constructPerson
(JNIEnv *env, jobject o, jstring name, jint age)
{
jclass personClass = (*env)->FindClass(env, "a/b/Person");
jmethodID personConstructMethod = (*env)->GetMethodID(env, personClass, "<init>", "(Ljava/lang/String;I)V");
jobject person = (*env)->NewObject(env, personClass, personConstructMethod, name, age);
return person;
}
JNIEXPORT void JNICALL Java_a_b_c_PersonJni_setNameAge
(JNIEnv *env, jobject o, jobject p, jstring name, jint age)
{
jclass personClass = (*env)->GetObjectClass(env, p);
jmethodID setName = (*env)->GetMethodID(env, personClass, "setName", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env, p, setName, name);
jmethodID setAge = (*env)->GetMethodID(env, personClass, "setAge", "(I)V");
(*env)->CallVoidMethod(env, p, setAge, age);
}

这里需要强调一下,c和c++的jni实现的写法是不一样的。每个写一行对比一下吧。

c写法:

1
(*env)->GetObjectClass(env, p);

c++写法:

1
env->GetObjectClass(p);

只要牵涉env指针操作的都是这个规律

编译、测试

切换到makefile所在的目录,执行如下命令:

1
2
3
4
$ make
$ cd ../classes
$ export LD_LIBRARY_PATH=.
$ java a.Hello

测试结果:

1
2
3
4
5
6
7
8
Picked up _JAVA_OPTIONS: -Xmx2048m -XX:MaxPermSize=512m -Djava.awt.headless=true
hello, 小明:22
this is jni test.
小明
22
这是一个人
hello, 小红:20
hello, 小刚:33

咦,结果中第一行是什么鬼?其实它是因为我设了一个_JAVA_OPTIONS的环境变量后出现的,因为我之前编译hadoop相关的的东西时,由于要求jvm内存很大,而默认的不能满足,所以设置了它。先不管,以后有时间再说个中含义吧。

附:打jar包

通过上面的操作可以知道怎么组织java工程了,但是,我们平时执行java程序时一般不会拿一堆class文件去执行,一般都会打成个jar包。下面记录一下把上面生成的类文件打成jar包并能正常执行输出测试结果的过程。

假设现在还在makefile文件所在的src目录,执行如下命令:

1
2
3
$ cd ../classes
$ tar cf hello.jar a #创建带自动生成的MANIFEST.MF文件的jar,这是执行测试是不行的,提示找不到主类
$ tar xf hello.jar #解压包

修改解压出来的META-INF/MANIFEST.MF,修改内容如下(记住最后有个空行):

1
2
3
Manifest-Version: 1.0
Created-By: 1.7.0_79 (Oracle Corporation)
Main-Class: a.Hello

前两行是打包时自动生成的,Main-Class: a.Hello是修改时添上的,意在指定主类(即main函数入口类)。

再执行如下命令重新打包,并测试执行:

1
2
$ jar cvfm hello1.jar META-INF/MANIFEST.MF a #经过这样打包就能找到主类了
$ jar -jar hello1.jar #执行测试程序

经过以上操作,jar打包就完成了。