|
java
|
|
||||||||||||||||||||||||||||||||||||||||||
Downloads Web-design |
|
Глубинное
родство
этих языков
программирования
позволяет
им
взаимодействовать,
расширяя
возможности
каждого. Язык
JAVA во многом
произошел
от С/С++, у
которых были
позаимствованы
синтаксис и
базовая
семантика.
Однако связь
между ними
не
ограничивается
только этим.
Используя JNI (JAVA
NATIVE INTERFACE), можно
вызывать
С/С++-функции
из
JAVA-программы и,
наоборот, из
программы, написанной
на С/С++, можно
создавать
JAVA-объекты и
вызывать
JAVA-методы.
Несмотря на
то, что
использование
JNI в
большинстве
случаев
ведет к
потере многоплатформенности
JAVA-кода, данная
возможность
расширяет
сферу
применения
самого
языка JAVA на
приложения,
для которых
это условие
не является
необходимым.
В таких
системах
использование
JNI позволяет
сочетать
современный
объектно-ориентированный
подход JAVA -
главное
преимущество
этой технологии,
с
существующим
(LEGACY)
системно-зависимым
(PLATFORM SPECIFIC) кодом на
С/С++. Это
является
важным и
необходимым
условием
перехода к
использованию
JAVA-технологии
при
разработке
компонентов
сервера. Существует
несколько
причин
совместного
использования
С/С++ и JAVA:
стандартные
библиотеки
JAVA-классов не
всегда
поддерживают
некоторые
системно-зависимые
возможности;
необходимость
использования
наработанного
и отлаженного
кода на
других
языках или
желание
максимально
эффективно
реализовать
участок
кода,
критичного
с точки
зрения времени
исполнения.
Эти причины
не существенны
при
разработке
клиентских
приложений,
однако в случае
серверных -
они
становятся
доминирующими. Для
обеспечения
интероперабельности
программного
кода в
рамках С/С++ и JAVA
JDK1.1 (JAVA DEVELOPERS KIT) предоставляет
набор
интерфейсов,
объединенных
в JNI (JAVA NATIVE INTERFACE). JNI
позволяет
JAVA-коду, исполняемому
виртуальной
JAVA-машиной (JVM - JAVA VIRTUAL
MACHINE), взаимодействовать
с
приложениями
и библиотеками,
написанными
на языках С/С++
или Ассемблера. Основным
преимуществом
JNI перед
предыдущей
версией (JDK 1.0 NI - NATIVE
INTERFACE) и другими
сходными
интерфейсами
(NETSCAPE JAVA RINTIME INTERFACE или MICROSOFT'S RAW NATIVE
INTERFACE AND COM/JAVA INTERFACE) является
то, что JNI
изначально
разрабатывался
для
обеспечения
двоичной
совместимости
(BINARY COMPATIBILITY),
подразумевающей
совместимость
приложений,
написанных
с
использованием
JNI, для любых JVM
на
конкретной
платформе.
Другими словами,
один и тот же
скомпилированный
С/С++-код
должен
одинаково
корректно
исполняться
JVM NETSCAPE NAVIGATOR и MICROSOFT EXPLORER, SYMANTEC VISUAL CAFО
и SUN JAVA WORKSHOP и т.д. для
данной
платформы (WIN32).
Следует
отметить,
что ранние
интерфейсы
не
удовлетворяли
этому
условию.
Например, JDK 1.0 NI,
входящий в JDK 1.0.2 и
поддерживаемый
в JDK 1.1 для
обратной
совместимости,
использует
С-структуры
для доступа
к членам
JAVA-объекта, что
определяет
зависимость
С/С++-кода от
того, как JVM
располагает
объекты в памяти.
В общем
случае, при
использовании
JDK 1.0 NI требуется
перекомпиляция
соответствующего
С/С++-кода для
каждой JVM на
данной
платформе. Несмотря
на определенную
универсальность
интерфейса,
обусловленную
его
двоичной
совместимостью,
JNI обладает
широкой
функциональностью,
предоставляя
разработчику
все
низкоуровневые
механизмы JVM:
создание
JAVA-объектов,
включая создание
массивов и
объектов
типа STRING; вызов JAVA-методов;
возбуждение
и перехват
исключительных
ситуаций (EXCEPTION);
загрузка
JAVA-классов и динамический
анализ типа
(RUNTIME TYPE CHECKING).
Отдельно в JNI
входит INVOCATION API,
позволяющий
приложениям
динамически
загружать JVM.
Динамическая
загрузка JVM из
С/С++-кода
позволяет
легко
встраивать возможности
JAVA в
существующие
системы без необходимости
их
статического
связывания
(LINKAGE) с кодом JVM. Ниже
будет
рассмотрено,
как
создавать
коды на С/С++ и JAVA
для их
совместного
использования
в рамках JNI и INVOCATION
API. Все примеры
разработаны
и протестированы
на
платформе WINDOWS 95.
Во всех
случаях,
когда это
необходимо,
даются
пояснения
для платформы
UNIX. JNI
определяется
библиотечными
и заголовочными
(HEADER) файлами
для С/С++.
Библиотечные
файлы
хранятся в
подкаталоге
LIB (DLL - DYNAMIC-LINK LIBRARY, для WIN32 - в
подкаталоге
BIN), а
заголовочные
файлы - в
подкаталоге
INCLUDE основного
каталога JAVA. Использование
JNI Взаимодействие
кодов JAVA и С/С++
может
осуществляться
двумя
способами:
С/С++-код
получает
управление
непосредственно
из
JAVA-программы
путем
вызова
собственного
(NATIVE) метода;
С/С++-код
динамически
загружает JVM с
помощью INVOCATION API.
Во втором
случае, по
сути, реализуется
специализированная
JVM, так как
разработчик
С/С++-кода сам
решает, в
какой
последовательности
выполнять
JAVA-код (когда и
какие
JAVA-объекты
создавать,
какие
методы
вызывать и т.
д.). Рассмотрим
первую из
указанных
возможностей. Для
того чтобы
передать
управление
С/С++-коду из
JAVA-программы,
необходимо
создать
собственный
JAVA-метод,
сгенерировать
с помощью
утилиты JAVAH
заголовочный
файл для С/С++-функций,
разработать
сами
функции, в
которые
будет
передаваться
управление,
и оттранслировать
их, поместив
в
библиотечный
файл. После
создания
библиотеки
ее можно
загружать
из
JAVA-программы
для
последующего
вызова
собственных
методов. Создание
собственного
JAVA-метода Собственный
метод
создается
путем добавления
к его
описанию
спецификатора
NATIVE, при этом он
не должен
иметь
реализации
(так же как и
методы в
описании
интерфейса).
Спецификатор
NATIVE сообщает компилятору,
что
реализация
данного
метода
будет
представлена
в виде
откомпилированного
С/С++-кода,
помещенного
в
библиотечный
файл. Когда JVM
встречает
обращение к
собственному
методу,
происходит
вызов
соответствующей
С/С++-функции.
Помимо
описания
собственнного
метода, JAVA-код
должен
динамически
загрузить
библиотеку,
содержащую
С/С++-функцию с
реализацией
данного
метода. Для
этого в
классе JAVA.LANG.SYSTEM
существует
метод PUBLIC STATIC VOID LOADLIBRARY (STRING
LIBNAME),
загружающий
указанную
библиотеку.
Следующий
пример
демонстрирует
описание собственного
метода.
В
приведенном
примере
метод DOSPECIFIC()
является
собственным,
и его
С/С++-реализация
находится в
библиотеке
SYSSPEC. Метод LOADLIBRARY()
вызывается
в
статическом
инициализаторе,
что обеспечивает
единственный
вызов этого
метода
после
загрузки
класса SYSTEMSPECIFIC
загрузчиком
классов (CLASS LOADER). В
принципе, LOADLIBRARY()
можно
вызывать
более одного
раза
(например, в
конструкторе),
однако загрузка
библиотеки
будет
происходить
только при
первом
обращении к
LOADLIBRARY(),
поскольку при
последующих
вызовах
этого
метода определяется,
что библиотека
уже
загружена и
будет
просто возвращаться
управление. Метод
LOADLIBRARY()
преобразует
свой
параметр в
соответствии
с тем, как
именуются
библиотечные
файлы на
конкретной
платформе. В
данном примере
SYSSPEC преобразуется
в SYSSPEC.DLL и LIBSYSSPEC.SO для WIN32 и
UNIX
соответственно.
Метод LOADLIBRARY()
использует
стандартный
алгоритм
поиска
библиотеки
для данной
платформы.
Для WIN32 DLL должна
находиться
либо в
текущем
каталоге
процесса,
либо в
каталоге,
содержащем
EXE-файл, то есть
исполняемый
модуль JVM, находящийся
в
подкаталоге
BIN основного
каталога JAVA,
либо в
системном
каталоге WIN32,
либо каталоге
WINDOWS или в
каталогах,
указанных в
переменной
окружения PATH.
Для UNIX
библиотечный
файл должен
находиться
либо в
текущем
каталоге
процесса,
либо в
подкаталоге
LIB основного
каталога JAVA,
либо в каталогах,
перечисленных
в
переменной
окружения
LD_LIBRARY_PATH. Если
указанную
библиотеку
найти не
удается,
метод LOADLIBRARY()
генерирует
исключительную
ситуацию
JAVA.LANG.UNSATISFIEDLINKERROR. Однако
данная
ситуация
возникает
не только в
этом случае.
Когда
интерпретатор
встречает
вызов
собственного
метода, он ищет
его (точнее
его полную
сигнатуру) в
списке
методов
загруженных
библиотек.
Если метод
не найден, то
генерируется
указанная
исключительная
ситуация. Для
более
надежной
работы с
собственными
методами
можно
использовать,
к примеру, следующий
код:
>
Компиляция
программ,
содержащих
собственные
методы,
ничем не
отличается
от
компиляции
обычных
программ.
Например,
если
записать
предыдущий
пример в
файл с
именем APP.JAVA, то
для его
компиляции
необходимо
выполнить
следующую
команду:
Создание
заголовочного
файла Создание
С/С++-кода
необходимо
начинать с
создания
заголовочного
файла. Его
можно написать
вручную или
воспользоваться
утилитой JAVAH.
Второй путь
предпочтительней,
так как
допускает
меньшее
количество
ошибок. При
обращении к
утилите JAVAH
указывается
имя класса и
параметр -JNI.
Без него JAVAH
будет
генерировать
файл в
формате JDK 1.0 NI.
Имя класса
представляет
собой
полное
квалифицированное
имя класса.
Например:
Перед
использованием
утилиты JAVAH
соответствующий
JAVA-класс
должен быть
скомпилирован
в CLASS-файл.
Утилита JAVAH
анализирует
CLASS-файл и строит
заголовочный
файл, в
котором
перечислены
объявления
С/С++-функций,
представляющих
реализации
соответствующих
собственных
методов. В
качестве
имен создаваемых
заголовочных
файлов
используются
полные
квалифицированные
имена классов,
которые
описаны в
указанном
файле и содержат
собственные
методы.
Например,
если выполнить
следующие
команды:
то
JAVAH
сгенерирует
следующий
файл SYSTEMSPECIFIC.H:
Как
указывалось
выше, данный
файл можно создать
вручную или
с помощью
утилиты JAVAH. В последнем
случае не
рекомендуется
вносить в
него
какие-либо
изменения,
так как при последующем
применении JAVAH
к данному
классу все
внесенные
изменения
будут
потеряны. Директива
препроцессора
#INCLUDE <JNI.H>
включает
файл JNI.H (из
подкаталога
INLCUDE основного
каталога JAVA), в
котором
находятся
все необходимые
объявления
типов и
функций для
реализации
собственного
метода. Макросы
JNIEXPORT и JNICALL
необходимы
только для
платформы WIN32,
где они
раскрываются
соответственно
в __DECLSPEC(DLLEXPORT) и __STDCALL и
позволяют
более
эффективно
строить DLL.
Платформа UNIX
использует
для этих
целей
обычные
С-соглашения,
поэтому
указанные
макросы
раскрываются
в пустые
строки. Как
видно из
примера, имя
С/С++-функции
значительно
отличается
от имени
собственного
JAVA-метода.
Важным
понятием
при
построении
имени
С/С++-функции и
использовании
JNI-функций
является
сигнатура
метода (SIGNATURE или
METHOD ARGUMENTS SIGNATURE). Сигнатура
метода Сигнатура
метода - это
сокращенная
форма
записи
параметров
метода и
типов возвращаемого
значения.
Следует
подчеркнуть,
что в
сигнатуру
не входят ни
имя метода, ни
имена
параметров. JNI
формирует
сигнатуры в
соответствии
с правилами,
представленными
в табл. 1. Таблица
1
Проиллюстрируем
эти правила
на примерах:
Полная
информация
о правилах
образования
сигнатуры
метода
представлена
в файле SIGNATURE.H. Правила
формирования
имени
С/С++-функции Имя
С/С++-функции
формируется
путем
последовательного
соединения
следующих
компонентов:
Использование
имен с
сигнатурой
на конце необходимо
только в
случае
перегрузки
двух или
более
собственных
методов
(перегрузка
с обычным
методом не
важна, так
как обычные
методы не
будут
находиться
в создаваемой
библиотеке,
что, однако,
не
допускает
наличия
собственного
и обычного
метода с
одинаковыми
именами и
сигнатурами). Для
соответствия
лексиграфическим
правилам С/С++
и
использования
UNICODE-кодировки,
применяются
дополнительные
правила
преобразования,
представленные
в табл. 2. Таблица 2
Ниже
приведен
пример
JAVA-класса с
собственными
методами:
и
соответствующие
им имена
С/С++-функций:
Рассмотрим
типы
параметров,
которые
получает на
входе
С/С++-функция
при ее
вызове. Типы и
структуры
данных JNI JNI
использует
целый набор
типов для
своих функций
и для
формальных
параметров
С/С++-функций,
представляющих
реализацию
собственных
методов. Все
эти типы
описаны в
файле JNI.H, который
включается
в любой
заголовочный
файл для JNI.
Файл JNI.H
использует
стандартную
технику
препроцессирования
с макросом _CPLUSPLUS.
Тем самым, в
зависимости
от того,
какой (С++ или С)
код
компилируется,
будут создаваться
две немного
отличающиеся
версии
описания
типов.
Каждая из
них требует
определенного
синтаксиса
доступа. Файл JNI_MD.H
содержит
системно-зависимые
описания JINT, JLONG и
JBYTE. В этом же
файле
определены
макросы JNIEXPORT и JNICALL.
Тип VOID
используется
без переопределения. Следует
отметить,
что для
представления
строковых
объектов JNI
использует
сокращенный
вариант
формата UTF-8. Первым
аргументом
С/С++-функции,
представляющей
реализацию
собственного
метода,
является
указатель
на
структуру JNIENV. Смысл
этого
указателя
определяет
важную идею,
лежащую в
основе
реализации JNI.
Если отвлечься
от
конкретного
языка, то
указатель
на JNIENV, или
интерфейсный
указатель (JNI
INTERFACE POINTER), является
указателем
на массив
указателей,
каждый из
которых
указывает
на
прикладную
функцию JNI.
Только
через этот
указатель
С/С++-функция
может получить
доступ к
функциям и
ресурсам JNI. В случае
С++ (макрос _CPLUSPLUS
определен)
тип JNIENV
является структурой,
а в случае С -
указателем
на
структуру. В
силу этого, для
доступа к
функциям JNI в С
и С++
применяется
различный
синтаксис:
Главным
преимуществом
такой
организации
функций JNI
является
легкость
модификации
и
дальнейшего
расширения
интерфейса. Указатель
на JNIENV
действителен
только в
текущем
потоке (THREAD). JVM
гарантирует
передачу
одного и
того же
интерфейсного
указателя
всем методам,
вызываемым
из данного
потока. Тем самым
запрещено
передавать
интерфейсный
указатель
другому
потоку. Если
методы
вызываются
из разных
потоков, то в
этом случае
каждый
метод
получает
различные интерфейсные
указатели. Если
С/С++-функция
представляет
реализацию нестатического
собственного
метода, то вторым
параметром
функции
является
объект типа
JOBJECT. Данный
параметр
является
ссылкой на
JAVA-объект, для
которого
был вызван
соответствующий
собственный
метод. Если
функция
представляет
статический
собственный
метод, то
вторым параметром
является
объект типа
JCLASS, определяющий
JAVA-класс, для
которого
вызван
собственный
метод
класса (CLASS METHOD). Последующие
параметры
С/С++-функции
соответствуют
параметрам
собственного
метода (если
собственный
метод их не
содержит, то
реализующая
его
С/С++-функция
имеет
только два
описанных
выше
параметра). JNI функции JNI определяет
210 прикладных
функций.
Доступ к ним
из С/С++-функции
можно
получить
через
интерфейсный
указатель JNIENV*,
который
передается
каждой С/С++-функции,
представлющей
реализацию
собственного
метода. Все
функции
разделены
на 14 групп:
Использование
JNI функций
необходимо
только в том
случае, если
С/С++-функция
осуществляет
какое-либо
взаимодействие
с JVM: вызов
JAVA-методов,
доступ к
данным,
создание
JAVA-объектов и
т.д. Ниже
приведен
пример
JAVA-программы,
которая выводит
на печать
количество
свободной памяти
на диске С
для
платформы WIN32.
Для этого используется
собственный
метод и
соответствующая
реализационная
С/С++-функция,
вызывающая
при своей
работе
функцию WIN32 API.
Для
успешной
компиляции
кода JAVA и С/С++ на
платформе WIN32
необходимо
правильно
установить переменные
окружения PATH, LIB,
INCLUDE и CLASSPATH (многие
из этих
значений
можно
задать как
параметры
соответствующих
компиляторов). Если
записать
исходные
тексты
предыдущего
примера в
файлы APP.JAVA и SYSTEMSPECIFIC.CPP
соответственно,
то для их
компиляции
необходимо
выполнить следующие
команды
(предполагается,
что исходные
файлы
находятся в
каталоге C:\TEST\NATIVE):
Для запуска
программы
необходимо
выполнить:
Для
трансляции
С/С++-файлов
можно
использовать
любой
компилятор,
допускающий
создание
32-битных DLL. Использование
INVOCATION API Использование
INVOCATION API
позволяет
встраивать JVM
в приложения
без
необходимости
их статического
связывания
с кодом
самой JVM.
Напомним, что
в этом
случае
управление
изначально
находится в
С/С++-программе.
INVOCATION API состоит
из небольшого
набора функций,
позволяющих
создавать и
уничтожать JVM
в текущем
процессе,
присоединять
и отсоединять
текущий
поток от JVM
(интерфейсный
указатель
существует
только в
рамках данного
потока). В общем
случае, для
встраивания
JVM в программу
ее
необходимо
создать и проинициализировать,
присоединить,
если это
необходимо,
к
какому-либо
потоку, а по
окончании
работы с JVM
удалить ее
из памяти
процесса.
После того
как JVM создана
и получен
интерфейсный
указатель,
можно
использовать
любые
JNI-функции. Рассмотрим
пример С++-кода,
который
создает JVM в
процессе
своей работы
и вызывает
статический
метод JAVA-класса.
Ниже
приведены
исходные
тексты
JAVA-класса и
C++-кода:
Для
компиляции
приведенной
программы
на платформе
WIN32 необходимо
выполнить следующие
команды
(предполагается,
что переменные
окружения PATH, LIB,
INCLUDE и CLASSPATH
установлены
верно и
исходные
файлы
находятся в
каталоге C:\TEST\NATIVE):
А для
запуска
программы:
На первый
взгляд,
наличие JNI и
полная
публикация
его
спецификаций
может
привлечь
разработчиков
к написанию
непереносимого
JAVA-кода, что, в
свою очередь,
может
значительно
снизить
эффективность
применения
технологии JAVA.
Однако на
самом деле JNI
способствует
обратному
процессу. Практически
для любого
приложения
можно совершенно
точно
определить
необходимость
в
многоплатформенном
исполнении.
Для систем,
которые не
нуждаются в
многоплатформенности,
JNI
предоставляет
инфраструктуру,
с помощью
которой
JAVA-приложение
может взаимодействовать
с
операционной
системой и
аппаратурой,
в среде
которых оно
исполняется.
Таким
образом, JNI
является
естественным
дополнением
JAVA-технологии.
Он
позволяет
использовать
ее как для
создания
переносимых
(клиентских)
приложений,
так и для
создания высокопроизводительных
(серверных)
систем,
использующих
всю
специфику
конкретной
платформы и
аппаратуры,
сохраняя в
то же время
главное
достоинство
JAVA -
современный
объектно-ориентированный
подход. Автор:
Никита
Иванов |
|
Новости |
||||||||||||||||||||||||||||||||||||||||
Design by: