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取得普通的属性和方法)
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); (篇一完) |
|