Родные методы

Хотя это случается редко, но все же иногда может понадобиться вызвать подпрограмму, написанную на языке, отличном от Java. Обычно такая подпрограмма существует в виде исполняемого кода для центрального процессора и среды, в которой вы работаете, то есть в виде родного (native) кода. Например, вы можете решить вызвать такую подпрограмму для достижения более высокой скорости выполнения. Или же вам может понадобиться работать со специализированной библиотекой от независимых поставщиков, например, с пакетом статистических расчетов. Однако поскольку Java-программы компилируются в байт-код, который затем интерпретируется (или компилируется "на лету") исполняющей системой Java, вызов подпрограмм родного кода из программ на Java может показаться невозможным. К счастью, это заключение ложно. В Java предусмотрено ключевое слово native, которое используется для объявления родных методов. Однажды объявленные, эти методы могут быть вызваны из вашей Java-программы точно так же, как вызываются любой другой метод Java.

Чтобы объявить родной метод, предварите его имя модификатором native, но не определяйте тело метода. Например:

public native int meth() ;

После объявления метода нужно собственно написать его и выполнить серию относительно сложных шагов, чтобы соединить его с кодом Java.

Большинство родных методов пишется на языке С. Механизм интеграции кода С с программой Java называется интерфейсом JNI (Java Native Interface). Подробное описание JNI выходит за рамки настоящей книги, но предложенное ниже краткое Описание дает достаточную информацию для большинства приложений.

На заметку! Точные шаги, которым вы должны следовать, варьируются между различными средами Java. Они также зависят от языка, который используется для реализации родных методов. Следующий пример ориентирован на среду Windows. Язык реализации метода — С.

Простейший способ понять процесс — исследовать его на примере. Для начала введите следующую короткую программу, которая использует native-метод по имени test ():

// Простой пример использования native-метода.
public class NativeDemo {
int i;
public static void main(String args[]) {
NativeDemo ob = new NativeDemo();
ob.i = 10;
System.out.println ("Это ob.i перед вызовом native-метода:" + ob.i);
ob.testO; // вызов native метода
System.out.println ("Это ob.i после вызова native-метода:" + ob.i);
}
// Объявление native-метода public native void test() ;
// загрузить DLL-библиотеку, содержащую статический метод
static {
System.loadLibrary("NativeDemo");
}
}

Обратите внимание, что метод test() объявлен как native и не имеет тела. Это метод, который будет вскоре реализован на С. Также посмотрите на блок static. Как уже объяснялось ранее в нашей книге, блок static выполняется только однажды — при запуске программы (или, точнее говоря, при первой загрузке ее класса). В этом случае он используется для загрузки динамической библиотеки, которая содержит реализацию метода test (). (Вскоре вы увидите, как создавать такую библиотеку.)

Библиотека загружается методом loadLibrary (), который является частью класса System. Его общая форма такова:

static void loadLibrary(String filename)

Здесь filename — строка, которая специфицирует имя файла, содержащего библиотеку. Для среды Windows предполагается, что файл имеет расширение . DLL.

После ввода текста программы скомпилируйте ее, чтобы получить NativeDemo .class. Далее вы должны использовать javah.exe, чтобы сгенерировать один заголовочный файл: NativeDemo. h (javah.exe включена в JDK). Вы включите NativeDemo. h в свою реализацию test (). Чтобы получить NativeDemo. h, выполните следующую команду:

javah -jni NativeDemo

Эта команда сгенерирует файл по имени NativeDemo.h. Этот файл должен быть включен в С-файл, реализующий test (). Вывод, сгенерированный этой командой, показан ниже.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class NativeDemo */
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef cplusplus
extern "C" {
#endif
/*
* Class: NativeDemo
* Method: test
* Signature: ()V */
JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *, jobject);
tifdef cplusplus
}
#endif
#endif

Обратите особое внимание на следующую строку, которая определяет прототип для создаваемой вами функции test ():

JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *, jobject);

Отметим, что именем функции будет Java_NativeDemo_test (). Вы должны использовать его в качестве имени native-функции, которую вы реализуете. То есть вместо написания на языке С функции test () вы создаете Java_NativeDemo_test (). Компонент NativeDemo в префиксе добавляется, поскольку он идентифицирует, что метод test () является членом класса NativeDemo. Помните, что другой класс может объявить свой собственный метод test (), абсолютно отличный от того, что объявлен в NativeDemo. Включение имени класса в префикс обеспечивает возможность различения разных версий. Основное правило — родным функциям присваивается имя, чей префикс включает имя класса, в котором он объявлен.

После генерации необходимого заголовочного файла вы можете написать свою реализацию метода test () и сохранить ее в файле NativeDemo.с:

/* Этот файл содержит С-версию метода test() .*/
#include
#include "NativeDemo.h"
#include
JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *env, jobject obj) {
jclass els; jfieldID fid; j int i ;
printf("Запуск native-метода.\n") ;
els = (*env)->GetObjectClass (env, obj);
fid = (*env)->GetFieldID(env, els, "i", "I");
if(fid == 0) {
printf("Невозможно получить поле id.\n");
return;
}
i = (*env)->GetIntField(env, obj, fid);
printf("i = %d\n", i) ;
(*env)->SetIntField (env, obj, fid, 2*i) ;
printf("Завершение native-метода.\n");
}

Отметим, что этот файл включает jni.h, содержащий интерфейсную информацию. Этот файл поставляется вместе с компилятором Java. Напомним, что заголовок NativeDemo .h ранее сгенерирован javah. В этой функции метод GetOb j ectClass () используется для получения С-структуры, имеющей информацию о классе NativeDemo. Метод GetFieldID () возвращает С-струк-туру с информацией о поле класса по имени i. GetlntField () извлекает исходное значение этого поля. SetlntField () сохраняет обновленное значение этого поля (см. в файле jni .h дополнительные методы, которые управляют другими типами данных).

После создания NativeDemo.с вы должны скомпилировать его и создать DLL-библиотеку. Чтобы сделать это с помощью компилятора Microsoft C/C++, используйте следующую командную строку (возможно, понадобится указать путь к jni.h и его подчиненному файлу jni_md.h):

Cl /LD NativeDemo.с

Эта команда создаст файл NativeDemo.dll. Посте того как только все будет сделано, вы сможете запустить Java-программу, которая выдаст такой результат:

Это ob.i перед вызовом native-метода:
10 Запуск native-метода, i = 10
Завершение native-метода.
Это ob.i после вызова native-метода: 20