说明:
本实例代码来自于《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;