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的实现方法命名实现做了简要的分析。