Логин:   Пароль:






Новости
Рассылки
Форум
Поиск


Java
- Апплеты
- Вопрос-ответ
- Классы
- Примеры
- Руководства
- Статьи
- IDE
- Словарь терминов
- Скачать

Мобильная Java
- Игры
- Примеры
- Статьи
- WAP, WML и пр.

JavaScript
- Вопрос-ответ
- Примеры
- Статьи

Веб-мастеринг
- HTML
- CSS
- SSI

Разминка для ума
Проекты
Книги
Ссылки
Программы
Юмор :)




Rambler's Top100
Rambler's Top100

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

Совместная отладка 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:\_jni\std>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:\_jni\std>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:\_jni\std>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:        "\022"

0xad1ada:        ""

0xad1adb:        ""

0xad1adc:        "0"

0xad1ade:        ""

0xad1adf:        ""

0xad1ae0:        "\022"

0xad1ae2:        ""

0xad1ae3:        ""

0xad1ae4:        "*"

0xad1ae6:        ""

0xad1ae7:        ""

0xad1ae8:        "C"

0xad1aea:        "a"

0xad1aec:        "t"

0xad1aee:        " "

0xad1af0:        "s"

0xad1af2:        "a"

0xad1af4:        "t"

0xad1af6:        " "

(gdb)

Компиляция с помощью GCC

В стандартную поставку Java не входят библиотеки для GCC, поэтому процесс компиляции несколько усложняется. Например, в приведенном ниже примере, воспользовавшись инструментарием MinGW, мы используем следующую последовательность команд:

1. Собираем библиотеку для GCC, базирующуюся на def-файле. Создаем файл с именем jvm.def со следующим содержимым:


EXPORTS

        JNI_CreateJavaVM@12


Если вы запустите одну из утилит, например, dumpbin или nm для libjvm-файла, вы сможете извлечь ссылки на другие Invocation API.

2. Создаем библиотеку для вашего приложения.
dlltool -k --input-def jvm.def --dll jvm.dll --output-lib libjvm.a

3. Компилируем ваше C-приложение
gcc -Ic:\_jdk\include -g -c main.c

4. Связываем вместе основное приложение и JVM
gcc -o main.exe main.o -L./ -ljvm

Несколько приемов программирования JNI-приложений напоследок

Приведем несколько приемов, руководствуясь которыми, вы сможете избежать множества часто возникающих проблем:

- Всегда проверяйте возвращаемые значения и возникающие исключения при JNI-вызовах.

- Когда вы проверяете на факт возникновения исключения, важно знать, что если вы вызываете метод ExceptionDescribe(), вы можете получить описание исключения, которое возникло несколько раньше, чем то, что произошло только что.

- Всегда вызывайте метод threadDetach() перед тем, как уничтожать какой-нибудь из ваших потоков. Не руководствуясь этим правилом, вы можете свалить на свою голову множество серьезных проблем, связанных со “сборщиком мусора”.

- Когда вы запускаетесь посредством JPDA,  всегда используйте java.compiler=NONE. Если вы используете java.compiler=fred, это остановит JIT, но фактически не будет работать.

За более детальными инструкциями вы можете обращаться к книге “The Java Native Interface ” (Sheng Liang) или на JNI-раздел java.sun.com. Если вы предпочтете книгу “Essential JNI” (которая очень хорошо освещает основы JNI), учтите, что в ней приведены функции для Java 2, которые основываются на бета-версии JDK.

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

Ресурсы

1. Архив с примером для приложения первого типа (std.zip) : ftp://www6.software.ibm.com/software/developer/library/j-jnidebug/std.zip
2. Архив с примером для приложения второго типа (invocation.zip): ftp://www6.software.ibm.com/software/developer/library/j-jnidebug/invocation.zip
3. Следующее руководство "шаг за шагом" IBM Distributed Debugger (http://www7.software.ibm.com/vad.nsf/Data/Document2346?OpenDocument&p=1&BCT=3&Footer=1&origin=j) содержит множество сценариев использования различных отладчиков, примеры локальной и удаленной отладки, а также инструкции, как привязать отладчик к работающей виртуальной машине.
4. JNI-форум на jGuru (http://www.jguru.com/faq/JNI) (управляемый человеком по имени Davanum Srinivas) является местом активных бесед на любые темы, касающиеся JNI и его применения.
5. Здесь вы можете получить много полезной информации о GCC и смежных инструментах и утилитах: http://gcc.gnu.org/.
6. SysInternals.com – сайт бесплатного программного обеспечения для разработки под Windows, содержит такие великолепные утилиты, как Process Explorer (GUI-утилита, которая позволяет пользователям видеть DLL-библиотеки и загруженные процессы) и ListDLLs (утилита, которая отображает DLL-библиотеки, используемые загруженным процессом).
7. MinGW (http://www.mingw.org/) – это коллекция header-файлов и библиотек, которые позволяют разработчикам использовать GCC и получить native-код Windows32-программ, которые не зависят от DLL-библиотек третьих производителей.
8. Cygwin (http://www.cygwin.com/) – это UNIX-среда для Windows, которая состоит из DLL-библиотек, которые эмулируют UNIX API, а также набор портированных инструментов, чтобы приблизить эмуляцию к оригиналу.
9. http://www-106.ibm.com/developerworks/java/ - содержит много полезной информации (статей и инструментов) для отладки Java-приложений, использующих JNI и не только.



По материалам Matthew B. White. 04.2003
Подготовил Алексей Литвинюк

Warning: mysql_connect() [function.mysql-connect]: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) in /pub/home/javaport/javaportal/books/show2b.php on line 11

Warning: mysql_db_query() [function.mysql-db-query]: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) in /pub/home/javaport/javaportal/books/show2b.php on line 19

Warning: mysql_db_query() [function.mysql-db-query]: A link to the server could not be established in /pub/home/javaport/javaportal/books/show2b.php on line 19

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /pub/home/javaport/javaportal/books/show2b.php on line 30
Узнай о чем ты на самом деле сейчас думаешь тут.


[an error occurred while processing this directive]



Warning: mysql_connect() [function.mysql-connect]: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) in /pub/home/javaport/javaportal/news/worldnews.php on line 91

Warning: mysql_db_query() [function.mysql-db-query]: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) in /pub/home/javaport/javaportal/news/worldnews.php on line 93

Warning: mysql_db_query() [function.mysql-db-query]: A link to the server could not be established in /pub/home/javaport/javaportal/news/worldnews.php on line 93

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /pub/home/javaport/javaportal/news/worldnews.php on line 95