Spec-Zone .ru
спецификации, руководства, описания, API
|
Содержание | Предыдущий | Следующий |
Эта глава сосредотачивается на главных вопросах проектирования в JNI. Большинство вопросов проектирования в этом разделе связывается с собственными методами. Проект API Вызова охватывается в Главе 5.
Собственный Java доступов кода функции VM, вызывая функции JNI. Функции JNI доступны через указатель на интерфейс. Указатель на интерфейс является указателем на указатель. Этот указатель указывает массиву указателей, каждый из которых указывает на функцию интерфейса. Каждая функция интерфейса при предопределенном смещении в массиве. Рисунок 2-1 иллюстрирует организацию указателя на интерфейс.
Интерфейс JNI организуется как таблица виртуальной функции C++ или интерфейс COM. Преимущество для использования интерфейсной таблицы, а не соединенных проводами функциональных записей, состоит в том, что пространство имен JNI становится отдельным от собственного кода. VM может легко обеспечить многократные версии таблиц функции JNI. Например, VM может поддерживать две таблицы функции JNI:
Указатель на интерфейс JNI только допустим в текущем потоке. Собственный метод, поэтому, не должен передать указатель на интерфейс от одного потока до другого. VM реализация JNI может выделить и сохранить локальные данные потока в области, на которую указывает указатель на интерфейс JNI.
Собственные методы получают указатель на интерфейс JNI как параметр. VM, как гарантируют, передаст тот же самый указатель на интерфейс к собственному методу, когда это сделает множественные вызовы собственного метода от того же самого потока Java. Однако, собственный метод можно вызвать от различных потоков Java, и поэтому может получить различные указатели на интерфейс JNI.
Начиная с Java VM многопоточен, собственные библиотеки должны также быть скомпилированы и соединены с мультиориентированными на многопотоковое исполнение собственными компиляторами. Например, -mt
флаг должен использоваться для кода C++, скомпилированного с компилятором Studio Sun. Для кода, выполненного GNU gcc компилятор, флаги -D_REENTRANT
или -D_POSIX_C_SOURCE
должен использоваться. Для получения дополнительной информации, пожалуйста, сошлитесь на собственную документацию компилятора.
Собственные методы загружаются System.loadLibrary
метод. В следующем примере метод инициализации класса загружает специфичную для платформы собственную библиотеку в который собственный метод f
определяется:
package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary(“pkg_Cls”); } }
Параметр System.loadLibrary
имя библиотеки, выбранное произвольно программистом. Система следует за стандартом, но специфичный для платформы, подход, чтобы преобразовать имя библиотеки к собственному имени библиотеки. Например, система Соляриса преобразовывает имя pkg_Cls
к libpkg_Cls.so
, в то время как система Win32 преобразовывает то же самое pkg_Cls
имя к pkg_Cls.dll
.
Программист может пользоваться единственной библиотекой, чтобы сохранить все собственные методы, необходимые любому числу классов, пока эти классы должны быть загружены тем же самым загрузчиком класса. VM внутренне поддерживает список загруженных собственных библиотек для каждого загрузчика класса. Поставщики должны выбрать собственные имена библиотеки, которые минимизируют шанс столкновений имени.
Если базовая операционная система не поддерживает динамическое подключение, все собственные методы должны быть предварительно соединены с VM. В этом случае VM завершается System.loadLibrary
вызовите, фактически не загружая библиотеку.
Программист может также вызвать функцию JNI RegisterNatives()
зарегистрировать собственные методы, связанные в классе. RegisterNatives()
функция особенно полезна со статически соединенными функциями.
Динамические компоновщики разрешают записи, основанные на их именах. Собственное имя метода связывается от следующих компонентов:
Java_
VM проверяет на соответствие имени метода для методов, которые находятся в собственной библиотеке. VM сначала ищет краткое название; то есть, имя без подписи параметра. Это тогда ищет длинное имя, которое является именем с подписью параметра. Программисты должны использовать длинное имя только, когда собственный метод перегружается с другим собственным методом. Однако, это не проблема, если у собственного метода есть то же самое имя как несобственный метод. Несобственный метод (метод Java) не находится в собственной библиотеке.
В следующем примере, собственном методе g
не должен быть соединен, используя длинное имя потому что другой метод g
не собственный метод, и таким образом не находится в собственной библиотеке.
Мы приняли простую искажающую имя схему гарантировать, что все символы Unicode преобразовывают на допустимые имена функций C. Мы используем подчеркивание (“ _ ”) символ как замена для наклонной черты (“ / ”) в полностью определенных именах классов. Начиная с имени или дескриптора типа никогда не начинается с числа, мы можем использовать _0
, ..., _9
для escape-последовательностей, поскольку Таблица 2-1 иллюстрирует:
И собственные методы и интерфейсные API следуют за стандартным соглашением о вызовах библиотеки по данной платформе. Например, системы UNIX используют соглашение о вызовах C, в то время как системы Win32 используют __ stdcall.
Указатель на интерфейс JNI является первым параметром собственным методам. Указатель на интерфейс JNI имеет тип JNIEnv. Второй параметр отличается в зависимости от того, статичен ли собственный метод или нестатичен. Вторым параметром нестатическому собственному методу является ссылка на объект. Вторым параметром статическому собственному методу является ссылка на свой класс Java.
Остающиеся параметры соответствуют регулярным параметрам метода Java. Собственный вызов метода пасует назад свой результат к вызывающей подпрограмме через возвращаемое значение. Глава 3 описывает отображение между типами C и Java.
Пример кода 2-1 иллюстрирует использование функции C, чтобы реализовать собственный метод f
. Собственный метод f
объявляется следующим образом:
C функционируют с долгим скорректированным именем Java_pkg_Cls_f_ILjava_lang_String_2
реализует собственный метод f
:
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */ jobject obj, /* "this" pointer */ jint i, /* argument #1 */ jstring s) /* argument #2 */ { /* Obtain a C-copy of the Java string */ const char *str = (*env)->GetStringUTFChars(env, s, 0); /* process the string */ ... /* Now we are done with str */ (*env)->ReleaseStringUTFChars(env, s, str); return ... }
Отметьте, что мы всегда управляем объектами Java, используя конверт указателя на интерфейс Используя C++, можно записать немного более чистую версию кода, как показано в Примере кода 2-2:
extern "C" /* specify the C calling convention */ jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */ jobject obj, /* "this" pointer */ jint i, /* argument #1 */ jstring s) /* argument #2 */ { const char *str = env->GetStringUTFChars(s, 0); ... env->ReleaseStringUTFChars(s, str); return ... }
С C++ дополнительный уровень абстракции и параметр указателя на интерфейс исчезают из исходного кода. Однако, базовый механизм является точно тем же самым как с C. В C++ функции JNI определяются как встроенные функции членства, которые расширяются до их дубликатов C.
Типы примитивов, такие как целые числа, символы, и так далее, копируются между Java и собственным кодом. Произвольные объекты Java, с другой стороны, передает ссылка. VM должен отследить все объекты, которые передали к собственному коду, так, чтобы эти объекты не были освобождены сборщиком "мусора". У собственного кода, поочередно, должен быть способ сообщить VM, что это больше не нуждается в объектах. Кроме того, сборщик "мусора" должен быть в состоянии переместить объект, упомянутый собственным кодом.
JNI делит ссылки на объект, используемые собственным кодом в две категории: локальные и глобальные ссылки. Локальные ссылки допустимы для продолжительности собственного вызова метода, и автоматически освобождаются после собственных возвратов метода. Глобальные ссылки остаются допустимыми, пока они явно не освобождаются.
Объекты передают к собственным методам как локальные ссылки. Все объекты Java, возвращенные функциями JNI, являются локальными ссылками. JNI позволяет программисту создавать глобальные ссылки из локальных ссылок. Функции JNI, которые ожидают объекты Java, принимают и глобальные и локальные ссылки. Собственный метод может возвратить локальную или глобальную ссылку на VM как его результат.
В большинстве случаев программист должен положиться на VM, чтобы освободить все локальные ссылки после собственных возвратов метода. Однако, есть времена, когда программист должен явно освободить локальную ссылку. Рассмотрите, например, следующие ситуации:
JNI позволяет программисту вручную удалять локальные ссылки в любой точке в пределах собственного метода. Чтобы гарантировать, что программисты могут вручную освободить локальные ссылки, функциям JNI не позволяют создать дополнительные локальные ссылки, за исключением ссылок, которые они возвращают как результат.
Локальные ссылки только допустимы в потоке, в котором они создаются. Собственный код не должен передать локальные ссылки от одного потока до другого.
Чтобы реализовать локальные ссылки, Java, VM создает реестр для каждого перехода управления от Java до собственного метода. Реестр отображает неподвижные локальные ссылки на объекты Java, и препятствует объектам быть собранным "мусор". Все объекты Java, которые передают к собственному методу (включая тех, которые возвращаются как результаты вызовов функции JNI), автоматически добавляются к реестру. Реестр удаляется после собственных возвратов метода, позволяя все его записи быть собранным "мусор".
Есть различные способы реализовать реестр, такой как использование таблицы, связанного списка, или хэш-таблицы. Хотя подсчет ссылок может использоваться, чтобы избежать дублированных записей в реестре, реализация JNI не обязана обнаружить и свернуть двойные записи.
Отметьте, что локальные ссылки не могут быть искренне реализованы, консервативно сканируя собственный стек. Собственный код может сохранить локальные ссылки в структуры данных "кучи" или глобальную переменную.
JNI обеспечивает богатый набор функций средства доступа на глобальных и локальных ссылках. Это означает, что та же самая собственная реализация метода работает независимо от того, как VM представляет объекты Java внутренне. Это - решающая причина, почему JNI может поддерживаться большим разнообразием реализаций VM.
Издержки использования функций средства доступа через непрозрачные ссылки выше чем тот из прямого доступа к структурам данных C. Мы полагаем, что в большинстве случаев программисты Java используют собственные методы, чтобы выполнить нетривиальные задачи, которые омрачают издержки этого интерфейса.
Эти издержки не являются приемлемыми для больших объектов Java, содержащих много примитивных типов данных, таких как целочисленные массивы и строки. (Рассмотрите собственные методы, которые используются, чтобы выполнить векторные и матричные вычисления.) Это было бы чрезвычайно неэффективно, чтобы выполнить итерации через Java, выстраивают и получают каждый элемент с вызовом функции.
Одно решение представляет понятие "прикрепления" так, чтобы собственный метод мог попросить, чтобы VM придавил содержание массива. Собственный метод тогда получает прямой указатель на элементы. У этого подхода, однако, есть две импликации:
Мы принимаем компромисс, который преодолевает обе из вышеупомянутых проблем.
Во-первых, мы обеспечиваем ряд функций, чтобы скопировать примитивные элементы массива между сегментом массива Java и собственным буфером памяти. Используйте эти функции, если собственный метод нуждается в доступе к только небольшому числу элементов в многочисленном массиве.
Во-вторых, программисты могут использовать другой набор функций, чтобы получить вниз прикрепленную версию элементов массива. Имейте в виду, что эти функции могут потребовать, чтобы Java VM выполнил выделение хранения и копирование. Копируют ли эти функции фактически массив, зависит от реализации VM, следующим образом:
Наконец, интерфейс обеспечивает функции, чтобы сообщить VM, что собственный код больше не должен получить доступ к элементам массива. Когда Вы вызываете эти функции, система или неприкрепляет массив, или это согласовывает исходный массив со своей неподвижной копией и освобождает копию.
Наш подход обеспечивает гибкость. Алгоритм сборщика "мусора" может принять отдельные решения относительно копирования или прикрепления для каждого данного массива. Например, сборщик "мусора" может скопировать маленькие объекты, но прикрепить большие объекты.
Реализация JNI должна гарантировать, что собственные методы, работающие в многократных потоках, могут одновременно получить доступ к тому же самому массиву. Например, JNI может сохранить внутренний счетчик для каждого прикрепленного массива так, чтобы один поток не неприкрепил массив, который также прикрепляется другим потоком. Отметьте, что JNI не должен заблокировать примитивные массивы для эксклюзивного доступа собственным методом. Одновременно обновление массива Java от различных потоков приводит к недетерминированным результатам.
JNI позволяет собственному коду получать доступ к полям и вызывать методы объектов Java. JNI идентифицирует методы и поля их символьными именами и подписями типа. Двухступенчатый процесс факторизует стоимость определения местоположения поля или метода с его имени и подписи. Например, чтобы вызвать метод f
в классе cls, собственный код сначала получает ID метода, следующим образом:
Собственный код может тогда неоднократно использовать ID метода без стоимости поиска метода, следующим образом:
ID поля или метода не препятствует тому, чтобы VM разгрузил класс, из которого был получен ID. После того, как класс разгружается, ID метода или поля становится недопустимым. Собственный код, поэтому, должен удостовериться к:
если это намеревается использовать метод или полевой ID в течение длительного периода времени.
JNI не вводит ограничений для того, как поле и ID метода реализуются внутренне.
JNI не проверяет на программирование ошибок, таких как передача в Нулевых указателях или недопустимых типах параметра. Недопустимые типы параметра включают такие вещи как использование нормального объекта Java вместо объекта класса Java. JNI не проверяет на эти ошибки программирования по следующим причинам:
Большинство библиотечных функций C не принимает меры против программирования ошибок. printf()
функция, например, обычно вызывает ошибку периода выполнения, вместо того, чтобы возвратить код ошибки, когда она получает недопустимый адрес. Принуждение C библиотечные функции, чтобы проверить на все возможные состояния ошибки, вероятно, привело бы к таким проверкам, которые будут дублированы - однажды в пользовательском коде, и с другой стороны в библиотеке.
Программист не должен передать недопустимые указатели или параметры неправильного типа к функциям JNI. Выполнение так могло привести к произвольным последствиям, включая поврежденное системное состояние или катастрофический отказ VM.
JNI позволяет собственным методам повышать произвольные исключения Java. Собственный код может также обработать выдающиеся исключения Java. Исключения Java, оставленные необработанный, распространяются назад к VM.
Определенные функции JNI используют механизм исключения Java, чтобы сообщить о состояниях ошибки. В большинстве случаев функции JNI сообщают о состояниях ошибки, возвращая код ошибки и выдавая исключение Java. Код ошибки обычно является специальным возвращаемым значением (таким как НУЛЬ), который является за пределами диапазона нормальных возвращаемых значений. Поэтому, программист может:
ExceptionOccurred()
, получить объект исключения, который содержит более подробное описание состояния ошибки.Есть два случая, где программист должен проверить на исключения не имея возможности, чтобы сначала проверить код ошибки:
ExceptionOccurred()
проверять на возможные исключения, которые произошли во время выполнения метода Java.ArrayIndexOutOfBoundsException
или ArrayStoreException
.Во всех других случаях неошибочное возвращаемое значение гарантирует, что никакие исключения не были выданы.
В случаях многократных потоков потоки кроме текущего потока могут отправить асинхронное исключение. Асинхронное исключение сразу не влияет на выполнение собственного кода в текущем потоке, до:
ExceptionOccurred()
явно проверять на синхронные и асинхронные исключения.Отметьте, что только те функция JNI, которая могла потенциально повысить синхронную проверку исключений на асинхронные исключения.
Собственные методы должны вставить ExceptionOccurred()
регистрации необходимых мест (такой как в трудном цикле без других проверок исключения), чтобы гарантировать, что текущий поток отвечает на асинхронные исключения за разумное количество времени.
Есть два способа обработать исключение в собственном коде:
ExceptionClear()
, и затем выполните его собственный код обработки исключений.После того, как исключение было повышено, собственный код должен сначала очистить исключение прежде, чем выполнить другие вызовы JNI. Когда есть исключение на ожидании, функции JNI, которые безопасно вызвать:
ExceptionOccurred()
ExceptionDescribe()
ExceptionClear()
ExceptionCheck()
ReleaseStringChars()
ReleaseStringUTFChars()
ReleaseStringCritical()
Release<Type>ArrayElements()
ReleasePrimitiveArrayCritical()
DeleteLocalRef()
DeleteGlobalRef()
DeleteWeakGlobalRef()
MonitorExit()
PushLocalFrame()
PopLocalFrame()
Содержание | Предыдущий | Следующий |