[ главная ]   [ рейтинг статей ]   [ справочник радиолюбителя ]   [ новости мира ИТ ]



Ответов: 0
25-02-12 07:01







   Web - программирование
PHP


ASP






XML



CSS

SSI





   Программирование под ОС











   Web - технологии








   Базы Данных









   Графика






Данные
Виды страхования грузов при международных перевозках inslog.ru.




Программирование под ОС / Java /

Совместная отладка Java и C/C++ кода

Два подхода к использованию JNI

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

При программировании некоторых приложений нет возможности обойтись чистым Java-кодом. Иногда возникает необходимость использовать C/C++, или некоторые API, для которых существуют интерфейс под C/C++, или же вам просто нужно получить возможность делать обращения к системным функциям операционной системы (например, для доступа к Windows Registry), эквивалентов которых нет в библиотеках классов Java. Проблема же состоит в том, что отладчиков, которые бы могли работать с приложениями, написанными на Java и C, нет.

Примечание: При написании этой статьи, использовались ОС Windows 2000, Java Debugger (JDB) и GNU Debugger (GDB). Однако все, рассмотренные в этой статье приемы, могут быть с легкостью применены к UNIX/Linux платформам. Более подробную информацию о каждом из упомянутых отладчиков можно получить, воспользовавшись списком ресурсов, в конце статьи.

В чем, собственно, состоит проблема? Операционная система воспринимает виртуальную Java-машину (JVM) как еще одно приложение. Следовательно, подход у операционной системы к JVM такой, как и к обычному приложению. Любой процесс отладки, при котором используется Java-отладчик (JDB), это, с точки зрения операционной системы, процесс, происходящий на уровне приложения. Другое дело “родные” приложения, для отладки которых, используются более привилегированные операции. (Для простоты далее будем употреблять native, вместо слова “родной”).

Java Native Interface (JNI) позволяет интегрировать обычные приложения с Java-приложениями, что, по сути, и является причиной возникновения проблемы отладки, о которой мы говорили выше.

Что такое JNI?

Изначально, люди, проектировавшие язык программирования Java, понимали, что возможность создания на 100% чистых Java-приложения – это благородная цель. Но они также понимали, что будет очень трудно обойтись только лишь средствами языка Java. По мере того как разрабатываются все новые Java-приложения, разработчикам часто очень не хочется выбрасывать старый native-код, который может быть достаточно дорогим, и который было не легко написать.

JNI (Java Native Interface) предоставляет разработчику возможность выйти за рамки определенные виртуальной Java-машиной и получить доступ ко всем возможностям компьютера, на котором запущена Java-машина. Таким образом, разработчики могут использовать свой старый код или писать новый, который невозможно было бы реализовать средствами языка Java. Главный недостаток JNI состоит в том, что при его использовании теряется суть идеи WORA (Write Once, Run Anywhere), поскольку любой native-код прочно связывает ваше Java-приложение с какой-либо платформой. Чтобы получить более подробную информацию о JNI, посмотрите список ресурсов в конце этой статьи.

Существует, по крайней мере, две основные структуры построения приложений с использованием JNI. Первый – это стандартный способ, а второй, соответственно, нестандартный, основывающийся на вызове Java-машины после отработки некоторого native-приложения.

Сущность стандартного подхода к написанию JNI-приложений состоит в том, что вы создаете некоторую native-библиотеку, и позже используете ее в своем Java-приложении. При таком подходе ваше Java-приложение может быть запущено привычным для всех образом, например, следующей командой: “java myApplication”.

Чтобы вам посмотреть на примере как работает этот стандартный метод использования JNI, скачайте архив с кодом, по адресу, который указан в списке ресурсов в конце статьи.
Файл называется std.zip и содержит следующие файлы:

  • jnitest.c
  • JNITest.class
  • jnitest.dll
  • JNITest.h
  • JNITest.java
  • jnitest.o
  • libtstdll.a

Непосредственно Java-код содержится в файле JNITest.java. В нем содержится метод main(), который загружает narive-библиотеки, а также методы instance() и static(). Помимо этого здесь также определяется метод native() следующим образом: public native int native_doubleInt(int value).

Файл jnitest.c содержит исходный C-код. Здесь определена реализация метода native_doubleInt().

Второй подход к написанию JNI-приложений заключается в том, что вы запускаете виртуальную Java-машину тогда, когда вам это нужно. Предположим, вы написали какой-нибудь native-артефакт на C, который использует какую-нибудь библиотеку Java-классов, и вы хотите, чтобы это приложение на C могло пользоваться этой библиотекой. В этом случае, ваша основная программа на C будет запускать JVM тогда, когда ей это необходимо.

Для реализации такого подхода необходимо воспользоваться Invocation API, который позволяет вам загружать JVM в произвольное native-приложение. Вообще, каждое Java-приложение запускается с использованием Invocation API. Сначала java.exe, программа-загрузчик, использует Invocation API для того, чтобы запустить виртуальную машину, после чего запускает основной класс. То, насколько правильное у вас представление о запуске Java-программ, определит глубину восприятия материала, изложенного в этой статье.

В разделе Ресурсы, в конце статьи, вы также можете найти адрес архива с примером для такого invocation-подхода. Архив называется invocation.zip и содержит следующие файлы:

  • jvm.def
  • libjvm.a
  • main.c
  • main.exe
  • main.o

В этом примере используется тот же Java-код, что и в примере стандартного подхода. main.c – это C-код, который компилируется в исполняемый файл, в нашем случае main.exe.

Метод goTest() вызывает статический метод Java-класса. (Он был создан статическим с тем, чтобы сделать JNI-код несколько проще). Непосредственно, перед вызовом метода goTest(), выполняется бесконечный цикл. Этот цикл был введен для отладочных целей. Зачем? Это станет понятно позже.

Инструменты

Здесь приведено несколько примеров отладчиков Java и C/C++ (как open-source, так и коммерческих), которые вы можете использовать для отладки ваших Java/C-приложений. Заметьте, что, как Java-, так и C/C++ приложения должны быть скомпилированы вместе с отладочной информацией. Для этого, вам нужно при компиляции добавить несколько ключей. В случае с Java – это ключ –g для компилятора javac. Для C вам необходимо ознакомиться с опциями, используемого вами компилятора, например, посмотрите раздел этой статьи “GCC компиляция”, в которой описан процесс компиляции с помощью GCC.

Java-отладчики:

-          JDB – это стандартный отладчик, включенный в поставку JDK и который был использован при написании этой статьи.

-          IBM VisualAge for Java поставляется со своим отладчиком. Мощный графический отладчик может использоваться отдельно от основного инструмента.

-          JSwat – великолепный open-source графический Java-отладчик.

C/C++ отладчики:

-          GDB – это GNU Debugger, и именно он использовался при написании этой статьи.

-          GCC и смежные с ним инструменты содержат множество портированных вещей для Windows. Один – это Cygwin, который предоставляет большое количество UNIX-подобных инструментов, которыми вы можете воспользоваться. Другой – это MinGW (Minimalist GNU for Windows), небольшой инструментарий, но уже без абстрактного слоя, который предоставляет Cygwin.

-          Множество коммерческих инструментов, например, VisualAge, Microsoft Visual C++, Borland, Code Warrior и другие предлагают удобный графический интерфейс для отладчиков, что делает их более простыми в использовании.

Отладка программы первого типа

Итак, давайте приступим к работе. Представьте, что вам нужно запустить вашу Java-программу и после присоединить к этому процессу C-отладчик. Такое можно организовать с помощью отладчиков, как, например, Visual C++, запустив исполняемый файл Java напрямую. В этом случае вам необходимо добавить вашу DLL в список дополнительных DLL-библиотек; в противном случае, когда ваше приложение будет запущено, точка прерывания установлена не будет.

Java-приложение содержит вызов System.loadLibrary, который будет динамически загружать нашу DLL. (Замечание: Этот пример демонстрирует, как производить отладку с совместным использованием JDB и GDB. Если вы не хотите использовать JDB, вы можете убрать режим ожидания пользовательского ввода из Java-приложение после вызова System.loadLibrary). Итак, процесс отладки укладывается в следующие 7 шагов:

  1. Запуск Java-приложения в отладочном режиме.
  2. Из другого окна, привязываем JDB к JVM.
  3. Устанавливаем точку прерывания.
  4. Привязываем GDB к JVM.
  5. Используем JDB, чтобы продолжить работу JVM.
  6. Выпускаем JVM через окно GDB.
  7. Проходимся отладчиком по native-коду.

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

Шаг 1. Первый шаг заключает в том, чтобы запустить Java-программу в отладочном режиме и присоединить к этому процессу Java-отладчик. В следующих примерах, мы использовали Java Platform Debugger Architecture (JPDA) вместо более старого отладочного Java-интерфейса.

Листинг 1. Использование JPDA для запуска Java-приложения  в отладочном режиме


C:\_jnistd>java –Xdebug -Xnoagent -Djava.compiler=none 
    -Xrunjdwp:transport=dt_socket,server=y,suspend=y JNITest

Команда suspend=y остановит виртуальную Java-машину, 
как только та подготовит порт, 
к которому может подключиться Java-отладчик. 

Шаг 2. Из другого окна присоединяем JDB к только что запущенной JVM. JDB должен иметь доступ к исходному коду и файлам классов отлаживаемого приложения. Пути к ним могут быть определены в командной строке JDB, с помощью ключей –sourcepath и –classpath. Как альтернативный вариант можно запускать JDB из директории, где находятся все эти файлы, тогда отпадает необходимость определять все эти опции с указанием путей.


C:\_jnistd>jdb -attach 1060

Initializing jdb...

main[1]

VM Started: No frames on the current call stack

main[1]

Шаг 3. Теперь мы можем установить точку прерывания в строке сразу после вхождения вызова System.loadLibrary.


main[1] stop at JNITest:22

Deferring breakpoint JNITest:22.

It will be set after the class is loaded.

main[1] run

> Set deferred breakpoint JNITest:22

Breakpoint hit: thread="main", JNITest.main(), line=22, bci=21

  22         System.out.println(" ##### About to call native method ");

main[1]

Заметьте, что существует два пути установки точек прерывания в JDB. Первый, при помощи команды stop in, а второй командой stop at. Командой stop at вы можете устанавливать точку прерывания в определенной строке; stop in же используется для установки точек прерывания в определенных методах и классах.

На этом этапе JVM временно остановила свою работу, но с точки зрения операционной системы, это приложение продолжает нормально функционировать. ОС (операционная система) также заметила связь, установленную между процессом JDB и процессом виртуальной Java-машины.

Шаг 4. Теперь мы можем присоединить GDB к процессу JVM. Для того чтобы это сделать, нам нужно узнать идентификатор этого процесса (PID). Чтобы его получить, вы можете воспользоваться менеджером задач или одним из инструментов, предназначенных для этих целей, например, listdlls (из SysInternals.com). Опции –nw и –quiet позволяют запустить GDB без графического интерфейса и без вывода информации о его версии.


C:\_jnistd>gdb -nw -quiet

(gdb) attach 1304

Attaching to process 1304

[Switching to thread 1304.0x2b8]

(gdb) break jnitest.c:15

Breakpoint 1 at 0x100010cf: file jnitest.c, line 15.

(gdb)

Шаг 5. Теперь в GDB установлена точка прерывания. Воспользовавшись JDB, нам нужно дать возможность JVM продолжать свою работу. (Не забывайте, что мы также установили точку прерывания и в GDB) Наберите cont в окне с JDB. Теперь JDB позволил JVM продолжить свое нормальное функционирование.

Шаг 6. Наберите cont в окне GDB, чтобы “отпустить” JVM. Заметьте, что мы достигли установленной точки прерывания.


(gdb) cont

Continuing.

[Switching to thread 1304.0x550]

Breakpoint 1, Java_JNITest_native_1doubleInt (pEnv=0x2346c8, obj=0x6fe20,

    value=42) at jnitest.c:15

15              printf(" ***** Entering C code\n");

(gdb)

Шаг 7.  Теперь вы можете проходиться отладчиком по native-коду, что, собственно, и требовалось. Если вы вернетесь в окно с JDB и введете “stop at JNITest:26”, чтобы приостановить приложение до того, как оно вызовет native-метод во второй раз, оно не будет отвечать. Чтобы это сделать, вам следует сначала сделать это в GDB.

Проход по native-коду и JNI-вызовам в GDB может показаться сложным. Вам может понадобится дважды вводить next, чтобы проходить по вызовам.

Лучше всего – расставлять точки прерывания, что позволит заметно ускорить процесс отладки, поскольку JNI-вызовы могут занимать некоторое время на выполнение, при работе в GDB-окружении.

Отладка программы второго типа

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

Заметьте, что опции в этом случае такие же, как и в предыдущем примере в листинге 1. Если вы напрямую запустите эту программу, то получите что-то похожее:


Listening for transport dt_socket at address: 1068

   #### java

В этом примере, мы использовали C-код, чтобы вызвать Java-метод, который утраивает числа. Теперь нам нужно привязать JDB к этому методу (порт, к которому нужно привязывать JDB мы уже получили). Самое сложно в таком стиле отладки – это позволить JDB обеспечить полную взаимосвязь с JVM, которая находится под контролем GDB.

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

  1. Загрузка приложения в GDB; установка точки прерывания.
  2. Повторять проход по циклу в GDB.
  3. С помощью GDB прервать цикл и продолжить отладку.
  4. Проследить, как JDB остановится в точке прерывания.

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

Шаг 1. Загружаем приложение в GDB и устанавливаем точку прерывания. Таким образом, мы сможем перемещаться по циклу. Хотя этот цикл не должен присутствовать в программах, он введен лишь в отладочных целях. Основная идея состоит в том, чтобы дать время JVM соединиться с JDB.

Шаг 2. Продолжаем проходить цикл в GDB, чтобы установить точку прерывания на Java-методе, который утраивает число. Просто продолжаем выполнять цикл, пока JDB отвечает. Мы могли бы сделать этот процесс более сложным, используя дополнительные sleep, например.


main[1] main[1] stop at JNITest:58

Deferring breakpoint JNITest:58.

It will be set after the class is loaded.

main[1]

Шаг 3. Как только вы выполнили второй шаг, вы можете прерывать цикл в GDB и продолжать отладку. Здесь мы устанавливаем точку прерывания в конец метода goTest(), чтобы дать возможность выполниться всем JNI-вызовам.


(gdb) print loop

$1 = 0

(gdb) set loop=1

(gdb) next

42         goTest(pEnv);

(gdb) step

goTest (pEnv=0x3f4e58) at main.c:53

53         javaClass = (*pEnv)->FindClass(pEnv,"JNITest");

(gdb) list

48

49         jclass    javaClass;

50         jmethodID mid;

51         jint      result;

52

53         javaClass = (*pEnv)->FindClass(pEnv,"JNITest");

54

55 mid = (*pEnv)->GetStaticMethodID(pEnv,javaClass,"java_tripleNumber","(I)I");

56

57         result = (*pEnv)->CallStaticIntMethod(pEnv,javaClass,mid,42) ;

(gdb) break main.c:58

Breakpoint 2 at 0x401474: file main.c, line 58.

(gdb) c

Continuing.

Шаг 4. Когда выполняется код, приведенный в предыдущем шаге, в окне JDB произойдет останов на точке прерывания.


Set deferred breakpoint JNITest:58

Breakpoint hit: thread="main", JNITest.java_tripleNumber(), line=58, bci=0

  58         System.out.println(" #### java ");

main[1]

Непонятные JNI-ссылки

JNI-ссылки по большей части сложны для понимания, поскольку их структура не публикуется. Если вы знаете структуру объекта, вы можете без проблем увидеть объект в памяти.

В этом куске, мы остановили C-код, сразу после вызова Java-метода. result содержит ссылку на jstring.


Breakpoint 3, goTest (pEnv=0x3f4e50) at main.c:60

60      }

(gdb) print result

$1 = 0x3fda44

В листинге 10 показан адрес переменной в памяти (0x00ad1ac8). Если вы распечатаете содержимое памяти, начиная с этой позиции, вы увидите начало строки. GDB из поставки Cygwin предлагает графический интерфейс, в котором есть окно для редактирования памяти – которое значительно упростит вам просмотр этой строки.


(gdb) x 0x3fda44

0x3fda44:       0x00ad1ac8

(gdb) x/30s 0x00ad1ac8

0xad1ac8:        "0021"

0xad1acc:        ""

0xad1acd:        ""

0xad1ace:        ""

0xad1acf:        ""

0xad1ad0:        "032-"

0xad1ad4:        ""

0xad1ad5:        ""

0xad1ad6:        ""

0xad1ad7:        ""

0xad1ad8:        "

		

			


Комментарии

 Ваш комментарий к данному материалу будет интересен нам и нашим читателям!



Последние статьи: Программирование под ОС / Java /

Особенности Java 5
23-02-2010   

Java 5 обладает некоторыми полезными возможностями. В данной статье мы рассмотрим их и узнаем, как можно извлечь из них выгоду. В этой части мы рассмотрим auto-boxing foreach... подробнее

Кол. просмотров: общее - 4882 сегодня - 2

Особенности Java 5. Часть 2
23-02-2010   

В первой части мы обсудили новые возможности Java 5 относительно функции auto-boxing и цикла foreach. В данной части мы обсудим поддержку функций с переменным числом аргументов и статическое импортирование (static import). Поскольку другие функции, такие как enum, annotation, и generics, заслуживают отдельной статьи, мы их не будет демонстрировать в данной... подробнее

Кол. просмотров: общее - 4025 сегодня - 2

Расширение функциональности элементов пользовательского интерфейса в Java
05-05-2009   

Довольно часто при создании приложений с GUI (stand alone приложений или апплетов) приходится сталкиваться с необходимостью несколько изменить внешний вид и поведение стандартных компонентов пользовательского интерфейса... подробнее

Кол. просмотров: общее - 4348 сегодня - 1

Введение в сервлеты
05-05-2009   

Что такое сервлет ? Это класс порожденный от класса HttpServlet с переопреденными методами doGet и doPost (управление приходит в один из этих методов в зависимости от того какого типа был запрос. Надеюсь у Вас есть некоторый опыт в cgi-программировании... подробнее

Кол. просмотров: общее - 3925 сегодня - 1

Java Server Pages
05-05-2009   

Технология Java Server Pages (JSP) является составной частью единой технологии создания бизнес-приложений J2EE. JSP - это альтернативная методика разработки приложений... подробнее

Кол. просмотров: общее - 4096 сегодня - 1



  WWW.COMPROG.RU - 2009-2012 | Designed and Powered by Zaipov Renat | Projects