TL; DR : множество факторов, среди которых двоичная совместимость, поддержка многопоточности и требуемые характеристики интерфейса, привели к разработке JNI и парадигмы вызова C, которая следует из него.
C JNI вызывает идиому
, так зачем нас заставлять
(*env)->NewGlobalRef((*env), my_object);
? Дополнительный уровень косвенности не передается в функции в качестве первого аргумента, поэтому они не могут обновить этот указатель.
Это неправильная форма для вызова JNI, Как свидетельствует спецификация , и это действительно ясно (i sh) из распределенных заголовочных файлов JNI, правильная форма будет
JNIEnv *env = /* ... */;
(*env)->NewGlobalRef(env, my_object);
Обратите внимание, что
- В API C
JNIEnv
сам по себе является типом указателя (как необходимо для применения оператора ->
к *env
), и особенно - Это само по себе
env
, а не *env
, который передается в функцию JNI. Таким образом, дополнительная косвенность равна , передаваемой функциям JNI , вопреки утверждению вопроса.
Соображения JNI
Имея Потребовалось некоторое время, чтобы подумать об этом и прочитать некоторые информативные разделы спецификации JNI , однако я передумал о том, что дизайн специально предназначен для создания чистого интерфейса C ++ за счет чуть менее чистого C интерфейса. Единственная техническая причина, по которой интерфейс JNI C должен быть спроектирован для использования вызывающей идиомы, это то, что функции JNI могут заменять одну среду на другую, но нет никаких причин думать, что любая функция JNI делает это или что предполагалось, что любая когда-либо будет. (Больше комментариев об этом выборе дизайна позже.)
Глава 1 спецификации содержит столько официальной истории, сколько доступно. В нем обсуждаются историческая справка и цели проектирования для текущей (второй) основной итерации JNI. В частности, позиция Sun заключалась в том, что хорошо разработанный интерфейс будет иметь следующие преимущества:
- Каждый поставщик виртуальных машин может поддерживать больший объем собственного кода.
- Инструмент сборщикам не нужно будет поддерживать различные виды собственных интерфейсов методов.
- Разработчики приложений смогут писать одну версию своего собственного кода, и эта версия будет работать на разных виртуальных машинах.
После консультации с различными заинтересованными сторонами, они пришли к следующим требованиям проектирования высокого уровня:
- Двоичная совместимость - основная цель - двоичная совместимость нативных библиотек методов для всех Java Реализация ВМ на данной платформе. Программисты должны поддерживать только одну версию своих собственных библиотек методов для данной платформы.
- Эффективность - Для поддержки критичного ко времени кода интерфейс собственных методов должен накладывать небольшие накладные расходы. Все известные методы, обеспечивающие независимость от ВМ (и, следовательно, двоичную совместимость), несут определенную нагрузку. Мы должны каким-то образом найти компромисс между эффективностью и независимостью от виртуальной машины.
- Функциональность - интерфейс должен предоставлять достаточно Java внутренних компонентов виртуальной машины, чтобы нативные методы могли выполнять sh полезных задач.
Документы express высоко ценят COM как интерфейсную технологию, которая решает эти задачи, и, действительно, Microsoft создала интерфейс COM для своей Java 1 VM. Но, конечно, у COM также есть некоторые проблемы, не только в отношении технических деталей по сравнению с Java, но также и с незначительным (не) наличием на интересующих платформах, включая собственную Sun Solaris. Следовательно, и я думаю, что это может быть верным ответом на поставленный вопрос:
Хотя объекты Java не представлены в собственном коде как объекты COM, сам интерфейс JNI является двоично-совместимым с COM. JNI использует ту же структуру таблицы переходов и соглашение о вызовах, что и COM. Это означает, что, как только станет доступна кроссплатформенная поддержка COM, JNI может стать интерфейсом COM для Java VM.
(Акцент в оригинале .)
Окончательный проект JNI
В спецификации дается высокоуровневое описание того, что означает наличие COM-конгруэнтной формы , причем ключевая часть:
Собственный код обращается к Java функциям виртуальной машины путем вызова функций JNI. Функции JNI доступны через указатель интерфейса. Указатель интерфейса - это указатель на указатель. Этот указатель указывает на массив указателей, каждый из которых указывает на интерфейсную функцию. Каждая интерфейсная функция имеет предопределенное смещение внутри массива.
Это именно то, что мы на самом деле видим, и spe c переходит к express, как это похоже на C ++ таблица виртуальных функций и интерфейс COM. Также поясняется, что использование таблицы функций имеет следующие преимущества:
- изоляция пространства имен JNI от собственного пространства имен
- , позволяющее одной и той же виртуальной машине предоставлять альтернативные таблицы функций в разных контекстах
Кроме того, он объясняет, что предоставление двойного указателя на таблицу функций облегчает представление разных таблиц в разных потоках:
Указатель интерфейса JNI действителен только в текущем потоке. Поэтому нативный метод не должен передавать указатель интерфейса из одного потока в другой. ВМ, реализующая JNI, может выделять и хранить локальные данные потока в области, указанной указателем интерфейса JNI.
Собственные методы получают указатель интерфейса JNI в качестве аргумента. Виртуальная машина гарантированно передает тот же указатель интерфейса на собственный метод, когда она выполняет несколько вызовов собственного метода из одного и того же потока Java. Однако собственный метод может вызываться из разных потоков Java и, следовательно, может получать разные указатели интерфейса JNI.
(«Указатель интерфейса JNI» - это вышеупомянутый двойной указатель, тип которого выражен в C JNI как JNIEnv *
.)
Заключение
Парадигма вызова C следует непосредственно из этих данных и дизайна интерфейса. Указатель на интерфейс JNI должен быть разыменован, чтобы получить указатель на таблицу функций, и сам указатель на интерфейс, а не указатель на таблицу функций, передается каждой функции.
То же самое происходит и в C ++ API, но его маскируют, заключая указатель таблицы функций в класс и маскируя указатель интерфейса JNI как указатель на экземпляр этого класса. Это также дает возможность предоставлять функции-оболочки, которые маскируют тот факт, что указатель интерфейса JNI передается функциям JNI. Я воспринимаю это как хорошее использование возможностей C ++ для обеспечения простого и естественного интерфейса на этом языке, а не как свидетельство C ++ - первого подхода к проектированию JNI.