предисловие: Прочитав исходный код JDK, можно увидеть, что метод registernatives() существует в общем классе, таком как класс Object, класс Class, класс ClassLoader. Определение его метода следующее:
private static native void registerNatives();
static {
registerNatives();
}
Видно, что registerNatives() — это нативный метод, и его реализация реализована на языке C/C++.Чтобы глубоко понять значение и функции метода registerNatives, вам сначала нужно хорошо понять родное ключевое слово в Java ( студенты с родственными основами могут напрямую игнорировать этот раздел).
1. Native Method
Объяснение официальной документации ключевого слова native Method выглядит следующим образом:
A native method is a Java method whose implementation is provided by non-java code.
Нативные методы — это методы, реализованные языками, отличными от Java. Обычно собственный метод в Java обычно реализуется на C/C++, а Java предоставляет API для связи с другими языками, а именно JNI (Java Native Interface). Если вы хотите использовать Java для вызова функций на других языках, вы должны следовать соглашениям JNI API.
1.1 Нативные правила использования
- Собственный идентификатор можно использовать с другими идентификаторами, за исключением того, что его нельзя использовать с абстрактным;
- Методы нативных методов могут вернуть любой тип Java, включая неманные типы или контроль исключения;
- Если класс содержит нативный метод метода унаследован, подкласс наследует метод нативного метода, вы также можете использовать язык переопределения Java этот метод;
- Если собственный метод идентифицируется с помощью fianl, его нельзя переопределить после того, как он унаследован.
1.2 Этапы реализации нативного метода
- объявить в java
native()
метод, затем скомпилируйте;- Используйте команду javah для создания файла .h;
- Напишите файл .CPP для реализации нативного метода экспорта, который должен включать в себя файл .h, сгенерированный на втором этапе (обратите внимание, что его необходимо включить файл jni.h с JDK);
- Собственный код компилируется в динамически подключаемую библиотеку (Windows: *dll, linux/unix: *so, mac os x: *jnilib...);
- В Java метод System.loadLibrary() используется для загрузки файла библиотеки динамической компоновки, созданного на четвертом шаге, и на этом весь процесс заканчивается.
Вот лишь краткое введение в соответствующие точки знаний родного языка.Если вам нужна более подробная информация, вы можете проверить соответствующую информацию самостоятельно.
Прочитав приведенную выше информацию, вы можете увидеть, чтоregisterNatives()
Методы реализованы на других языках. Угадывание по названию методаregisterNatives()
Роль заключается в регистрации нативных методов. Вызов registerNatives() в статическом блоке кода приводит к регистрации класса при его загрузке. Увидев это, я верю, что у студентов обязательно возникнут сомнения в душе, зачем нужно регистрировать Native метод?registerNatives()
Как зарегистрировать локальный метод?
Ознакомьтесь с «Руководством и спецификацией программиста по собственному интерфейсу Java» здесь для следующего фрагмента.
Before an application executes a native method it goes through a two-step process to load the native library containing the native method implementation and then link to the native method implementation:
1.System.loadLibrary locates and loads the named native library. For example, System.loadLibrary("foo") may cause foo.dll to be loaded on Win32.
2.The virtual machine locates the native method implementation in one of the loaded native libraries. For example, a Foo.g native method call requires locating and linking the native function Java_Foo_g, which may reside in foo.dll.
Согласно приведенному выше объяснению, перед вызовом нативного метода Java выполнит два шага для загрузки библиотеки реализации нативного метода.Первый шаг — использовать System.loadLibrary() для загрузки динамического файла, содержащего реализацию нативного метода, в память; второй Первый шаг заключается в том, что когда Java-программе необходимо вызвать собственный метод, виртуальная машина находит и связывает собственный метод в загруженном динамическом файле, чтобы можно было выполнить собственный метод. Ниже я подробно объясню смысл этого отрывка, реализуя демонстрацию нативного метода.
2. Используйте JNI для реализации нативных методов
public class ByteCodeEncryptor {
public native static byte[] encrypt(byte[] text);
static {
System.loadLibrary("byteCodeEncryptor.dll");
}
public static void main(String args[]) {
String test = "hello world";
System.out.println(encrypt(test.getBytes()));
}
}
分析:以上代码的作用是使用本地方法encrypt()实现加密;
Используйте команду javac для создания файла заголовка (.h) указанного выше исходного файла Java.
#include <jni.h>
#ifndef _Included_com_seaboat_bytecode_ByteCodeEncryptor
#define _Included_com_seaboat_bytecode_ByteCodeEncryptor
#ifdef __cplusplus
extern "C" {
#endif
// 在 Windows 中编译 dll 动态库规定,如果动态库中的函数要被外部调用,需要在函数声明中添加__declspec(dllexport)标识,表示将该函数导出在外部可以调用
JNIEXPORT jbyteArray JNICALL Java_com_individual_thread_register_ByteCodeEncryptor_encrypt
(JNIEnv *, jclass, jbyteArray);
#ifdef __cplusplus
}
#endif
#endif
Реализовать метод шифрования в заголовочном файле
#include "com_individual_thread_register_ByteCodeEncryptor.h"
#include "jni.h"
void encode(char *str)
{
unsigned int m = strlen(str);
for (int i = 0; i < m; i++)
{
str[i] = str[i]+4;
}
}
extern"C" JNIEXPORT jbyteArray JNICALL
Java_com_individual_thread_register_ByteCodeEncryptor_encrypt(JNIEnv *env, jclass cla,jbyteArray text)
{
char* dst = (char*)env->GetByteArrayElements(text, 0);
encode(dst);
env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst);
return text;
}
说明:
第一个参数:JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM 函数表的指针,函数表中的每一个入口指向一个JNI 函数,每个函数用于访问JVM中特定的数据结构。
第二个参数:调用Java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是 jobject,如果是静态方法,则是 jclass。
第三个参数:Java 对应 JNI 中的数据类型,Java 中 String 类型对应 JNI 的 jstring 类型,(Java于JNI数据类型的映射关系)。
函数返回值类型:夹在 JNIEXPORT 和 JNICALL 宏中间的 jbyteArray,表示函数的返回值类型,对应Java的byte[]类型。
在Java中调用encrypt()本地方法即可得到加密后的结果。
Уведомление:Для реализации нативного метода требуется формат имени метода локального исходного файла C/C++: Java_полное имя класса_имя метода, имя пакета.
номер, с_
Представляет _method, поскольку JVM имеет соответствующие правила для имен локальных методов, которым необходимо следовать при использовании JNI.
Например, класс Java написан выше для предоставления локального метода шифрования, где метод шифрования является локальным методом, а реализация находится в динамической библиотеке byteCodeEncryptor, тогда его локальное соответствующее имя функции — Java_com_individual_thread_register_ByteCodeEncryptor_encrypt.
3. Используйте функцию JNI_onload для реализации собственного метода.
Чтобы реализовать собственный метод с использованием JNI_onload, вам сначала нужно знать функцию JNI_OnLoad. Функция JNI_OnLoad вызывается, когда JVM выполняет метод System.loadLibrary, и в этом методе может быть вызвана функция registerNatives() для регистрации локальных функций. Во-первых, шаги использования JNI_OnLoad:
- Определить и реализовать локальный метод, объявленный в соответствующей java в файле c/c++, имя метода может быть произвольным, но тип параметра и количество параметров должны быть одинаковыми;
- Создает массив, объявляющий тип JNINativeMethod, значения которого являются нативными методами, которым необходимо динамически загружать сопоставление.
Ниже приведена реализация локального шифрования для метода JNI_onload.
3.1 Нативные методы, объявленные в Java
public class RegisterTest {
static {
System.loadLibrary("registerTest.dll");
}
public native static byte[] encrypt(byte[] text);
public static void main(String args[]) {
String test = "hello world";
System.out.println(encrypt(test.getBytes()));
}
}
3.2 Реализуйте метод шифрования с помощью JNI_onload
#include <jni.h>
#include <string>
JNIEXPORT jbyteArray JNICALL encrypt
(JNIEnv *env, jclass cla, jbyteArray text) {
char* dst = (char*)env->GetByteArrayElements(text, 0);
unsigned int m = strlen(dst);
for (int i = 0; i < m; i++)
{
dst[i] = dst[i] + 4;
}
env->SetByteArrayRegion(text, 0, strlen(dst), (jbyte *)dst);
return text;
}
/**
第一个参数:encrypt 是java中的方法名称
第二个参数:([B)[B 是java中方法的签名,可以通过javap -s -p 类名.class 查看
第三个参数: (jstring *)encrypt (返回值类型)映射到native的方法名称
*/
static JNINativeMethod methods[] = {
{ "encrypt", "([B)[B", (jbyteArray *) encrypt}
};
static jclass myClass;
// 这里是java调用C的存在Native方法的类路径
static const char* const className = "com/individual/thread/register/RegisterTest";
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL; //注册时在JNIEnv中实现的,所以必须首先获取它
jint result = -1;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) { //从JavaVM获取JNIEnv,一般使用1.4的版本
return -1;
}
// 获取映射的java类
myClass = env->FindClass(className);
if (myClass == NULL)
{
printf("cannot get class:%s\n", className);
return -1;
}
// 通过RegisterNatives方法动态注册登记
if (env->RegisterNatives(myClass, methods, sizeof(methods) / sizeof(methods[0])) < 0)
{
printf("register native method failed!\n");
return -1;
}
return JNI_VERSION_1_4; //必须返回版本,否则加载会失败。
}
Файл .cpp можно упаковать в файл dll, и его можно успешно вызывать в Java. Результат использования JNI_onlad для реализации собственного метода такой же, как и у обычного JNI. Когда \C++ реализует собственный метод, его имя метода может не совпадать с именем метода формируемого заголовочного файла, для сравнения, второй способ более удобен. Кроме того, эффективность у них неодинакова. Ознакомьтесь с соответствующими материалами и используйте JNI_onload для более эффективной реализации нативных методов по следующим причинам:
- Используя традиционный метод JNI, когда класс Java вызывает локальную функцию, он обычно полагается на то, что виртуальная машина динамически находит локальную функцию в библиотеке ссылок (поэтому требуется формат именования определенного правила), и метод RegisterNatives второй метод используется для преобразования локальной функции.Регистрация на виртуальной машине позволяет более эффективно находить функции;
- Динамически настраивайте отношения сопоставления между собственными функциями и значениями функций Java во время выполнения, просто вызывая метод RegisterNatives() несколько раз и передавая различные параметры таблицы сопоставления. Например, статические методы JNINativeMethod[] = { { "зашифровать", "([B)[B", (jbyteArray *) зашифровать} } способ добиться сопоставления нескольких нативных методов.
На этом этапе можно в основном заключить, что причина, по которой второй метод используется в исходном коде Thread и Classload, заключается в том, что метод registerNatives() в JNI позволяет программе активно связывать локальный метод с вызывающей программой. программе необходимо вызвать локальный метод, она может вызывать ее напрямую, без необходимости повторного поиска и связывания виртуальной машиной. Для более четкого понимания того, как зарегистрироваться, вам необходимо прочитать исходный код C/C++ ниже.
4. Исходный код registerNatives()
Привяжите указанный собственный метод к указанной функции через registerNatives в таких классах, как Object и Thread, например, привяжите собственные методы hashCode и клонирования к функциям JVM_IHashCode и JVM_IHashCode.
Ниже приведена часть кода Thread.c в коде OpenJDK.
···
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield}
};
JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
···
Из приведенного выше кода видно, что целью кода registerNatives в Java является регистрация и привязка локальных методов, а метод — достижение динамической привязки через функцию JNI_onload.
Примечание:
- Структура JNINativeMethod
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
Приведенный выше код является исходным кодом в JNI.h, видимыйJNINativeMethod
Содержит три элемента: имя метода, сигнатуру метода и собственный указатель функции.Эта структура используется для описания сведений о методе, которые необходимо зарегистрировать.
2. Функции JNI_OnLoad и JNI_OnUnload
JNI_OnLoad():Эта функция вызывается, когда виртуальная машина выполняет функцию System.loadLibrary(xxx), и имеет две важные функции: указание версии JNI, сообщение виртуальной машине, какую версию JNI использует компонент (если функция JNI_OnLoad() не указана, виртуальная машина будет использовать последнюю версию по умолчанию.Старая версия JNI 1.1), если вы хотите использовать новую версию JNI, такую как JNI версии 1.4, вы должны вернуть константу JNI_VERSION_1_4 (эта константа определена в jni.h) с помощью Функция JNI_OnLoad() для уведомления параметров инициализации виртуальной машины, когда Когда виртуальная машина выполняет функцию System.loadLibrary(), она немедленно выполнит метод JNI_OnLoad(). Наиболее целесообразно инициализировать различные ресурсы в этом методе. вот в это время.
JNI_OnUnload():Он вызывается, когда ВМ освобождает компонент.Роль функции JNI_OnUnload() соответствует роли JNI_OnLoad().Поэтому в этом методе происходит очистка последствий, а действие по освобождению ресурса является наиболее подходящим.
4. Метод registernatives() в JNI — это метод предоставления собственного метода в JNI, который объявляет объявление в структуре JNINATIVEINTERFACE_.
5. Тип JNIENV фактически представляет среду Java, с помощью этого указателя JNIENV * можно управлять концом JAVA. Например, создается объект в классе Java, вызывается метод объекта Java, получаются свойства объекта Java и т. д. Указатель JNienv будет управляться JNI функцией реализации локального метода.
6. Приведенный выше пример кода выполняется в Eclipse+Visual Studio 2015. При запуске кода необходимо обратить внимание на конфигурацию среды.
5. Ссылки
- Учебные заметки JNI — регистрация нативных методов через RegisterNatives
- Подробное введение в роль метода registerNatives в классе Object.
- Два метода вызова jni javah и RegisterNatives
- Регистрационный родной код с использованием регистрируемых
Наконец, благодаря вышеупомянутым авторам, смогли поделиться такой хорошей статьей, которая заставила меня учиться.