JNI的静态注册的方法命名规范实现

JNI规范定义了从Java本机方法名到C本地库实现函数名如下:

1.前缀Java_

2.给定声明本机方法的类的内部形式的二进制名称:转义名称的结果。

3.下划线("_")。

4.转义的方法名称.

5.如果本机方法声明重载:两个下划线("_")后跟方法声明的转义参数描述符。

转义使每个字母数字ASCII字符(A-Za-z0-9)保持不变,并替换每个下表中的UTF-16代码单元n具有相应的转义序列。如果要命名转义包含一个代理项对,然后是高代理项代码单元和低代理项代码单位分别转义。转义的结果是一个仅由ASCII组成的字符串字符A-Za-z0-9和下划线。

UTF-16代码单元 转义序列
正斜杠(/,U+002F) _
下芯(_,U+005F) _1
分号(;,U+003B) _2
左方括号 ([, U+005B) _3
任意UTF-16代码单元\u_WXYZ不表示字母数字ASCII(A-Za-z0-9)正斜杠、下划线、分号或左方括号 _0wxyz,其中w、x、y和z是十六进制数字W、X、Y和Z的小写形式(例如,U+ABCD变为_0abcd)

请注意,转义序列可以安全地以_0、_1等开头,因为类和方法Java源代码中的名称从不以数字开头。然而,在不是从Java源代码生成的类文件。为了保留到本机方法名称的1:1映射,VM将生成的名称检查为跟随。如果从本机方法声明中转义任何前导字符串的过程(类或方法名,或参数类型)导致"0"、"1"、"2"或"3"字符从前体字符串开始,在结果中显示为不变或者 紧接在转义字符串开头的下划线(它将跟在下划线后面在完全组装的名称中),则称转义过程"失败"。 在这种情况下,不执行本机库搜索,并且尝试链接本机方法调用将抛出不满足的链接错误。

例如 包/my_class/方法 和 包/my/1类/方法

两者都映射到 Java_package_my_1class_method

为了解决这个潜在的冲突,我们只需要检查后面的字符/是数字0..3,或者如果插入的"_"分隔符后的第一个字符是数字0..3。如果遇到无效标识符,我们将重置stringStream并返回false。否则,字符串流包含映射的 name,我们返回true。

那么hotspot是实际代码实现的路径如下 hotspot/share/prims/nativeLookup.cpp

arduino 复制代码
address NativeLookup::lookup_entry(const methodHandle& method, TRAPS) {
  address entry = nullptr;
  // 计算Jni的方法名
  char* pure_name = pure_jni_name(method);
  if (pure_name == nullptr) {
    // 空判断
    return nullptr;
  }
  // 计算参数个数
  int args_size = 1      // 第一个参数是JNIEnv
     + (method->is_static() ? 1 : 0) // 方法是否是静态,会传this指针
                + method->size_of_parameters(); // 实际参数

  // 1) Try JNI short style
  entry = lookup_style(method, pure_name, "", args_size, true,  CHECK_NULL);
  if (entry != nullptr) return entry;

  //长Jni的方法名称
  char* long_name = long_jni_name(method);
  if (long_name == nullptr) {
    // JNI name mapping rejected this method so return
    // null to indicate UnsatisfiedLinkError should be thrown.
    return nullptr;
  }
  // 2) Try JNI long style
  entry = lookup_style(method, pure_name, long_name, args_size, true,  CHECK_NULL);
  if (entry != nullptr) return entry;

  // 3) Try JNI short style without os prefix/suffix
  entry = lookup_style(method, pure_name, "",        args_size, false, CHECK_NULL);
  if (entry != nullptr) return entry;

  // 4) Try JNI long style without os prefix/suffix
  entry = lookup_style(method, pure_name, long_name, args_size, false, CHECK_NULL);

  return entry; // null indicates not found
}

纯碎的Jni的方法名称,是Java_前綴 + 类全名转义下划线 + '_' + 方法全名转义下划线, 转成字符串。

arduino 复制代码
char* NativeLookup::pure_jni_name(const methodHandle& method) {
  stringStream st;
  // Prefix
  st.print("Java_");
  // Klass name
  if (!map_escaped_name_on(&st, method->klass_name())) {
    return nullptr;
  }
  st.print("_");
  // Method name
  if (!map_escaped_name_on(&st, method->name())) {
    return nullptr;
  }
  return st.as_string();
}

下面这段代码是转义类名称和方法名称的转义处理方法。

rust 复制代码
static bool map_escaped_name_on(stringStream* st, Symbol* name, int begin, int end) {
  char* bytes = (char*)name->bytes() + begin;
  char* end_bytes = (char*)name->bytes() + end;
  bool check_escape_char = true; // initially true as first character here follows '_'
  while (bytes < end_bytes) {
    jchar c;
    bytes = UTF8::next(bytes, &c);
    if (c <= 0x7f && isalnum(c)) {
      if (check_escape_char && (c >= '0' && c <= '3')) {
        // This is a non-Java identifier and we won't escape it to
        // ensure no name collisions with a Java identifier.
        if (log_is_enabled(Debug, jni, resolve)) {
          ResourceMark rm;
    log_debug(jni, resolve)("[Lookup of native method with non-Java identifier rejected: %s]", name->as_C_string());
        }
        st->reset();  // restore to "" on error
        return false;
      }
      st->put((char) c);
      check_escape_char = false;
    } else {
      check_escape_char = false;
      if (c == '_') st->print("_1");
      else if (c == '/') {
        st->print("_");
        // Following a / we must have non-escape character
        check_escape_char = true;
      }
      else if (c == ';') st->print("_2");
      else if (c == '[') st->print("_3");
      else               st->print("_%.5x", c);
    }
  }
  return true;
}

lookup_style是根据Jni的符号,调用操作系统的库的os::dll_lookup函数,寻找动态链接库中函数的入口地址address。

scss 复制代码
address NativeLookup::lookup_style(const methodHandle& method, char* pure_name, const char* long_name, int args_size, bool os_style, TRAPS) {
  address entry;
  const char* jni_name = compute_complete_jni_name(pure_name, long_name, args_size, os_style);

  // If the loader is null we have a system class, so we attempt a lookup in
  // the native Java library. This takes care of any bootstrapping problems.
  // Note: It is critical for bootstrapping that Java_java_lang_ClassLoader_findNative
  // gets found the first time around - otherwise an infinite loop can occur. This is
  // another VM/library dependency
  Handle loader(THREAD, method->method_holder()->class_loader());
  if (loader.is_null()) {
    entry = lookup_special_native(jni_name);
    if (entry == nullptr) {
       entry = (address) os::dll_lookup(os::native_java_library(), jni_name);
    }
    if (entry != nullptr) {
      return entry;
    }
  }
  // Otherwise call static method findNative in ClassLoader
  Klass*   klass = vmClasses::ClassLoader_klass();
  Handle name_arg = java_lang_String::create_from_str(jni_name, CHECK_NULL);

  JavaValue result(T_LONG);
  JavaCalls::call_static(&result,
                         klass,
                         vmSymbols::findNative_name(),
                         vmSymbols::classloader_string_long_signature(),
                         // Arguments
                         loader,
                         name_arg,
                         CHECK_NULL);
  entry = (address) (intptr_t) result.get_jlong();

  if (entry == nullptr) {
    // findNative didn't find it, if there are any agent libraries look in them
    JvmtiAgentList::Iterator it = JvmtiAgentList::agents();
    while (it.has_next()) {
      entry = (address)os::dll_lookup(it.next()->os_lib(), jni_name);
      if (entry != nullptr) {
        return entry;
      }
    }
  }
  return entry;
}

总结

本次主要对openjdk的jni的实现方法命名实现做了简要的分析。

相关推荐
zyt.com4 分钟前
线程池总结
jvm
pjx9874 小时前
JVM 语言与生态
jvm
猿究院---王某人5 小时前
Java 内存模型(JMM)
java·开发语言·jvm
爱棋笑谦8 小时前
JVM基础
jvm
懒洋洋大魔王18 小时前
7.Java高级编程 多线程
java·开发语言·jvm
只吹45°风18 小时前
JVM-类加载器的双亲委派模型详解
jvm·类加载器·双亲委派
五味香21 小时前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
longlongqin1 天前
JVM 虚拟机的编译器、类加载过程、类加载器有哪些?
jvm
niceffking1 天前
JVM HotSpot 虚拟机: 对象的创建, 内存布局和访问定位
java·jvm
刘大猫.1 天前
Arthas dashboard(当前系统的实时数据面板)
jvm·arthas·dashboard·当前系统的实时数据面板·dashboard命令·arthas命令