Глубинное родство этих языков программирования позволяет им взаимодействовать, расширяя возможности каждого.
Язык 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.
Апрель
19,
2008
— Рубрика: C++
Метки: C++, Java, совместное использование
Одним из главных недостатков языка Java традиционно считается невысокая скорость работы программ по сравнению с приложениями на языке С++. И для приложений, где переносимость между платформами или сложность разработки не является критически важной, именно скорость часто была причиной, по которой разработчики делали выбор в пользу С++.
Однако опубликованные программистом Кейтом Ли результаты новых тестов показывают, что бытующее мнение о медленной работе Java не вполне справедливо.
Сравненние производительности Java vs. C++
Сравнению подвергались программы на С++, скомпилированные при помощи G++ (GCC) 3.3.1, и программы на Java, скомпилированные при помощи Sun Java 1.4.2_01. Для выполнения Java-программ использовалась виртуальная машина Sun версии 1.4.2_01. Измерения велисть на ноутбуке с процессором Pentium 4 и 512 Мб памяти, который работает под управлением ОС Red Hat Linux 9 / Fedora Test 1 с ядром версии 2.4.20-20.9 на .
В ходе тестирования выяснилось, что ключевым моментом, влияющим на производительность программ на Java являются настройки виртуальной машины. Как видно из диаграммы, при использовании “клиентского” варианта настроек (он установлен по умолчанию) практически все операции программы на Java выполняют медленнее, чем программы на C++, хотя и не так сильно, как можно было бы предположить. Зато при включении “серверных” настроек, в которых нет столь жестких ограничений по занимаемому объему памяти, преимущество в большинстве тестов оказалось на стороне Java. Причем ряд операций, например, вызов метода и хэширование, выполняется в программах на Java в несколько раз больше, чем в программах на C++. Впрочем, в основной массе тестов скорости Java и C++ оказались сопоставимыми, что, конечно, тоже может служить аргументом против мнения о медленной работе Java.
Апрель
13,
2008
— Рубрика: C++
Метки: C++, Java, производительности
Несмотря на отсутствие сборки мусора в C++, его, в общем-то, несложно реализовать самому. Предлагаю один из примеров такой реализации.
1. Опишем базовый класс, который будет прародителем всех объектных классов. Назовем его, скажем, CUniObject. Единственным членом класса будет счетчик ссылок (unsigned int count). Счетчик ссылок будет приватным.
2. Описываем класс универсального указателя CUniPointer и вместо указателей на объекты будем создавать экземпляры этого класса. Для поддержки безопасности типов класс CUniPointer следует объявить шаблонным (например, так: template class CUniPointer), где в качестве параметра будет использоваться тип, на который мы хотим указывать. Тип, очевидно, будет производным от CUniObject, поэтому класс CUniPointer может смело ссылаться на basetype::count. Собственно указатель будет являться членом этого класса: basetype *pointer.
3. Чтобы класс указателя мог иметь непосредственный доступ к счетчику ссылок своего типа-параметра, класс CUniPointer следует объявить дружественным в каждом производном классе от CUniObject (friend class CUniPointer<ЭтотКласс>).
4. Объявим два конструктора: первый - конструктор по умолчанию - предназначен для создания неинициализированных объектов-указателей (pointer = NULL), второй - для инициализированных. Последний будет принимать параметром ссылку на другой объект. Другая версия инициализирующего конструктора будет принимать параметр-указатель (для того, чтобы мы могли объявить переменную вот так: CUniPointer ptr = new SomeType). Причем конструктор по умолчанию инициализирует pointer значением NULL, счетчик ссылок при этом, конечно, не затрагивается. Остальные конструкторы увеличивают счетчик ссылок объекта, находящегося по адресу pointer, на 1: pointer->count++.
5. В момент присваивания объекту-указателю нового значения счетчики ссылок старого объекта и нового объекта следует также менять. Для этого перегрузим оператор = в классе CUniPointer примерно таким образом:
template CUniPointer
&CUniPointer::operator = (basetype *new_ptr)
{
if (pointer != NULL)
pointer->count–;
pointer = new_ptr;
if (pointer != NULL)
pointer->count++;
return *this;
}
Таким образом, если раньше объект-указатель на что-то указывал, счетчик ссылок этого объекта уменьшим, поскольку теперь объект-указатель на него указывать не будет. Напротив, счетчик ссылок нового объекта увеличим, конечно, в том случае, если новый указатель не пуст.
6. Деструктор объекта-указателя должен также уменьшать счетчик ссылок, если pointer не пуст.
7. Остается добавить в оператор = и в деструктор специальный код, проверяющий счетчик ссылок объекта на равенство нулю, и уничтожающий объект в случае равенства. Таким образом, окончательный вид оператора = будет таков:
template CUniPointer
&CUniPointer::operator = (basetype *newptr)
{
if (pointer != NULL)
if (–(pointer->count) == 0)
delete pointer;
pointer = newptr;
if (pointer != NULL)
pointer-count++;
return *this;
}
Дополнительно ко всему этому можно перегрузить оператор = с параметром типа CUniPointer и реализовать оператор ->, который упростит доступ к членам объекта pointer, так, что объект CUniPointer будет работать как обычный указатель:
template
basetype *CUniPointer::operator -> ()
{
return pointer;
}
В результате этих действий мы, по сути, получим эмулятор сборщика мусора.
Замечу, что в стандарте C++ предусмотрен специальный класс - автоуказатель - который несколько похож на описанный выше CUniPointer и самостоятельно уничтожает содержащийся внутри него указатель в своём деструкторе. Это класс template class autoptr, и находится он в пространстве имен std. Есть очень важное отличие: autoptr не содержит счетчика ссылок и освобождает память в любом случае. Значит, если вы опишете функцию, в которой объявите экземпляр переменной autoptr, и значение указателя, содержащегося в ней, вернёте этой функцией, вы, по сути, вернете указатель на свободную память, не занятую никаким объектом, т. к. при выходе из вашей функции объект autoptr будет уничтожен и вместе с ним будет уничтожен ваш указатель. Поэтому autoptr - это не сборщик мусора, а именно автоматически уничтожаемый указатель. Тем не менее, он находит применения.
Остается только отметить, что и класс autoptr, и CUniPtr обладают хорошим свойством: прекрасно работают в условиях обработки исключений. Другими словами, объект autoptr или CUniPtr автоматически будет уменьшать счетчик ссылок и уничтожать указатель при условии счетчик = 0 даже тогда, когда произойдет исключение.
В следующей статье я расскажу об управлении памятью “изнутри” - как устроена система управления памятью с точки зрения операционных систем, языков программирования, сред разработки.
Апрель
11,
2008
— Рубрика: C++
Метки: C++, управлении памятью