предисловие
В Java мы часто сталкиваемся с ситуациями, когда нам нужно вызывать собственные методы, и многие классы в базовой библиотеке Java также часто используют собственные методы.При использовании JNI собственные функции должны быть названы в соответствии с согласованным форматом. Не хотите писать длинные функции Имя должно зарегистрировать метод в JVM, вот как зарегистрировать локальный метод в JVM.
соглашение об именовании
Существуют соглашения для имен локальных методов в JVM, которым необходимо следовать при использовании JNI, а именноJava_<fully qualified class name>_method
.
Например, здесь написан класс Java для предоставления локального метода шифрования, где метод шифрования является локальным методом, а реализация находится в динамической библиотеке ByteCodeEncryptor, тогда его локальное соответствующее имя функцииJava_com_seaboat_bytecode_ByteCodeEncryptor_encrypt
.
package com.seaboat.bytecode;
public class ByteCodeEncryptor {
static{
System.loadLibrary("ByteCodeEncryptor");
}
public native static byte[] encrypt(byte[] text);
}
registerNatives
Если вы чувствуете, что соглашение об именах локальных функций громоздко, вы можете использовать метод registerNatives для регистрации локальных функций, чтобы вы могли называть функции по своему усмотрению. И считается, что функции, зарегистрированные в JVM через registerNatives, будут более эффективными во время выполнения, потому что поиск функции происходит быстрее.
Как зарегистрироваться
Есть два способа реализовать регистрацию собственного метода:
1. Статические блоки в Java
- Объявить класс Java
registerNatives
статический метод. - В нативном коде определите
Java_<fully qualified class name>_registerNatives
функция. - call перед вызовом других локальных функций
registerNatives
метод.
Например, для класса Object выполните в классе следующее:
private static native void registerNatives();
static {
registerNatives();
}
через местныеregisterNatives
Привяжите указанный собственный метод к указанной функции, например здесьhashCode
иclone
Родной метод привязан кJVM_IHashCode
иJVM_IHashCode
функция.
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
2. Используйте JNI_OnLoad
JNI_OnLoad
Функция выполняется в JVMSystem.loadLibrary
вызывается метод, поэтому его можно вызвать в этом методеRegisterNatives
Функция регистрирует локальную функцию. Регистрация нативных методов таким образом избавляет от необходимости объявлять их в классе Java.RegisterNatives
локальный способ регистрации.
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
int JNI_OnLoad(JavaVM* vm, void* reserved)
{
...
if ((*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0])) < 0)
{
return JNI_ERR;
}
...
}
что делает registerNatives
Структура JNINativeMethod определена для объявления методов и функций следующим образом: имя представляет имя собственного метода Java, сигнатура представляет сигнатуру метода, а fnPtr представляет указатель на функцию.
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
в основном зависит от(*env)->RegisterNatives
эта функция,
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}
Метод объявлен вJNINativeInterface_
В структуре структура содержит все объявления функций интерфейса JNI, а переменные структуры определены в JVM.jni_NativeInterface
для использования, только перечисленные здесьRegisterNatives
Объявление функции, остальные функции опущены.
struct JNINativeInterface_ {
...
jint (JNICALL *RegisterNatives) (JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
...
}
struct JNINativeInterface_ jni_NativeInterface = {
...
jni_RegisterNatives,
...
}
заглянутьjni_RegisterNatives
Знайте перед реализацией функцииJNI_ENTRY
иJNI_END
макрос, эти два макроса извлекли общие части. вJNI_END
Проще, всего две закрывающие скобки.
#define JNI_ENTRY(result_type, header) JNI_ENTRY_NO_PRESERVE(result_type, header) WeakPreserveExceptionMark __wem(thread);
#define JNI_END } }
JNI_ENTRY
Основная логика:
- Получить объект указателя JavaThread текущего потока выполнения.
- Создайте объект ThreadInVMfromNative.
- TRACE_CALL здесь ничего не делает.
- Создайте объект HandleMarkCleaner.
- Назначьте поток THREAD в исключениях.
- Проверьте выравнивание стека.
- Создайте объект WeakPreserveExceptionMark.
#define JNI_ENTRY_NO_PRESERVE(result_type, header) \
extern "C" { \
result_type JNICALL header { \
JavaThread* thread=JavaThread::thread_from_jni_environment(env); \
assert( !VerifyJNIEnvThread || (thread == Thread::current()), "JNIEnv is only valid in same thread"); \
ThreadInVMfromNative __tiv(thread); \
debug_only(VMNativeEntryWrapper __vew;) \
VM_ENTRY_BASE(result_type, header, thread)
#define VM_ENTRY_BASE(result_type, header, thread) \
TRACE_CALL(result_type, header) \
HandleMarkCleaner __hm(thread); \
Thread* THREAD = thread; \
os::verify_stack_alignment();
Смотриjni_RegisterNatives
Конкретная реализация функции, логика такова:
- JNIWrapper используется для отладки.
- И HOTSPOT_JNI_REGISTERNATIVES_ENTRY, и DT_RETURN_MARK используются для dtrace.
- Создайте объект KlassHandle.
- Начните обход массива методов, чтобы получить соответствующее имя метода, сигнатуру метода, длину метода и другую информацию.
- Попробуйте выяснить, существуют ли уже соответствующее имя метода и сигнатура метода в символическом пуле констант, если они не найдены, сгенерируйте исключение, поскольку класс Java был добавлен в пул констант при нормальных обстоятельствах.
- перечислить
register_native
регистрация функций.
JNI_ENTRY(jint, jni_RegisterNatives(JNIEnv *env, jclass clazz,
const JNINativeMethod *methods,
jint nMethods))
JNIWrapper("RegisterNatives");
HOTSPOT_JNI_REGISTERNATIVES_ENTRY(env, clazz, (void *) methods, nMethods);
jint ret = 0;
DT_RETURN_MARK(RegisterNatives, jint, (const jint&)ret);
KlassHandle h_k(thread, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
for (int index = 0; index < nMethods; index++) {
const char* meth_name = methods[index].name;
const char* meth_sig = methods[index].signature;
int meth_name_len = (int)strlen(meth_name);
TempNewSymbol name = SymbolTable::probe(meth_name, meth_name_len);
TempNewSymbol signature = SymbolTable::probe(meth_sig, (int)strlen(meth_sig));
if (name == NULL || signature == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s.%s%s not found", h_k()->external_name(), meth_name, meth_sig);
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), -1);
}
bool res = register_native(h_k, name, signature,
(address) methods[index].fnPtr, THREAD);
if (!res) {
ret = -1;
break;
}
}
return ret;
JNI_END
register_native
Логика функции следующая:
- Найдите указанный метод в соответствующем объекте Klass и сгенерируйте исключение, если он не существует.
- Если метод не объявлен как нативный, он сначала попытается найти нативный метод с префиксом, потому что префикс некоторых нативных методов может быть установлен в TI-агенте JVM, и если он все еще пуст, возникает исключение будет брошен в конце концов.
- назовите самое главное
set_native_function
функция, которая связывает функцию C++ с объектом Method. - Если указатель функции равен нулю, вызовите
clear_native_function
Очистите объекты собственного метода.
static bool register_native(KlassHandle k, Symbol* name, Symbol* signature, address entry, TRAPS) {
Method* method = k()->lookup_method(name, signature);
if (method == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s name or signature does not match",
Method::name_and_sig_as_C_string(k(), name, signature));
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
}
if (!method->is_native()) {
method = find_prefixed_native(k, name, signature, THREAD);
if (method == NULL) {
ResourceMark rm;
stringStream st;
st.print("Method %s is not declared as native",
Method::name_and_sig_as_C_string(k(), name, signature));
THROW_MSG_(vmSymbols::java_lang_NoSuchMethodError(), st.as_string(), false);
}
}
if (entry != NULL) {
method->set_native_function(entry,
Method::native_bind_event_is_interesting);
} else {
method->clear_native_function();
}
if (PrintJNIResolving) {
ResourceMark rm(THREAD);
tty->print_cr("[Registering JNI native method %s.%s]",
method->method_holder()->external_name(),
method->name()->as_C_string());
}
return true;
}
set_native_function
Логика функции такова:
- пройти через
native_function_addr
Функция получает локальный адрес функции, эта функция напрямуюreturn (address*) (this+1);
, вы можете видеть, что он напрямую использует адрес объекта метода + 1 в качестве адреса локальной функции. Это возможно, потому что при создании объекта Method будет оцениваться, является ли он собственным методом, и если да, то будут зарезервированы два дополнительных адреса для адреса локальной функции и сигнатуры метода. - Определить, равен ли уже локальный адрес функции указателю на функцию, если да, то это означает, что она была привязана и возвращается напрямую, в противном случае продолжается вниз.
- Событие отправляется, если Jvmti настроен на распространение связанных событий собственного метода.
- Назначьте указатель функции локальному адресу функции.
- GCC получает скомпилированный код функции.
void Method::set_native_function(address function, bool post_event_flag) {
assert(function != NULL, "use clear_native_function to unregister natives");
assert(!is_method_handle_intrinsic() || function == SharedRuntime::native_method_throw_unsatisfied_link_error_entry(), "");
address* native_function = native_function_addr();
address current = *native_function;
if (current == function) return;
if (post_event_flag && JvmtiExport::should_post_native_method_bind() &&
function != NULL) {
assert(function !=
SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
"post_event_flag mis-match");
JvmtiExport::post_native_method_bind(this, &function);
}
*native_function = function;
CompiledMethod* nm = code();
if (nm != NULL) {
nm->make_not_entrant();
}
}
------------- Рекомендуем прочитать ------------
Зачем писать «Анализ проектирования ядра Tomcat»
Резюме моей статьи за 2017 год — машинное обучение
Краткое изложение моих статей за 2017 год — Java и промежуточное ПО
Резюме моих статей 2017 года — глубокое обучение
Краткое изложение моих статей за 2017 год — исходный код JDK
Резюме моей статьи за 2017 год — обработка естественного языка
Резюме моих статей 2017 года — Java Concurrency
Поговори со мной, задай мне вопросы:
Добро пожаловать, чтобы следовать: