JNI初步 -- 在CPP中创建并访问Java对象

说明: 

本实例代码来自于《Android 框架揭秘》

程序运行环境 -- Ubuntu 12.04 64bit, JDK1.6, g++ 4.6.3 。


1  创建java文件

/**
 * 在C中创建java对象, 并调用java对象的方法
 */
public class JniFuncMain{
	
	private static int staticIntField = 300;

	static{
		System.loadLibrary("jnifunc");	
	}

	public static native JniTest createJniObject();
	
	public static void main(String[] args){
		System.out.println("java中createJniObject()调用本地方法");
		
		JniTest jniObject = createJniObject();

		jniObject.callTest();  //该方法在c层创建
	}
}

class JniTest{
	private int intField;

	public JniTest(int num){
		intField = num;
		System.out.println("在C中调用构造方法,初始化该类的对象");	
	}
	
	//此方法由JNI本地函数调用
	public int callByNative(int num){
		System.out.println("在C中调用JniTest类的成员方法callByNative. num = " + num);
		return num;	
	}

	//该方法测试由C创建的java对象能不能正常调用他的方法
	public void callTest(){
		System.out.println("由C创建的java对象在java中进行方法调用");
	}
}


2  编译java文件

javac JniFuncMain.java

ls JniFuncMain.class
JniFuncMain.class



3  生成头文件

javah JniFuncMain

ls JniFuncMain.h
JniFuncMain.h

头文件JniFuncMain.h的内容如下:

#include <jni.h>
/* Header for class JniFuncMain */

#ifndef _Included_JniFuncMain
#define _Included_JniFuncMain
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JniFuncMain
 * Method:    createJniObject
 * Signature: ()LJniTest;
 */
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

4  创建CPP实现文件

该实例演示了以下知识点:

在CPP中如何调用Java中的静态方法,

如何创建并返回Java对象, 

如何调用Java对象的成员方法, 

如何修改Java对象的成员变量

jnifunc.cpp的文件内容如下:

#include <jni.h>
#include "JniFuncMain.h"
#include <stdio.h>

JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
  (JNIEnv * env, jclass clazz){

	jclass targetClass;
	jmethodID mid;
	jobject newObject;
	jstring helloStr;
	jfieldID fid;
	jint staticIntField;
	jint result;
	
	//获取JniFuncMain类中的staticField静态变量的值
	fid = env->GetStaticFieldID(clazz, "staticIntField", "I");
	staticIntField = env->GetStaticIntField(clazz, fid);
	printf("cpp获取JniFuncMain类中的staticField静态变量的值");
	printf("    JniFuncMain.staticField = %d\n", staticIntField);
	
	//查找生成对象的类
	targetClass = env->FindClass("JniTest");
	//查找该类的构造方法
	mid = env->GetMethodID(targetClass, "<init>", "(I)V");
	
	//生成JniTest类的对象, 返回该对象的引用
	printf("cpp中生成niTest类的对象\n");
	newObject = env->NewObject(targetClass, mid, 100);
	
	//调用对象的方法, 对象在cpp中, 对象的方法在java中定义
	mid = mid = env->GetMethodID(targetClass, "callByNative", "(I)I");  //找到方法
	result = env->CallIntMethod(newObject, mid, 200);
	
	//设置JniObject对象的intField的值
	fid = env->GetFieldID(targetClass, "intField", "I");
	printf("cpp 中设置JniTest对象的intField值为200\n");
	env->SetIntField(newObject, fid, result);
	
	//返回对象的引用
	//虽然在java中刚得到这个对象, 但在cpp中已经对这个对象进行了操作, 例如调用了方法, 设置了成员变量的值
	return newObject;
	
	
}

5  编译CPP文件为动态库

有三个知识点需要注意:

1) linux下的动态库必须以lib开头, 格式为 lib库名称.so

2) 编译cpp文件使用linux中的g++命令

3) JniFuncMain.h中包含了jdk中的两个头文件, 需要在g++命令中指定这两个头文件的路径

编译命令如下:

 g++ -fPIC -D_REENTRANT -I/develop/jdk1.6.0_31/include -I//develop/jdk1.6.0_31/include/linux -shared -o libjnifunc.so jnifunc.cpp

编译完成之后在当前目录下存在libjnifunc.so文件

ls lib*
libjnifunc.so

6  运行java程序

注意以下问题:

在java程序运行时, 首先会加载动态库。虚拟机在特定的路径下加载动态库,即java.library.path属性指定的路径。(详细信息见上一篇博客JNI初步 -- Hello JNI示例)在上篇文章中, 我们把动态库放入了java.library.path属性指定的一个目录中。这不是一个好的方法,因为修改了linux系统的动态库。在本示例中,将在java命令执行时指定libjnifunc.so动态库所在的目录。

使用以下命令执行java程序:

java -Djava.library.path='.' JniFuncMain

-D选项 表示在java运行时使用制定的属性值

. 代表当前路径, 表示在执行System.loadLibrary("jnifunc");时从当前路径加载动态库。

以下是程序在控制台的输出:

 java -Djava.library.path='.' JniFuncMain
java中createJniObject()调用本地方法
cpp获取JniFuncMain类中的staticField静态变量的值    JniFuncMain.staticField = 300
cpp中生成niTest类的对象
在C中调用构造方法,初始化该类的对象
在C中调用JniTest类的成员方法callByNative. num = 200
cpp 中设置JniTest对象的intField值为200
由C创建的java对象在java中进行方法调用

如果不使用-D选项指定路径,还可以使用export命令设置LD_LIBRARY_PATH环境变量,然后再执行java命令。

其实原理是一样的, 都是指定要加载的动态库的路径为当前目录。执行结果如下:

zhangjg@MyUbuntu://home/zhangjg/JNITest/CreateJavaObjectInC$ export LD_LIBRARY_PATH=".":$LD_LIBRARY_PATH
zhangjg@MyUbuntu://home/zhangjg/JNITest/CreateJavaObjectInC$ java JniFuncMain
java中createJniObject()调用本地方法
cpp获取JniFuncMain类中的staticField静态变量的值    JniFuncMain.staticField = 300
cpp中生成niTest类的对象
在C中调用构造方法,初始化该类的对象
在C中调用JniTest类的成员方法callByNative. num = 200
cpp 中设置JniTest对象的intField值为200
由C创建的java对象在java中进行方法调用

补充:

有时在写jni程序时, 常常被jni中定义的类型所迷惑, 也不知道这些类型是如何与java中的类型对应的。下面列出一些定义,作为备忘。更详细的类型定义在jni.h头文件中声明。jni.h文件存在于JDK目录下的include目录中。
jni.h头文件中一些数据结构的定义
		
		typedef unsigned char	jboolean;
		typedef unsigned short	jchar;
		typedef short		jshort;
		typedef float		jfloat;
		typedef double		jdouble;
		typedef jint            jsize;

		struct _jfieldID;
		typedef struct _jfieldID *jfieldID;

		struct _jmethodID;
		typedef struct _jmethodID *jmethodID;
		
		class _jobject {};
		typedef _jobject *jobject;

		class _jclass : public _jobject {};
		typedef _jclass *jclass;
		
		class _jstring : public _jobject {};
		typedef _jstring *jstring;





你可能感兴趣的:(linux,jni,cpp,g++)