Эта статья является
простым – в стиле “шаг за шагом” –
описанием использования JNI.
Минимум теории –
максимум практики.
Предполагается, что
читатель знаком с синтаксисом языка
программирования Java,
может самостоятельно установить jdk
и – если это необходимо – интегрированную
среду разработки и знает основы C/C++.
Используемые инструменты:
- jdk1.6
- Среда разработки на Java – IntelliJ Idea 10 Community Edition
- Компилятор для C – MIN GW
- Среда разработки для C/C++ - Code::Block 10
Операционная система:
Windows XP Professional SP3
Принятые соглашения:
- JDK_HOME – папка инсталляции jdk;
- JVM – виртуальная java машина
Введение.
JNI (Java
Native Interface)
представляет собой способ обеспечить
доступ коду, написанному на Java,
к низкоуровневым возможностям,
предоставляемым операционной системой.
Как правило, необходимость
в этом возникает в следующих случаях:
- Существует работоспособный и проверенный код, написанный на С/C++, переписывать который на Java нерентабельно;
- Необходимую функциональность невозможно или сложно реализовать средствами Java;
- Существует возможность улучшения быстродействия кода при переписывании его на C/C++.
Так или
иначе, если вы чувствуете необходимость
использовать платформенно-ориентированные
средства - эта статья для вас.
Шаг первый:
Написать java-класс,
содержащий native-методы.
В нашей
статье мы напишем класс, который,
используя JNI, печатает в
консоль.
Исходный
код класса:
package com.blogspot.toolkas;
public class Console {
public static native void print(String string);
}
Как видим,
компилятор не требует реализации метода,
предваряемого ключевым словом “native”
(скажем больше – нельзя скомпилировать
класс, нативные методы которого имеют
тело).
Если
попытаться воспользоваться данным
классом, например:
package com.blogspot.toolkas;
public class TestConsole {
public static void main(String[] args) {
Console.print("TEST");
}
}
То мы получим следующую ошибку:
Exception in thread "main" java.lang.UnsatisfiedLinkError:
com.blogspot.toolkas.Console.print(Ljava/lang/String;)V
at com.blogspot.toolkas.Console.print(Native Method)
at com.blogspot.toolkas.TestConsole.main(TestConsole.java:5)
которая
сообщает нам, что виртуальная машина
Java не смогла обнаружить
подходящей реализации метода Console.print.
Что ж, поможем
ей в этом=)
Шаг второй: реализовать native-метод платформенно ориентированным способом.
Шаг второй: реализовать native-метод платформенно ориентированным способом.
Для реализации
метода Console.print
будем использовать язык Си.
а) Используя
утилиту javah (которая
находится в папке { JDK_HOME
}/bin), сгенерируем заголовочный
файл из скомпилированного класса
ru.olympico.io.Console:
javah -classpath (класспас) -d (папка) com.blogspot.toolkas.Console
Не
забываем, что для корректной работы
утилиты javah в класспасе
должен находиться скомпилированный
класс Console (то есть после
внесения изменений в исходный код –
сначала компилируем – потом перегенерируем
заголовочный файл).
Так
или иначе, если утилита отработала
корректно, на выходе мы должны получить
что-то подобное:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ru_olympico_io_Console */
#ifndef _Included_com_blogspot_toolkas_io_Console
#define _Included_com_blogspot_toolkas_Console
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_blogspot_toolkas_Console
* Method: print
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_blogspot_toolkas_Console_print
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
Разберемся
немного в сгенерированном файле. Первое
что мы видим – это включение заголовочного
файла jni.h.
Этот
файл можно найти в папке {JDK_HOME}\include.
Данный заголовочный файл содержит
большое количество функций структур и
макросов, которые позволят нам
взаимодействовать с JVM
из нативного кода.
Дальше
идут знакомые каждому, кто писал код на
C или C++ -
так называемые стражи включения
(_Included_com_blogspot_toolkas_Console),
которые гарантируют, что тело заголовочного
файла не будет включено в исходный код
программы дважды и более раз.
И
наконец прототип функции, реализацию
которой мы и должны написать:
JNIEXPORT void JNICALL
Java_com_blogspot_toolkas_Console_print
(JNIEnv *, jclass, jstring);
Отметим
следующие интересные моменты:
- Чтобы обеспечить уникальность функции и однозначность связывания с методом, написанным на Java, генератор формирует имя функции, используя полный путь класса (включая пакет) и имя метода (случай перегруженных функций можете исследовать сами) ;
- Аргументы функции соответствуют аргументам метода (за исключением первых двух), поэтому можно спокойно их использовать;
- Указатель JNIEnv* представляет собой аргумент, обеспечивающий доступ к API JVM;
- jclass – это структура, которая представляет собой класс, содержащий вызываемый метод;
- существует определенное соответствие между типом передаваемых аргументов в Java и в C:
Язык
Java Язык C
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
String jstring
Эти и другие
типы вы можете найти в jni.h.
Итак, перейдем
к цели нашей статьи – реализуем метод
print.
Напишем тело
функции Java_com_blogspot_toolkas_Console_print
в файле Console.c.
Сгенерированный заголовочный файл
назовем Console.h.
Console.c
будет выглядеть примерно так:
#include <stdio.h>
#include "Console.h"
JNIEXPORT void JNICALL Java_ru_olympico_io_Console_print(JNIEnv* env, jclass clazz, jstring string) {
const char* val = (*env)->GetStringUTFChars(env, string, NULL);
printf(val);
fflush(stdout);
(*env)->ReleaseStringUTFChars(env, string, val);
}
Строчка
const char*
val = (*env)->GetStringUTFChars(env, string, NULL);
конвертирует
jstring в массив char-символов,
для того, чтобы мы могли их распечатать.
printf(val);
- осуществляет непосредственно печать
массива символов в консоль.
fflush(stdout);
- очищает буфер вывода в консоль.
И наконец
(*env)->ReleaseStringUTFChars(env, string, val);
Сообщает JVM,
что платформенно-ориентированному коду
больше не требуется доступ к массиву
val;
Шаг третий: компиляция
Теперь, необходимо
скомпилировать dll и
сообщить JVM о ее существовании.
Компилятор
MinGW под Windows
делает это следующим образом:
gcc -I {JDK_HOME}/include -I {JDK_HOME}/include/win32 -shared -Wl,--add-stdcall-alias -o Console.dll Console.c
Шаг четвертый: запуск
Теперь необходимо
сообщить JVM имя библиотеки,
которую необходимо загрузить. Это
делается вызовом
System.loadLibrary("Console"):
package com.blogspot.toolkas;
public class TestConsole {
public static void main(String[] args) {
System.loadLibrary("Console");
Console.print("TEST");
}
}
Чтобы облегчить
поиск нашей библиотеки – самый простой
способ – скопировать ее в папку
{JDK_HOME}/bin.
Если все сделано
правильно, то запуск класса TestConsole
пройдет успешно и на консоль будет
выведена строка TEST.
На этом я хочу
закончить наше краткое введение в мир
JNI.
Удачных
исследований!
Комментариев нет:
Отправить комментарий