Сигнатура метода

Сигнатура метода - это сокращенная форма записи параметров метода и типов возвращаемого значения. Следует подчеркнуть, что в сигнатуру не входят ни имя метода, ни имена параметров. JNI формирует сигнатуры в соответствии с правилами, представленными в табл. 1.

Таблица 1Знак сигнатуры JAVA-тип
Z BOOLEAN
B BYTE
C CHAR
S SHORT
Internet INT
J LONG
F FLOAT
V VOID
D DOUBLE
L полное квалифицированное имя класса полное квалифицированное имя класса
[ тип тип[]
(типы аргументов) возвращаемый тип полная сигнатура метода

Проиллюстрируем эти правила на примерах:
метод LONG M1(INT N, STRING S, INT[] ARR);
сигнатура (ILJAVA/LANG/STRING;[I)J;
метод VOID M2(FLOAT N, BYTE[][] ARR, RUNTIME R);
сигнатура (F[[BLJAVA/LANG/RUNTIME;)V.

Полная информация о правилах образования сигнатуры метода представлена в файле SIGNATURE.H.

Апрель 30, 2008 — Рубрика: C++
Метки: ,

Создание заголовочного файла

Создание С/С++-кода необходимо начинать с создания заголовочного файла. Его можно написать вручную или воспользоваться утилитой JAVAH. Второй путь предпочтительней, так как допускает меньшее количество ошибок. При обращении к утилите JAVAH указывается имя класса и параметр -JNI. Без него JAVAH будет генерировать файл в формате JDK 1.0 NI. Имя класса представляет собой полное квалифицированное имя класса. Например:
JAVAH -JNI JAVA.LANG.RUNTIME

Перед использованием утилиты JAVAH соответствующий JAVA-класс должен быть скомпилирован в CLASS-файл. Утилита JAVAH анализирует CLASS-файл и строит заголовочный файл, в котором перечислены объявления С/С++-функций, представляющих реализации соответствующих собственных методов. В качестве имен создаваемых заголовочных файлов используются полные квалифицированные имена классов, которые описаны в указанном файле и содержат собственные методы. Например, если выполнить следующие команды:
JAVAC APP.JAVA
JAVAH -JNI SYSTEMSPECIFIC

то JAVAH сгенерирует следующий файл SYSTEMSPECIFIC.H:
/* DO NOT EDIT THIS FILE - IT IS MACHINE GENERATED */
#INCLUDE
/* HEADER FOR CLASS SYSTEMSPECIFIC */
#IFNDEF _INCLUDED_SYSTEMSPECIFIC
#DEFINE _INCLUDED_SYSTEMSPECIFIC
#IFDEF _ _CPLUSPLUS
EXTERN “C” {
#ENDIF
/*
* CLASS: SYSTEMSPECIFIC
* METHOD: DOSPECIFIC
* SIGNATURE: ()V
*/
JNIEXPORT VOID JNICALL JAVA_SYSTEMSPECIFIC_DOSPECIFIC(JNIENV *, JOBJECT);
#IFDEF _ _CPLUSPLUS
}
#ENDIF
#ENDIF

Как указывалось выше, данный файл можно создать вручную или с помощью утилиты JAVAH. В последнем случае не рекомендуется вносить в него какие-либо изменения, так как при последующем применении JAVAH к данному классу все внесенные изменения будут потеряны.

Директива препроцессора #INCLUDE включает файл JNI.H (из подкаталога INLCUDE основного каталога JAVA), в котором находятся все необходимые объявления типов и функций для реализации собственного метода.

Макросы JNIEXPORT и JNICALL необходимы только для платформы WIN32, где они раскрываются соответственно в __DECLSPEC(DLLEXPORT) и __STDCALL и позволяют более эффективно строить DLL. Платформа UNIX использует для этих целей обычные С-соглашения, поэтому указанные макросы раскрываются в пустые строки.

Как видно из примера, имя С/С++-функции значительно отличается от имени собственного JAVA-метода. Важным понятием при построении имени С/С++-функции и использовании JNI-функций является сигнатура метода (SIGNATURE или METHOD ARGUMENTS SIGNATURE).

Апрель 28, 2008 — Рубрика: C++
Метки: ,

Ресурсоемкие операции

Вместе с тем следует помнить, что существуют операции, выполнение которых само по себе требует больших ресурсов, чем для обычных запросов. Например, использование операции DISTINCT к функции SELECT вызывает потребление гораздо большего количества процессорного времени, чем обычный SELECT. DISTINCT пытается искать уникальные значения, зачастую производя множество сравнений, подстановок и расчетов. Причем, чем больше становится объем данных, к которому применяется DISTINCT (ведь Ваша база со временем растет), тем медленее будет выполняться такой запрос и рост ресурсов, требуемых для выполнения такой функции, будет происходить не прямо пропорцонально объему хранимых и обрабатываемых данных, а гораздо быстрее.

Апрель 25, 2008 — Рубрика: C++
Метки:

C++ и Java: совместное использование

Глубинное родство этих языков программирования позволяет им взаимодействовать, расширяя возможности каждого.

Язык 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++
Метки: , ,

Java обгоняет по производительности C++

Одним из главных недостатков языка 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++

Несмотря на отсутствие сборки мусора в 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++
Метки: ,

Сборщик мусора как средство автоматического контроля памяти

В системах, имеющих “сборщик мусора”, программисту не нужно вручную освобождать занятую им память - этим занимается сборщик мусора. Примеров таких систем можно привести множество: .NET, Java, Visual Basic: Следует отметить, что кроме наличия сборки мусора большинство таких языков характеризуются еще и нетипизированностью - объявляя переменную, программист не ограничивает ее тип и может хранить в ней всё что угодно.

Как это происходит? На самом деле всё достаточно просто - достаточно к каждому объекту “привязать” счетчик ссылок, который увеличивается каждый раз, когда на выделенный блок памяти ссылается какая-то переменная, и уменьшается, когда в эту переменную записывается другое значение, т. е. ссылка на объект теряется. К примеру, мы объявили некоторую переменную и тут же создали объект, присвоив ссылку на него этой переменной. Тут же счетчик ссылок этого объекта становится равным 1, т. к. наша переменная ссылается на объект. Теперь мы вызываем некоторую функцию, в качестве одного из параметров которой передаем нашу переменную. Тут счетчик ссылок следует опять увеличить, и он станет равным 2. Естественно, в момент выхода из вызванной функции он вернется в состояние 1. Если мы объявим вторую переменную и присвоим ей значение первой - счетчик вновь увеличится. Как только счетчик ссылок достигает нуля, объект можно удалять, поскольку адрес объекта нигде, ни в какой переменной или блоке памяти не содержится, таким образом, программа “забыла” ее адрес и всё равно никак не сможет прочитать или изменить объект.

В некоторых системах удаление объектов производится именно в тот момент, когда счетчик ссылок становится нулевым. В некоторых момент освобождения памяти объекта откладывается, пока у процессора не появится свободное время (он не будет ничем особенно занят) или свободной памяти не останется.

Кроме очевидного преимущества, у системы сборки мусора есть свои подводные камни. В этих системах невозможно или сложно реализовать “ручные” механизмы выделения и освобождения памяти - к примеру, если на C++ вы можете с легкостью запросить память у операционной системы огромным куском, а внутри этого большого “куска” реализовать свои механизмы выделения/освобождения памяти, перегрузив операторы new и delete, вы теоретически сможете путем ручного управления памятью ускорить работу своего приложения (если, конечно, сможете придумать алгоритм, более оптимальный, чем стандартные), то в таких языках и средах вы этого сделать не сможете. А стандартные методы освобождения и выделения приходится иногда переписывать - например, если вы хотите хранить данные в файле и использовать отображение файлов на память. Другой подводный камень - система сборки мусора может физически переместить объект (естественно, поскольку адрес объекта изменился, все ссылки на него стали недействительными - их надо исправить, и это дело системы сборки мусора). Таким образом, вы не можете “доверять” адресу переменной (если вообще есть возможность его получить), так как он непостоянен. Кроме того, чтобы у сборщика мусора была возможность перемещать объекты физически, объект должен содержать не просто счетчик ссылок, а список самих ссылок, что увеличивает расходы (как расход памяти, так и ресурс времени) на содержание объектов.

Апрель 9, 2008 — Рубрика: C++
Метки: ,

Управление памятью под ответственностью программиста

В языке C++ (именно на его примере рассмотрим системы, где ответственность за своевременное освобождение блоков памяти лежит на программисте) существуют предусмотренные стандартом C++, а также перешедшие “в наследство” от C средства выделения и освобождения памяти. Кроме того, каждая платформа предоставляет свои средства управления памятью (как правило, стандартные языковые средства как раз к ним и обращаются, но у программиста есть возможность обратиться к ним непосредственно). Проблема этих систем и языков (в том числе C++) состоит в том, что программист сам отвечает за своевременное освобождение блоков памяти после того, как в них отпадает необходимость - для этого тоже есть специальные функции. Если программист забудет освободить блок, впоследствии может возникнуть нехватка памяти - виртуальное адресное пространство процесса “забьется” и свободного места не останется, хотя в то же время в памяти будут присутствовать блоки, которые фактически не используются.

Рассмотрим коротко средства управления памятью.

Оператор new очень удобен - он не требует указания размера блока, который нужно выделить. Размер определяется компилятором автоматически исходя из типа, который указывается после ключевого слова new. Кроме вызова самой функции выделения памяти происходит еще и вызов конструктора объекта, если указан объектный тип. Синтаксис вызова оператора имеет несколько вариантов, рассмотрим наиболее часто употребляемый:
new Type (parameters)

где Type - тип создаваемого объекта, на основе которого компилятор автоматически определит требуемый для него объем памяти;

parameters - необязательный список параметров, который будет передан конструктору объекта. В случае отсутствия списка параметров скобки указывать необязательно.

Функция malloc, доставшаяся языку C++ в наследство от C, требует указания необходимого количества байт и не производит кроме собственно выделения памяти никаких дополнительных действий.

Функции операционной системы Windows - LocalAlloc и GlobalAlloc - считаются устаревшими, хотя и поддерживаются в целях совместимости. Современным приложениям рекомендуется пользоваться HeapAlloc, а также VirtualAlloc, которая, помимо выделения памяти, поддерживает операцию резервирования памяти и выделения зарезервированной памяти.

Соответственно, средства освобождения памяти следующие.

Оператор delete освобождает занятый приложением блок, но перед этим вызывает деструктор объекта, если переменная объектного типа. Ничего, кроме параметра, содержащего адрес удаляемого блока, передавать не надо.

Функция free () освобождает выделенный с помощью malloc () блок. Следует передать адрес блока.

Функции Windows по освобождению памяти называются LocalFree, GlobalFree, HeapFree и VirtualFree.

Объясню суть проблемы такого управления памятью подробнее. Допустим, программисту нужно в цикле копировать файлы. Для этого он выделяет блок памяти необходимого размера, читает туда содержимое файла-источника, затем пишет содержимое блока в файл-приемник. Допустим теперь, что перед выходом из цикла человек забыл освободить выделенный блок. Начинается следующая итерация цикла, и происходит новое выделение памяти. Переменная, которая раньше содержала адрес первого блока, содержит теперь новый адрес - таким образом, адрес первого блока теряется! Значит, если вовремя не освободить выделенную память, есть вероятность, что адрес этого блока будет навсегда “забыт” приложением, и оно никогда не сможет его освободить.

Вообще-то, по завершении приложения операционная система просто полностью уничтожает виртуальное адресное пространство процесса, так что об этом, казалось бы, беспокоиться даже не стоит - память освободится автоматически при окончании работы программы. Однако, как упоминалось выше, неосвобожденные блоки будут накапливаться, а доступная для выделения свободная память будет заканчиваться, и это может привести к ситуации, когда система откажет вам в очередном выделении памяти. Особенно эта ситуация вероятна тогда, когда программист забывает об освобождении в цикле, тем более если тот имеет много итераций.

Апрель 7, 2008 — Рубрика: C++
Метки: , ,

Оперативная память. Эпизод III. Управление памятью в приложениях

Введение

По управлению памятью современные языки программирования и средства разработки можно условно разделить на те, где программист ответственен за управление памятью и обязан вовремя освобождать неиспользуемые блоки, и на языки с так называемой сборкой мусора. Рассмотрим основополагающие принципы и тех и других. Кроме того, в этой статье я предложу способы автоматизации управления памятью в тех системах, где управлением памятью занимается программист, а также плюсы и минусы сборки мусора.

Апрель 5, 2008 — Рубрика: C++
Метки:

Перенаправление stdout в область памяти (или файл)

Для перенаправления stdout в область памяти (или файл) необходимо написать небольшую конструкцию.
Редирект в память или строку (std::string)
// make stdout buffer
char buf[16384]={0};
int fdpipe[2];
// make pipe
_pipe( fdpipe, sizeof(buf), O_BINARY );
// backup stdout handle
int old=_dup(_fileno(stdout));
// replace stdout handle with write-pipe
_dup2(fdpipe[1], _fileno(stdout));

// test output
fprint(stdout,”test”);

// get collected buffer
int r = read(fdpipe[0],buf,sizeof(buf));
buf[r]=0;
// restore original stdout
_dup2(old, _fileno(stdout));
// make string
std::string str(buf);

Пример для работы с STL std::cout
std:stringstream oss;
std::cout.rdbuf( oss.rdbuf() );
std::cout << “here’s some text”;
Пример редиректа в файл
stream = freopen( “freopen.out”, “w”, stderr );
fprintf( stdout, “successfully reassigned\n” );
fclose( stream );

Апрель 1, 2008 — Рубрика: C++
Метки: , ,