![]() |
|
   [ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ] |
|
|
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. Например, это объявление может выглядеть следующим образом:
Естественно, что для использования native-метода необходимо загрузить ту библиотеку, в состав которой он входит. Для этого программа на Java может воспользоваться методами load и loadLibrary(), входящими в состав класса System. Это – все, что необходимо сделать в Java-программе для обеспечения вызова native-метода. Приведем пример объявления и вызова native-метода. При этом допустим, что файл называется Example.java и что native-метод должен быть реализован в библиотеке с именем 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, который приведен ниже:
Что мы можем узнать, взглянув на этот файл? Во-первых, то, что в состав программы на С будет включен заголовочный файл jni.h, в котором, кстати, содержатся все необходимые для работы JNI описания.
в программе на C должен быть описан несколько по-другому, а именно как
Определение JNIEXPORT в файле jni_md.h, который вызывается из jni.h, описано следующим образом:
Описание вполне понятно и я не стану тратить время и внимание читателя на объяснение очевидных вещей. В том же файле определение JNICALL описано так:
После этого становится понятным, что все эти «страшные» описания являются просто обозначениями, используемыми при вызове обычной экспортируемой функции. Что же касается описания «jint», то в файлах jni.h и jni_md.hопределены несколько простых типов, которые могут быть использованы при написании native-методов. Я приведу их в таблице 1.
Таблица 1. Вспомним также, что программа на С для вызова из программы на Java должна содержать не функцию nativeMethod(), а функцию Java_Example_nativeMethod(). Вероятно, новое имя функции показывает, что она принадлежит классу Example, который, в свою очередь, является классом Java. Очень важно и то, что у этой функции должно быть два аргумента. Первым аргументом функции является указатель на «среду» JNI. Описание этой «среды» находится в файле jni.h, оно достаточно объемно, поэтому, к сожалению, нет возможности привести его здесь. Второй аргумент – это аналог указателя «this» в C, то есть подобие указателя на объект, из которого произведен вызов native-метода. Об этих аргументах мы поговорим немного позже. Но уже сейчас нам известно достаточно для того, чтобы написать native-метод и вызвать его из класса Java. Ниже приведен исходный текст программы Article.cpp, в которой определен native-метод, предназначенный для вызова из программы на языке Java:
Результатом компиляции этого файла будет файл Article.dll. А результатом работы класса Example, осуществляющего вызов метода из библиотеки Article.dll, будет следующий вывод:
Приведенный выше результат работы программы показывает, что, с одной стороны, отработала функция (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, приведены ниже:
Во-вторых, получив указатель на класс, необходимо получить идентификатор поля объекта Java, доступ к которому необходимо получить из native-метода. Это делается при помощи входящего в состав «среды» JNI метода GetFieldID(). Прототипы этих методов, приведенные ниже, также находятся в файле jni.h:
В этом случае аргументы методов не столь очевидны, как в случае GetObjectClass(). Думаю, что аргументы env и clazz понятны без объяснений. Аргумент name представляет собой имя поля, значение которого необходимо получить. А что представляет собой поле sig? Дело в том, что у каждого поля и метода класса, реализованного на языке Java, есть не только имя, но и так называемая сигнатура. Для того, чтобы получить эту сигнатуру, можно воспользоваться утилитой javap, вызвав ее с опцией -s и передав ей в качестве аргумента имя класса, сигнатуры которого нужно получить. В частности, для класса Example, приведенного выше, утилита javap выдала следующий результат:
Отсюда видно, что сигнатура поля i равна «I». Передав методу GetFieldID() сигнатуру поля в качестве последнего аргумента, мы получим идентификатор поля. Но это еще не все. Для того чтобы получить значение поля, следует обратиться к методу
Возвращенное этим методом значение и будет являться тем значением поля, которое мы хотим получить.
Думаю, что назначения аргументов методов не должны вызывать вопросов. Метод возвращает идентификатор метода в классе. Зная этот идентификатор, можно обратиться к методу при помощи одного из методов группы Call
Ниже приведен исходный текст класса Java, значение поля i которого будет получено в native-методе и метод printMessage() которого будет вызван из native-метода:
Исходный текст DLL, в которой находится код, осуществляющий доступ к полю класса Example и вызывающий метод printMessage(), также приведен ниже:
Результат работы связки «класс Example <-> native-метод Java_Example_nativeMethod» был следующим:
Проанализировав приведенный выше результат, можно заметить, что первое сообщение выдается классом Example. Cледующие пять строк выдаются native-методом. При этом строка, начинающаяся с «This method…», выдана методом класса Example, вызванным из native-метода. И, наконец, последние два сообщения выдаются опять-таки классом Example. Связь между классом Java и native-методом налицо. ЗаключениеЕстественно, описанными выше возможностями интерфейс JNI не ограничивается. В частности, native-метод может получить идентификатор любого класса при помощи метода FindClass(), после чего создать объект этого класса при помощи метода NewObject() и обращаться к методам созданного объекта при помощи методов группы Call Однако в каждой бочке меда есть своя ложка дегтя. Дело в том, что использование в языке Java native-методов нарушает принцип многоплатформенности языка Java. Программа, использующая DLL, становится заведомо «привязанной» к платформе, на которой реализована DLL. Использование native-методов можно порекомендовать в тех случаях, когда предполагается использование основной программы (класса Java) на разных платформах, в то время как машинно- или платформенно-зависимые части программы в виде native-методов планируется разработать для каждой конкретной платформы. Если программу на Java, использующую native-методы, планируется применять только на той платформе, на которой реализованы native-методы, то такая затея заведомо является бессмысленной. И еще один, более серьезный аргумент против native-методов. Native-код может получить доступ к любой части системы, что в принципе является небезопасным. Поскольку одним из требований, предъявляемых к языку Java, является требование безопасности, применение native-методов опять-таки идет вразрез с идеологией языка Java. Тем не менее, ответственность за принятие решения о применении в программе на Java native-методов, расширяющих стандартные возможности Java, лежит на программисте.
Автор:
[ вверх ]
Ваш комментарий к данному материалу будет интересен нам и нашим читателям!
|
|
WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects |
|