Логин:   Пароль:




Новости
Рассылки
Форум
Поиск


Java
- Апплеты
- Вопрос-ответ
- Классы
- Примеры
- Руководства
- Статьи
- IDE
- Словарь терминов
- Скачать

Мобильная Java
- Игры
- Примеры
- Статьи
- WAP, WML и пр.

JavaScript
- Вопрос-ответ
- Примеры
- Статьи

Веб-мастеринг
- HTML
- CSS
- SSI

Разминка для ума
Проекты
Книги
Ссылки
Программы
Юмор :)




Rambler's Top100

Java: СтатьиJNI: взаимодействие Java с другими языками

JNI: взаимодействие Java с другими языками

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

Однако я не зря написал, что самодостаточность языка Java проявляется именно в большинстве случаев. Иногда без привлечения вспомогательных средств написать программу полностью невозможно. Например, необходимо воспользоваться кодом, который обеспечивает низкоуровневый доступ к «железу» того компьютера, на котором выполняется программа. В языке программирования Java, который по своей идеологии является многоплатформенным, средства низкоуровневого доступа к аппаратной части просто-напросто не предусмотрены.

С другой стороны, к моменту появления языка Java в мире программирования уже существовали колоссальные «залежи» программ и библиотек, позволяющих решать практически любые задачи, начиная от математических вычислений и заканчивая управлением сложными системами. Естественно, не замечать это богатство было бы просто неразумно.

Разработчики Java, рассуждая, вероятно, подобным образом, включили в язык возможность обращения из Java-программ к программам, реализованным на других языках программирования, так называемым native-методам. Подсистема Java, реализующая эту возможность, называется JNI (Java Native Interface – интерфейс языка Java, позволяющий обращение к native-методам).

В этой статье я не буду касаться внутренней реализации и остановлюсь на вопросах практического применения JNI.

Обращение к native-методам из языка Java

Процедуры, обеспечивающие связь native-метода с программой на Java, зависят от применяемой операционной системы и языка программирования, на котором native-методы реализованы. Рассмотреть все или хотя бы наиболее распространенные языки операционные системы в рамках журнальной статьи не представляется возможным. Поэтому я остановлюсь на связи языка Java с библиотеками DLL операционных систем семейства Microsoft Windows.

Представим, что в программе на языке Java есть метод с именем nativeMethod(), который де-факто является native-методом и реализован в какой-то динамической библиотеке. Для того, чтобы указать, что метод nativeMethod() является native-методом, при его объявлении необходимо использовать ключевое слово native. Например, это объявление может выглядеть следующим образом:


public native int nativeMethod();

Естественно, что для использования native-метода необходимо загрузить ту библиотеку, в состав которой он входит. Для этого программа на Java может воспользоваться методами load и loadLibrary(), входящими в состав класса System.

Это – все, что необходимо сделать в Java-программе для обеспечения вызова native-метода. Приведем пример объявления и вызова native-метода. При этом допустим, что файл называется Example.java и что native-метод должен быть реализован в библиотеке с именем Article.dll:


public class Example
{
public static void main( String args[] )
{
Example ex = new Example();
System.out.println( "Before native method call." );
int result = ex.nativeMethod();
System.out.println( "After native method call." );
System.out.println( "Result = " + result );
}
public native int nativeMethod();
static
{
System.load( "f:/Article/Example/Article.dll" );
}
}

Откомпилировав этот файл и, соответственно, содержащийся в нем класс, мы получим файл Example.class. Однако, несмотря на то, что класс откомпилировался без ошибки, он пока неработоспособен, так как до сих пор не установлена связь с native-методом. Для того, чтобы установить эту связь, придется проделать некоторую работу.

Подготовка native-метода к вызову из языка Java

Во-первых, при компиляции native-метода компилятор языка Java производит некоторые вспомогательные действия, результатом которых, в частности, является то, что при вызове native-метода к списку аргументов последнего добавляются некоторые величины, существование которых игнорировать нельзя. Для того чтобы определить, как будет выглядеть интерфейс вызова native-метода, следует воспользоваться утилитой javah (вероятно, сокращение от Java’s Header), которая входит в состав JDK. Все аргументы этой утилиты описаны в ее документации. Нам сейчас важно понять, что эта утилита обрабатывает ранее откомпилированный java-класс, который находится в файле, имя которого совпадает с именем класса.

Аргументом при вызове утилиты должно быть имя класса, в котором описан native-метод. Отыскав в этом классе вызов native-метода, утилита определяет, с каким аргументами будет вызван native-метод, после чего формирует файл заголовка (.h-файл), который должен быть включен в С-файл, в котором будет находиться реализация метода nativeMethod().

Итак, применительно к нашему примеру утилиту javah необходимо вызвать следующим образом:
javah Example

Результатом работы утилиты будет файл Example.h, который приведен ниже:


/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class Example */

#ifndef _Included_Example
#define _Included_Example
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Example
* Method: nativeMethod
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_Example_nativeMethod (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Что мы можем узнать, взглянув на этот файл? Во-первых, то, что в состав программы на С будет включен заголовочный файл jni.h, в котором, кстати, содержатся все необходимые для работы JNI описания.
Во-вторых, мы увидим, что тот метод, который в классе Example был описан как


public native int nativeMethod();

в программе на C должен быть описан несколько по-другому, а именно как


JNIEXPORT jint JNICALL Java_Example_nativeMethod (JNIEnv *, jobject);

Определение JNIEXPORT в файле jni_md.h, который вызывается из jni.h, описано следующим образом:


#define JNIEXPORT __declspec(dllexport)

Описание вполне понятно и я не стану тратить время и внимание читателя на объяснение очевидных вещей. В том же файле определение JNICALL описано так:


#define JNICALL __stdcall

После этого становится понятным, что все эти «страшные» описания являются просто обозначениями, используемыми при вызове обычной экспортируемой функции.

Что же касается описания «jint», то в файлах jni.h и jni_md.hопределены несколько простых типов, которые могут быть использованы при написании native-методов. Я приведу их в таблице 1.

Определение Тип
typedef unsigned char Boolean
typedef unsigned short Jchar
typedef short Jshort
typedef float Jfloat
typedef double Jdouble
typedef long Jint
typedef __int64 Jlong
typedef signed char Jbyte

Таблица 1.

Вспомним также, что программа на С для вызова из программы на Java должна содержать не функцию nativeMethod(), а функцию Java_Example_nativeMethod(). Вероятно, новое имя функции показывает, что она принадлежит классу Example, который, в свою очередь, является классом Java. Очень важно и то, что у этой функции должно быть два аргумента. Первым аргументом функции является указатель на «среду» JNI. Описание этой «среды» находится в файле jni.h, оно достаточно объемно, поэтому, к сожалению, нет возможности привести его здесь. Второй аргумент – это аналог указателя «this» в C, то есть подобие указателя на объект, из которого произведен вызов native-метода. Об этих аргументах мы поговорим немного позже. Но уже сейчас нам известно достаточно для того, чтобы написать native-метод и вызвать его из класса Java. Ниже приведен исходный текст программы Article.cpp, в которой определен native-метод, предназначенный для вызова из программы на языке Java:


#include 
#include 
#include 
#include "Example.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                      DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE;
}

JNIEXPORT jint JNICALL Java_Example_nativeMethod (JNIEnv * je, 
                                                  jobject jo)
{
printf( "!!! In the native method !!!\n" );
return 32;
}

Результатом компиляции этого файла будет файл Article.dll. А результатом работы класса Example, осуществляющего вызов метода из библиотеки Article.dll, будет следующий вывод:


Before native method call.
!!! In the native method !!!
After native method call.
Result = 32

Приведенный выше результат работы программы показывает, что, с одной стороны, отработала функция (native-метод), находящаяся в библиотеке, написанной на C (именно она выдает сообщение «!!! In the native method !!!»). С другой стороны, класс на языке Java получил результат выполнения native-метода и отобразил его.

Однако все, о чем говорилось выше, касается только для тех native-методов, которые программист может написать сам. А как же быть в случае, когда native-методы находятся в библиотеке, к исходным текстам которой программист доступа не имеет? В таком случае процесс обращения к native-методу состоит из двух этапов. Во-первых, необходимо создать «промежуточную» библиотеку на языке C/C++, в которой будут реализованы методы с именами и аргументами, подготовленными к вызову из программы на языке Java. Во-вторых, из этой промежуточной библиотеки должны вызываться методы и функции, находящиеся в уже готовой библиотеке. Так как «промежуточная» библиотека написана на языке С/С++, то ничто не мешает обратиться к функциям, находящимся в другой библиотеке, обычным образом, а результаты работы передать в программу на языке Java.

Вызов методов класса Java из native-кода

Итак, как показано выше, вызов native-методов и получение результата их выполнения из программы на языке Java возможно. Возникает вопрос: а что представляют собой передаваемые native-методу аргументы? Что такое «среда» JNI? Что представляет собой объект Java с точки зрения практического использования?

Фактически «среда» JNI представляет собой массив указателей на методы, используя которые можно осуществлять доступ к полям класса Java и осуществлять вызов методов класса Java (как класса, из которого вызывается native-метод, так и любого Java-класса, подробнее см. в конце статьи). Передаваемый же native-методу объект позволяет осуществить привязку методов JNI непосредственно к конкретному объекту и классу объектов, с которыми может работать native-метод. Продемонстрируем это на практике.

Технология получения native-методом значения поля, принадлежащего классу Java, состоит в следующем. Во-первых, необходимо получить указатель на класс, к которому относится объект, осуществивший вызов native-метода. Это можно сделать при помощи метода GetObjectClass(), входящего в состав «среды» JNI. В том случае, если native-метод реализован на языке C++, этому методу передается только полученный native-методом объект (второй аргумент при вызове native-метода). В случае написания native-метода на языке C аргументами метода являются указатель на «среду» JNI (первый аргумент при вызове native-метода) и переданный native-методу объект. Прототипы метода GetObjectClass() для языков C и С++ соответственно, взятые из файла jni.h, приведены ниже:


jclass (JNICALL *GetObjectClass) (JNIEnv *env, jobject obj); // C
jclass GetObjectClass(jobject obj) 
    {return functions->GetObjectClass(this,obj); } // C++

Во-вторых, получив указатель на класс, необходимо получить идентификатор поля объекта Java, доступ к которому необходимо получить из native-метода. Это делается при помощи входящего в состав «среды» JNI метода GetFieldID(). Прототипы этих методов, приведенные ниже, также находятся в файле jni.h:


jfieldID (JNICALL *GetFieldID) 
(JNIEnv *env, jclass clazz, const char *name, const char *sig);

jfieldID GetFieldID(jclass clazz, const char *name, const char *sig)
{
return functions->GetFieldID(this, clazz, name, sig);
}

В этом случае аргументы методов не столь очевидны, как в случае GetObjectClass(). Думаю, что аргументы env и clazz понятны без объяснений. Аргумент name представляет собой имя поля, значение которого необходимо получить. А что представляет собой поле sig?

Дело в том, что у каждого поля и метода класса, реализованного на языке Java, есть не только имя, но и так называемая сигнатура. Для того, чтобы получить эту сигнатуру, можно воспользоваться утилитой javap, вызвав ее с опцией -s и передав ей в качестве аргумента имя класса, сигнатуры которого нужно получить. В частности, для класса Example, приведенного выше, утилита javap выдала следующий результат:


Compiled from Example.java
public class Example extends java.lang.Object {
int i;
/* I */
public Example();
/* ()V */
public static void main(java.lang.String[]);
/* ([Ljava/lang/String;)V */
public void printMessage();
/* ()V */
public native int nativeMethod();
/* ()I */
static {};
/* ()V */
}

Отсюда видно, что сигнатура поля i равна «I». Передав методу GetFieldID() сигнатуру поля в качестве последнего аргумента, мы получим идентификатор поля. Но это еще не все. Для того чтобы получить значение поля, следует обратиться к методу


GetIntField():
jint (JNICALL *GetIntField)
(JNIEnv *env, jobject obj, jfieldID fieldID);

jint GetIntField(jobject obj, jfieldID fieldID)
{
return functions->GetIntField(this, obj, fieldID);
}

Возвращенное этим методом значение и будет являться тем значением поля, которое мы хотим получить.
Замечу, что общее название группы методов, позволяющих получить значение поля – GetField.
Для того чтобы из native-метода вызвать метод, определенный в вызывающем классе, нужно затратить усилий не меньше, чем для того, чтобы получить значение поля. Во-первых, при помощи метода GetMethodId() следует получить идентификатор метода. Прототипы метода GetMethodId() приведены ниже:


jmethodID (JNICALL *GetMethodID) (JNIEnv *env, 
  jclass clazz, const char *name, const char *sig);

jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)
{
return functions->GetMethodID(this, clazz, name, sig);
}

Думаю, что назначения аргументов методов не должны вызывать вопросов. Метод возвращает идентификатор метода в классе. Зная этот идентификатор, можно обратиться к методу при помощи одного из методов группы CallMethod(). В данном случае нужно обратиться к методу printMessage(), не возвращающему значения, поэтому целесообразно воспользоваться методом CallVoidMethod():


void (JNICALL *CallVoidMethod)
(JNIEnv *env, jobject obj, jmethodID methodID, ...);

void CallVoidMethod(jobject obj, jmethodID methodID, ...) {
va_list args;
va_start(args,methodID);
functions->CallVoidMethodV(this,obj,methodID,args);
va_end(args);
}

Ниже приведен исходный текст класса Java, значение поля i которого будет получено в native-методе и метод printMessage() которого будет вызван из native-метода:


public class Example
{
int i;

public static void main( String args[] )
{
Example ex = new Example();
ex.i = 28;
System.out.println( "Before native method call." );
int result = ex.nativeMethod();
System.out.println( "After native method call." );
System.out.println( "Result = " + result );
}

public void printMessage()
{
System.out.println( "This method was called from native method." );
}

public native int nativeMethod();

static
{
System.load( "f:/Article/Example/Article.dll" );
}
}

Исходный текст DLL, в которой находится код, осуществляющий доступ к полю класса Example и вызывающий метод printMessage(), также приведен ниже:


#include "stdafx.h"
#include 
#include "Example.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE;
}

JNIEXPORT jint JNICALL Java_Example_nativeMethod
(JNIEnv *je, jobject jo)
{
jclass javaClass;
jfieldID javaFieldID;
jmethodID javaMethodID;

printf( "!!! In the native method !!!\n" );
javaClass = je->GetObjectClass( jo );
javaFieldID = je->GetFieldID( javaClass, "i", "I" );
printf( "i = %d.\n", je->GetIntField( jo, javaFieldID ) );
javaMethodID = je->GetMethodID( javaClass, "printMessage", "()V" );
printf( "javaMethodID = %x.\n", javaMethodID );
je->CallVoidMethod( jo, javaMethodID );
printf( "!!! About to leave the native method !!!\n" );
return 123;
}

Результат работы связки «класс Example <-> native-метод Java_Example_nativeMethod» был следующим:


Before native method call.
!!! In the native method !!!
i = 28.
javaMethodID = 6ac790.
This method was called from native method.
!!! About to leave the native method !!!
After native method call.
Result = 123

Проанализировав приведенный выше результат, можно заметить, что первое сообщение выдается классом Example. Cледующие пять строк выдаются native-методом. При этом строка, начинающаяся с «This method…», выдана методом класса Example, вызванным из native-метода. И, наконец, последние два сообщения выдаются опять-таки классом Example. Связь между классом Java и native-методом налицо.

Заключение

Естественно, описанными выше возможностями интерфейс JNI не ограничивается. В частности, native-метод может получить идентификатор любого класса при помощи метода FindClass(), после чего создать объект этого класса при помощи метода NewObject() и обращаться к методам созданного объекта при помощи методов группы CallMethod(). Таким образом, возможности по связи классов языка Java и native-методов являются практически неограниченными.

Однако в каждой бочке меда есть своя ложка дегтя. Дело в том, что использование в языке Java native-методов нарушает принцип многоплатформенности языка Java. Программа, использующая DLL, становится заведомо «привязанной» к платформе, на которой реализована DLL. Использование native-методов можно порекомендовать в тех случаях, когда предполагается использование основной программы (класса Java) на разных платформах, в то время как машинно- или платформенно-зависимые части программы в виде native-методов планируется разработать для каждой конкретной платформы. Если программу на Java, использующую native-методы, планируется применять только на той платформе, на которой реализованы native-методы, то такая затея заведомо является бессмысленной.

И еще один, более серьезный аргумент против native-методов. Native-код может получить доступ к любой части системы, что в принципе является небезопасным. Поскольку одним из требований, предъявляемых к языку Java, является требование безопасности, применение native-методов опять-таки идет вразрез с идеологией языка Java.

Тем не менее, ответственность за принятие решения о применении в программе на Java native-методов, расширяющих стандартные возможности Java, лежит на программисте.




Сергей Фельдман
"Система программирования JAVA без секретов. Как создать безопасное приложение с "нуля""
Подробнее>>
Заказать>>


Е. Е. Аккуратов
"Знакомьтесь: Java. Самоучитель"
Подробнее>>
Заказать>>

Узнай о чем ты на самом деле сейчас думаешь тут.


[an error occurred while processing this directive]



Apache Struts 2.0.11
Apache MyFaces Trinidad Core 1.2.3.
Sun переводит мобильные устройства с Java ME на Java SE
Хакерская атака!