java代码访问本地代码(c/c++)
JNI: Java Native Interface(调用c/c++/其他本地代码,该接口提供了java与os本地代码互相调用的功能。
>首先在java类中声明一个native的方法。
>使用javah命令生成包含native方法定义的c/c++头文件。
.不会使用命令可以直接在命令行中敲入,例如:javac -help 【回车】 javah -help就会列出这个命令详细参数的用法。
.利用javah编译已经编译的.class文件并且class文件含有本地(native)的方法才可被解释。
.cmd切换到当前class文件所在的目录(不包括当前所在的包)javah 包名\class文件名【enter】
>按照生成的c/c++头文件来编写c/c++源文件。
本地代码(C/C++)访问java代码:在被调用的c/c++函数中也可以反过来访问java程序中的类。
javah工具生成c/c++函数声明中,可以看到有两个参数:
JNIEXPORT void JNICALL 包_类名(JNIEnv* env, jobject obj){};注:jobject指向java类中的对象引用(非静态)或类class(静态)的引用。
.JNIEnv类型实际上代表了java环境。通过这个JNIEnv*指针,就可以对java端的代码进行操作。例如,创建java类对象,调用java对象的方法,获取java对象的属性等的。JNIEnv的指针会被JNI传入到本地方法的实现函数中来对java端的代码进行操作。
.JNIEnv类中有很多函数可以用:
NewObject/NewString
New<TYPE>Array
Get/Set<TYPE>Field
Get/SetStatic<TYPE>Field
Call<TYPE>Method/CallStatic<TYPE>Method等等好多函数。
Java的类型在c/c++中的映射关系:
Jclass的获取:.为了能够在c/c++中使用java类,JNI.h头文件定义了jclass类型来表示Java中的Class类。
.JNIEnv类中有如下几个简单的函数可以取得:
1. jclass FindClass(const char* clsName);
2. jclass GetObjectClass(jobject obj);
3. jclass GetSuperClass(jclass obj);返回指定类的父类
注意:FindClass会在classpath系统环境变量下寻找类;传入完整的类名,此时包与包之间是用‘/’而不是‘.’。如:jclass cls_string = env->FindClass(“java/lang/String”);
.在c/c++本地代码中访问java端的代码,一个常见的应用就是获取类的属性和类的方法。为了在c/c++中表示属性和方法,JNI在jNI.h头文件中定义了jfieldID,jmethodID类型来分别代表java端的属性和方法。
.我们在访问,或是设置java属性的时候,首先就要先在本地代码取得代表该java属性的jfieldID,然后才能在本地代码进行java属性操作。同样的,我们需要呼叫java端的方法是,也是需要取得代表该方法jmethodID才能进行java方法的调用。
.可以使用JNIEnv的GetFieldID()/GetMethodID和GetStaticFieldID/GetStaticMethodID来取得相应的就fieldID和jmethodID。(jmethodID、jfieldID取得普通的属性和方法)
相关说明: clazz为指定的java类;name为指定的java类的属性或方法名称;其实这类似Reflect(反射),需要指定类跟属性、方法名来取得相应的类、属性、方法。而sign就是指定取得具体属性和方法的[参数类型和返参]类型。 |
GetMethodID也能够取得构造函数的jmethodID。创建一个java对象可以调用指定的构造方法。
如1:env->GetMethodID(data_Clazz, “<init>”, “()V”);
如2:package cn.itcast;
Public class TestNative{
public int property;
public int function(int foo, Date date, int[] arr);
Public void function( int i) {…}
Public void function( double d){…}
}
本地方法的实现:
JNIEXPORT void Java_Hello_test(JNIEnv* env, jobect obj) {
Jclass hello_clazz = env->GetObjectClass(obj);
jfieldID fieldID_prop = env->GetMethodID(hello_clazz, “property”, “I”);
jmethodID methodID_func = env->GetMethodID(hello_clazz, “function”,”(IL java/util/Date; [I)I”;//这里怕写错sign签名:可以使用JDK的javap命令,其使用方式跟javah一样。只不过这个目的是为了生成sign的全部签名。Javap常用的命令:javap –s –p[full className],-s表示输出签名信息; -p同-private,输出包括private访问权限的信息。
env->CallIntMethod(obj, methodID_func, 0L, NULL, NULL);//invoke
然后再c/c++代码中需要调用其中一个function方法的话…
//first:找到方法、属性所在的类。
Jclass clazz_TestNative = env->FindClass(“cn/itcast/TestNative”);
//second:取得jmethodID方可调用。
jmethodID id_func = env->GetMethodID(clazz_TestNative, “function”, ??);
//??这样写程序不知道怎么调用,因为有两个相同的方法。因此,引出sign的作用:
就是要用于指定要取得的属性或方法的类型。比如:(D)V 表示的是返回的函数返回值是void,参数是int。(即sign签名)
获得java属性后就可以设定相应的java属性值:取得了相应属性的jfield之后就可以用Set<TYPE>Field,Get<TYPE>Field,SetSatic<TYPE>Field,GetStatic<TYPE>Field等函数来对java属性进行操作。注:这里<TYPE>不是指代泛型之类的,TYPE一类的类型,如:Int、Short等。
Get<TYPE>Field方法有两个参数:jobject obj, jfieldID fieldID
Set<TYPE>Field方法有三个参数:jobject obj, jfieldID fieldID, jlong val
GetStatic<TYPE>Field方法有两个参数:jclass clazz, jfieldID fieldID
SetStatic<TYPE>Field方法有三个参数:jclass clazz, jfieldID fieldID, jint value
.怎么样获得数组属性呢?我们可以使用GetObjectField来取得数组类型的属性。
#include "stdafx.h"
#include <iostream>
#include "JavaNative.h" //<>这个表示调用系统库中的函数,””这个表示调用本地自己编写或其他人编写的库函数。
#include "jni_md.h"
using namespace std;
JNIEXPORT void JNICALL Java_JavaNative_sayhello(JNIEnv * env, jobject ogj)
{
jclass clazzNativeCode = env->GetObjectClass(ogj);//得到当前类class对象
jfieldID number_id = env->GetFieldID(clazzNativeCode, "number", "I");//获得jfieldID对象,其就是标识java类中定义了number的这个属性的ID。
jint number = env->GetIntField(ogj,number_id);//获得number的值。
cout<<number<<endl;
env->SetIntField(ogj, number_id, 100L);//设置number的值。
} 注意:调用java相关属性步骤的顺序<jclass ->jfieldID ->get/set…>
c/c++中调用java中的方法:
.JNIEnv提供了很多的Call<TYPE>Method跟CallStatic<TYPE>Method,还有CallNonvirtual<TYPE>Method函数,需要通过GetMethodID取得相应方法的jmethodID来传入到上述函数的参数中。
.调用实例方法的三种形式:
Call[Static]<TYPE>Method(jobect obj, jmethodID id, …);
Call[Static]<TYPE>MethodV(jobject obj, jmethodID id, va_list lst);
Call[Static]<TYPE>MethodA(jobject obj, jmethodID id, jvalue* v);
第一种是最常用的方式;第二种是当调用这个函数的时候有一个指向参数表的valist变量时使用的。第三种是当调用这个函数的时候有一个指向jvalue或jvalue数组的指针时用的。
例 1:jmethodID max_id = env->GetMethodID(clazzNativeCode, "max", "(DD)D");
jdouble maxValue = env->CallDoubleMethod(obj, max_id, 3.14, 3.15 );
cout<<maxValue<<endl;
例 2:java code:
public class Father {
Public void father() {…}
}
Public class Child extends Father {
Public void father() {…}//重写了Father类中的father方法。
}
如果执行: Father f = new Child();
f.father();这里是不会去调用父类的father方法的,而是调用子类的。如果硬要执行父类的该怎么办了(除了Father f = new Father()外),有什么方法。希望知道的人说下你们的建议。我在下面利用JNI中提供的一个方法CallNonvirtual()可以在本地代码中实现这种功能,但是这一种治标不治本的方法。(仅针对java编译器)
C++ code:
Class Father {
[Virtual]Void father() {…}
}
Class Child : Father {
Void father() {…}
}
如果执行: Father* f = new Child();
f->father();这里是会去调用父类的father方法的,而不会去调用子类的。但是c++中有个虚函数(virtual)的,可以让其执行子类的相应方法。看上面[]中的。
JNI中定义了CallNonvirtual<TYPE>Method方法就能够实现子类对象调用父类方法的功能。如果想要调用一个对象的父类的方法,而不是子类的方法,就可以使用CallNonvirtual<TYPE>Method方法。
要使用它,首先要取得父类及要调用的父类的方法的JmethodID,然后传入到这个函数就能够通过子类对象呼叫被复写(override)的父类方法。
public class JavaNative {
public int property;
public int number=10;
public native void sayhello();//调用本地的c/c++代码
// public void function(int foo, Date date, int[] arr) {
// System.out.println("i execute!");
// }
public double max(double num1, double num2) {
return num1>num2? num1: num2;
}
public Father p = new Child();
public static void main(String [] args) {
System.loadLibrary("nativeCode");//调用c/c++编写的dll文件
JavaNative na = new JavaNative();
na.sayhello();
}
}注意:Father和Child类没有粘贴进来。
jclass clazzNativeCode = env->GetObjectClass(ogj);
jfieldID id_p = env->GetFieldID(clazzNativeCode, "p", "LFather;");//p这个对象被声明在了java的NativeCode中了,所以先要获得这个属性的id。
jobject p = env->GetObjectField(ogj, id_p);//因为p是Father类的对象,所以此处定义jobject类型和getobjectField()返回相应的对象。
jclass clazz_father = env->FindClass("Father");//因为p要调用father方法,而这个方法在Father类中,子类只不过是继承了,所以此处必须要加载Father类到本地。
jmethodID id_Father_father = env->GetMethodID(clazz_father, "father", "()V");
env->CallVoidMethod(p,id_Father_father);//调用了子类复写的父类的father方法。
env->CallNonvirtualVoidMethod(p, clazz_father, id_Father_father);//调用了父类的father方法。
目的:
在c/c++本地代码中创建java的对象—NewObject
.使用函数jobject NewObject(jclass clazz, jmethodID methodID, ...)可以用来创建java对象
>.Clazz表示要指定的java类;jmethodID表示要指定的构造器的ID,…表示要指定具体参数,没有的话可以不填,即这个是可选项。GetMethodID能够取得构造器的jmethodID,如果传入方法名称设定为“<init>“就能够取得相应的构造器。
>.构造器的返回值类型签名始终为void。
例如:jclass clazz_date = env->FindClass(“java/../Naivecode”);
jmethodID mid_date = env->GetMethod(clazz_date,”<init>”,”()V”);
jobject now = env->NewObject(clazz_date, mid_date);生成好了动态链接库后,就需要调用本地方法,然后就需设置path,可以直接通过java代码设置,也可以手动设置(这种不可取,仅供测试)。Path的路径应该是dll文件的路径后面再加上分号。
. 另一方法:Java对象的创建—AllocObject
>.使用函数AllocObject可以根据传入的jclass创建一个java对象,但是其状态是非初始化的,在使用这个对象之前绝对要用CallNonvirtualVoidMethod来调用该jclass的构造器。这样可以延迟构造器的调用。(不常用)
例如:jclass clazz_str = env->FindClass(“java/lang/String”);
jmethodID methodID_str = env->GetMethodID(clazz_str, “<init>”, “([c)V”);
jobject string = env->AllocObject(clazz_str);
jcharArray arg = env->NewCharArray(4);
env->SetCharArrayRegion(arg, 0, 4, L”woshishui”);
env->CallNonvirtualVoidMethod(string, clazz_str, methodID_str, arg);
jclass clazz_this = env->GetObjectClass(obj);
//这里STATIC_STR为类中的一个属性。
jfieldID fieldID_this = env->GetStaticFieldID(clazz_this, “STATIC_STR”, “Ljava/lang/String;”);
env->SetStaticObjectField(clazz_str, field_str, string);
(篇一完)