分享

java本地接口调用基础篇一(共四篇)

 quandsu 2013-06-25

java代码访问本地代码(c/c++

JNI: Java Native Interface(调用c/c++/其他本地代码,该接口提供了javaos本地代码互相调用的功能。

  >首先在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++中表示属性和方法,JNIjNI.h头文件中定义了jfieldID,jmethodID类型来分别代表java端的属性和方法

.我们在访问,或是设置java属性的时候,首先就要先在本地代码取得代表该java属性的jfieldID,然后才能在本地代码进行java属性操作。同样的,我们需要呼叫java端的方法是,也是需要取得代表该方法jmethodID才能进行java方法的调用。

.可以使用JNIEnvGetFieldID()/GetMethodIDGetStaticFieldID/GetStaticMethodID来取得相应的就fieldIDjmethodID。(jmethodIDjfieldID取得普通的属性和方法)

 

相关说明:

clazz为指定的java类;name为指定的java类的属性或方法名称;其实这类似Reflect(反射),需要指定类跟属性、方法名来取得相应的类、属性、方法。而sign就是指定取得具体属性和方法的[参数类型和返参]类型。

 

 

 

 

 

 

GetMethodID也能够取得构造函数的jmethodID。创建一个java对象可以调用指定的构造方法。

1env->GetMethodID(data_Clazz, “<init>”, “()V”);

2package 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签名:可以使用JDKjavap命令,其使用方式跟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一类的类型,如:IntShort等。

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>MethodCallStatic<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变量时使用的。第三种是当调用这个函数的时候有一个指向jvaluejvalue数组的指针时用的。

例 1jmethodID max_id = env->GetMethodID(clazzNativeCode, "max", "(DD)D");

  jdouble maxValue = env->CallDoubleMethod(obj, max_id, 3.14, 3.15 );

  cout<<maxValue<<endl;

   例 2java 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();

  }

}注意:FatherChild类没有粘贴进来。

     jclass  clazzNativeCode = env->GetObjectClass(ogj);

  jfieldID id_p = env->GetFieldID(clazzNativeCode, "p", "LFather;");//p这个对象被声明在了javaNativeCode中了,所以先要获得这个属性的id

  jobject p = env->GetObjectField(ogj, id_p);//因为pFather类的对象,所以此处定义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);

(篇一完)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多