在 JNI 中提供了哪几种数据类型,是如何实现的?

Java 语言上定义了不同的数据类型,比如有基础类型int、double等等,还有所有类的父类Object等,这些都是 Java 层面的类型,而使用本地方法的处理过程需要有它们对应的类型。

除了基础的类型映射外,Java 层其他对象类型为引用类型,那么本地方法对应的是 jobject 类型,另外,它还会派生出经常用的一些子类,比如 jstring、jclass 等等,具体如下,


/*
 * Reference types, in C++
 */
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
 
typedef _jobject*       jobject;
typedef _jclass*        jclass;
typedef _jstring*       jstring;
typedef _jarray*        jarray;
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;
typedef _jthrowable*    jthrowable;
typedef _jobject*       jweak;

可以看到定义了_jobject类,该类为空类,而其他的类包括_jclass _jthrowable _jstring _jarray都是继承_jobject类。此外,数组类型还派生出了9个子类,分别对应基础类型数组和引用类型数组。

typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;

前面定义完类后再定义指针别名,这里的就是本地方法的类型了。另外,这些都是 C++ 的定义,如果是 C 编译器则会使用 struct 来定义 _jobject,而非 class。

上面的引用类型定义为空类,这里了解下C++的空类,通常我们要定义一个空类可以如下两种方式,

class Empty{}
struct Empty{}

经过上述定义后的空类,它的大小为1,但是一个空类啥都没有的话它有什么用呢?其实它可以用来区分不同的对象,空类定义的不同对象拥有不同的地址,使用new操作出来的对象也有不同的指针,而且空类也能区分不同的类别。

所以有了这些类型映射后我们是怎么联系起来使用的呢?其实很简单,答案就是进行指针转换,前面提到过 Java 层的对象在 JVM 中是有一定的数据结构的,即用 oop 来表示对象指针,那么 jobject 可以作如下转换,其中 handle 即为 jobject 类型。

oop result = reinterpret_cast<oop>(handle);
转换成 oop 后要进一步处理就很方便了,比如想要获取一些类相关的元数据时可以使用其中的 klass 来获取。

https://www.mianshigee.com/note/detail/78710enq/



在 JNI 用空类来实现 Java 中的类型,对象、方法,主要原因是为了不暴露 JVM 内部的实现细节,在 Jvm 内部用 klass 表示类,用 oop 表示对象指针,但是这个是JVM内部才可以直接使用,在 JNI 中 只是用 jobject 可以指向 oop,但是并不会暴露 oop的内部结构
当要访问 oop 时,需要通过 env中提供的用 api来实现,而 ENV 本身就是 JVM的一部分,在 JVM 启动是进行初始化,在 Env API 中会将 jobject 转成 oop,完成实际功能

/Users/xinyu.jiang/Project/sourcecode/openjdk/openjdk8/hotspot/src/share/vm/oops/oopsHierarchy.hpp

typedef class oopDesc*                            oop;

typedef class   instanceOopDesc*            instanceOop;

typedef class   arrayOopDesc*                    arrayOop;

typedef class     objArrayOopDesc*            objArrayOop;

typedef class     typeArrayOopDesc*            typeArrayOop;/Users/xinyu.jiang/Project/sourcecode/openjdk/openjdk8/hotspot/src/share/vm/oops/oopsHierarchy.hpp

typedef class oopDesc*                            oop;

typedef class   instanceOopDesc*            instanceOop;

typedef class   arrayOopDesc*                    arrayOop;

typedef class     objArrayOopDesc*            objArrayOop;

typedef class     typeArrayOopDesc*            typeArrayOop;

// JNI调用方法的实现

JNI_ENTRY(void, jni_CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID, ...))

  JNIWrapper("CallVoidMethod");

\#ifndef USDT2

  DTRACE_PROBE3(hotspot_jni, CallVoidMethod__entry, env, obj, methodID);

\#else /* USDT2 */

  HOTSPOT_JNI_CALLVOIDMETHOD_ENTRY(

​                                   env, obj, (uintptr_t) methodID);

\#endif /* USDT2 */

  DT_VOID_RETURN_MARK(CallVoidMethod);



  va_list args;

  va_start(args, methodID);

  JavaValue jvalue(T_VOID);

  JNI_ArgumentPusherVaArg ap(methodID, args);

  jni_invoke_nonstatic(env, &jvalue, obj, JNI_VIRTUAL, methodID, &ap, CHECK);

  va_end(args);

JNI_END

static void jni_invoke_nonstatic(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
  oop recv = JNIHandles::resolve(receiver);
  if (recv == NULL) {
    THROW(vmSymbols::java_lang_NullPointerException());
  }
  Handle h_recv(THREAD, recv);

  int number_of_parameters;
  Method* selected_method;
  {
    Method* m = Method::resolve_jmethod_id(method_id);
    number_of_parameters = m->size_of_parameters();
    Klass* holder = m->method_holder();
    if (!(holder)->is_interface()) {
      // non-interface call -- for that little speed boost, don't handlize
      debug_only(No_Safepoint_Verifier nosafepoint;)
      if (call_type == JNI_VIRTUAL) {
        // jni_GetMethodID makes sure class is linked and initialized
        // so m should have a valid vtable index.
        assert(!m->has_itable_index(), "");
        int vtbl_index = m->vtable_index();
        if (vtbl_index != Method::nonvirtual_vtable_index) {
          Klass* k = h_recv->klass();
          // k might be an arrayKlassOop but all vtables start at
          // the same place. The cast is to avoid virtual call and assertion.
          InstanceKlass *ik = (InstanceKlass*)k;
          selected_method = ik->method_at_vtable(vtbl_index);
        } else {
          // final method
          selected_method = m;
        }
      } else {
        // JNI_NONVIRTUAL call
        selected_method = m;
      }
    } else {
      // interface call
      KlassHandle h_holder(THREAD, holder);

      int itbl_index = m->itable_index();
      Klass* k = h_recv->klass();
      selected_method = InstanceKlass::cast(k)->method_at_itable(h_holder(), itbl_index, CHECK);
    }
  }

  methodHandle method(THREAD, selected_method);

  // Create object to hold arguments for the JavaCall, and associate it with
  // the jni parser
  ResourceMark rm(THREAD);
  JavaCallArguments java_args(number_of_parameters);
  args->set_java_argument_object(&java_args);

  // handle arguments
  assert(!method->is_static(), "method should not be static");
  args->push_receiver(h_recv); // Push jobject handle

  // Fill out JavaCallArguments object
  args->iterate( Fingerprinter(method).fingerprint() );
  // Initialize result type
  result->set_type(args->get_ret_type());

  // Invoke the method. Result is returned as oop.
  JavaCalls::call(result, method, &java_args, CHECK);

  // Convert result
  if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
    result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
  }
}

以上是 JNI中方法调用的内部实现,其中 jobject --> oop 的装换如下

  oop recv = JNIHandles::resolve(receiver);
inline oop JNIHandles::resolve(jobject handle) {
  oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);
  assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");
  assert(result != badJNIHandle, "Pointing to zapped jni handle area");
  return result;
};

直接将 jobject 的空类指针强转成 oop 指针,至于强转后规则和意义参考 https://www.cnblogs.com/sunddenly/articles/16055307.html

#include <iostream>
using namespace std;

class _jobject {};
typedef _jobject *jobject;


class oopDesc {
   public:
      double length;   // 长度
      double breadth;  // 宽度
      double height;   // 高度
      // 成员函数声明
      double get(void);
      void set( double len, double bre, double hei );
};

// 成员函数定义
double oopDesc::get(void)
{
    return length * breadth * height;
}
 
void oopDesc::set( double len, double bre, double hei)
{
    length = len;
    breadth = bre;
    height = hei;
}

typedef class oopDesc*                            oop;


oop resolve_non_null(jobject handle) {
  oop result = *(oop*)handle;
  return result;
};


int main() {
    oop old = new oopDesc();
    old->breadth=10009;
    cout << "Hello Vscode2" << endl;
    jobject obj = (jobject)old;
    oop o = resolve_non_null(obj);
    // int b = o->breadth;
    cout << old->breadth << endl;
    return 0;
}
posted @ 2022-03-23 21:22  TomStudio  阅读(26)  评论(0)    收藏  举报