logo
Методичка Java

Подключение внешних библиотек dll.“Родные” (native) методы*

*- данный параграф приводится в ознакомительных целях

Для прикладного программирования средств Java в подавляющем большинстве случаев хватает. Однако иногда возникает необходимость подключить к программе ряд системных вызовов. Либо обеспечить доступ к библиотекам, написанным на других языках программирования. Для таких целей в Java используются методы, объявленные с модификатором native –“родной”. Это слово означает, что при выполнении метода производится вызов “родного” для конкретной платформы двоичного кода, а не платформо-независимого байт-кода как во всех других случаях. Заголовок “родного” метода описывается в классе Java, а его реализация осуществляется на каком-либо из языков программирования, позволяющих создавать динамически подключаемые библиотеки (DLL – Dynamic Link Library под Windows, Shared Objects под UNIX-образными операционными системами).

Правило для объявления и реализации таких методов носит название JNI – Java Native Interface.

Объявление “родного” метода в Java имеет вид

Модификаторы native ВозвращаемыйТип имяМетода(список параметров);

Тело “родного” метода не задаётся – оно является внешним и загружается в память компьютера с помощью загрузки той библиотеки, из которой этот метод должен вызываться:

System.loadLibrary(“ИмяБиблиотеки”);

При этом имя библиотеки задаётся без пути и без расширения. Например, если под Windows библиотека имеет имя myLib.dll, или под UNIX или Linux имеет имя myLib.so , надо указывать System.loadLibrary(“myLib”);

В случае, если файла не найдено, возбуждается непроверяемая исключительная ситуация UnsatisfiedLinkError.

Если требуется указать имя библиотеки с путём, применяется вызов

System.load (“ИмяБиблиотекиСПутём”);

Который во всём остальном абсолютно аналогичен вызову loadLibrary.

После того, как библиотека загружена, с точки зрения использования в программе вызов “родного” метода ничем не отличается от вызова любого другого метода.

Для создания библиотеки с методами, предназначенными для работы в качестве “родных”, обычно используется язык С++. В JDK существует утилита javah.exe, предназначенная для создания заголовков C++ из скомпилированных классов Java. Покажем, как ей пользоваться, на примере класса ClassWithNativeMethod. Зададим его

в пакете нашего приложения:

package java_example_pkg;

public class ClassWithNativeMethod {

/** Creates a new instance of ClassWithNativeMethod */

public ClassWithNativeMethod() {

}

public native void myNativeMethod();

}

Для того, чтобы воспользоваться утилитой javah, скомпилируем проект и перейдём в папку build\classes . В ней будут располагаться папка с пакетом нашего приложения java_example_pkg и папка META-INF. В режиме командной строки выполним команду

javah.exe java_example_pkg.ClassWithNativeMethod

- задавать имя класса необходимо с полной квалификацией, то есть с указанием перед ним имени пакета. В результате в папке появится файл java_example_pkg_ClassWithNativeMethod.h со следующим содержимым:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class java_example_pkg_ClassWithNativeMethod */

#ifndef _Included_java_example_pkg_ClassWithNativeMethod

#define _Included_java_example_pkg_ClassWithNativeMethod

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: java_example_pkg_ClassWithNativeMethod

* Method: myNativeMethod

* Signature: ()V

*/

JNIEXPORT void JNICALL Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod

(JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif

#endif

Функция Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod(JNIEnv *, jobject), написанная на C++, должна обеспечивать реализацию метода myNativeMethod() в классе Java. Имя функции C++ состоит из: префикса Java, разделителя “_”, модифицированного имени пакета (знаки подчёркивания “_” заменяются на “_1”), разделителя “_”, имени класса, разделителя “_”, имени “родного” метода.

Первый параметр JNIEnv * в функции C++ обеспечивает доступ “родного” кода к параметрам и объектам, передающимся из функции C++ в Java. В частности, для доступа к стеку. Второй параметр, jobject , – ссылка на экземпляр класса, в котором задан “родной” метод, для методов объекта, и jclass – ссылка на сам класс – для методов класса. В языке C++ нет ссылок, но в Java все переменные объектного типа являются ссылками. Соответственно, второй параметр отождествляется с этой переменной.

Если в “родном” методе имеются параметры, то список параметров функции C++ расширяется. Например, если мы зададим метод

public native int myNativeMethod(int i);

то список параметров функции C++ станет

(JNIEnv *, jobject, jint)

А тип функции станет jint вместо void.

Соответствие типов Java и C++:

Тип Java

Тип JNI (C++)

Характеристика типа JNI

boolean

jboolean

1 байт, беззнаковый

byte

jbyte

1 байт

char

jchar

2 байта, беззнаковый

short

jshort

2 байта

int

jint

4 байта

long

jlong

8 байт

float

jfloat

4 байта

double

jdouble

8 байт

void

void

-

Object

jobject

Базовый для остальных классов

Class

jclass

Ссылка на класс Java

String

jstring

Строки Java

массив

jarray

Базовый для классов массивов

Object[]

jobjectArray

Массив объектов

boolean[]

jbooleanArray

Массив булевских значений

byte[]

jbyteArray

Массив байт (знаковых значений длиной в байт)

char[]

jcharArray

Массив кодов символов

short[]

jshortArray

Массив коротких целых

int[]

jintArray

Массив целых

long[]

jlongArray

Массив длинных целых

float[]

jfloatArray

Массив значений float

double[]

jdoubleArray

Массив значений double

Throwable

jthrowable

Обработчик исключительных ситуаций

В реализации метода требуется объявить переменные. Например, если мы будем вычислять квадрат переданного в метод значения и возвращать в качестве результата значение параметра, возведённое в квадрат (пример чисто учебный), код реализации функции на C++ будет выглядеть так:

#include ”java_example_pkg_ClassWithNativeMethod.h”

JNIEXPORT jint JNICALL Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod

(JNIEnv *env, jobject obj, jint i ){

return i*i

};

Отметим, что при работе со строками и массивами для получения и передачи параметров требуется использовать переменную env. Например, получение длины целого массива, переданного в переменную jintArray intArr, будет выглядеть так:

jsize length=(*env)->GetArrayLength(env, intArr);

Выделение памяти под переданный массив:

jint *intArrRef=(*env)->GetIntArrayElements(env, intArr,0);

Далее с массивом intArr можно работать как с обычным массивом C++.

Высвобождение памяти из-под массива:

(*env)->ReleaseIntArrayElements(env, intArr, intArrRef ,0);

Имеются аналогичные функции для доступа к элементам массивов всех примитивных типов:

GetBooleanArrayElements, GetByteArrayElements,…, GetDoubleArrayElements. Эти функции копируют содержимое массивов Java в новую область памяти, с которой и идёт работа в C++. Для массивов объектов имеется не только функция GetObjectArrayElement, но и SetObjectArrayElement – для получения и изменения отдельных элементов таких массивов.

Строка Java jstring s преобразуется в массив символов C++ так:

const char *sRef=(*env)->GetStringUTFChars(env,s,0);

Её длина находится как int s_len=strlen(sRef);

Высвобождается из памяти как

(*env)->ReleaseStringUTFChars(env,s,sRef);