Java like С/C++

Привет всем!

Сегодня мы с вами поговорим о любви Java к C++. Связь не очевидна, но она есть. Итак, поехали!

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



    • Пускай у вас есть проверенный на 100% код на другом языке. Для того что бы его перевести на язык Java, потребуется немало времени, а полученный код нужно опять проверять и отлаживать. Особенно если это огромная библиотека или проект.

    • Или нам нужны низкоуровневые операции для доступа к аппаратным средствам, в таком случае Java не очень подходит для таких операций.

    • Или если нам нужно множественное применение сложных операций с машиной, требующих очень оптимизированного кода, чего на Java опять не добьёшься (хотя этому можно поставить в противовес just-in-time компиляцию).



Если мы всё же столкнулись с одной из вышеперечисленных проблем, то, скорее всего нам-таки придётся научиться вызывать платформенно-ориентированный код прямиком из Java. Для этого в Java предусмотрены специальные способы работы с системными библиотеками, а JDK содержит инструменты, которые позволят нам справиться с этими проблемами.

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

Но выбор за вами, стоит ли это делать в ситуации, с которой вы столкнулись или нет ;)

Я не буду описывать кучу методов, кучу ситуаций, и тому подобное, это лучше всего расскажет специализированная книжка, а тут будет приведены примеры почти стандартных задач, вида Hello “damn” world! ;)

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

Для того, чтобы в Java показать, что функция будет платформенно-ориентированной, есть ключевое слово “native”, которое предупреждает компилятор о внешнем определении данного метода. Саму функцию нужно описывать в классе.

public static native void welcome(String str, int value);

В данном случае функция объявлена как статическая, но это не обязательное условие. Теперь поместим её в класс:

class Hello {

      public static native void welcome(String str, int value);

}

Такой класс откомпилировать вы сможете, но вылетит исключение, говорящее, что программа не может найти метод. Для того? чтобы метод был найден, его тело нужно создать на C/C++, причём не просто метод, а метод говорящий что его будут вызывать из кода на Java. Но об этом попозже, просто знайте, что мы создадим разделяемую библиотеку для экспорта на C/C++, а далее загрузим её в Java. И только тогда программа найдёт этот метод и сможет успешно выполняться.

Библиотеку удобнее всего подключить в статическом блоке, чтобы при вызове функции, она уже была подгружена. И вот что получается:

class Hello {

   public static native void welcome(String str, int value);

   static  {

      System.loadLibrary("JAVALOVECHELLO");

   }

}

Метод loadLibrary("JAVALOVECHELLO"), загружает библиотеку по имени JAVALOVECHELLO.

Теперь настало время для создания C/C++ функции, имя которой должно строго (значит точно) соответствовать соглашениям об именовании:



    • Имя метода в C/C++ должно полностью соответствовать ПОЛНОМУ имени в Java программе. Например, если мы поместим программу в пакет com.intel, тогда полное имя Java-метода будет com.intel.Hello.welcome. Теперь нужно заменить все точки на нижние подчёркивания (_) и добавить префикс “Java_”, получим строку Java_com_intel_Hello_welcome. Если код находится в пакете “default”, то имя C-функции должно выглядеть так: Java_Hello_welcome.

    • Стоит быть внимательными с различными Unicode-символами: их нужно заменять на их 16-ричное представление, но об этом не будем тут. На самом деле и это ещё не все соглашения, но сейчас расскажем, как не делать этого ручками.



Для правильно описанных методов не стоит их создавать руками, для этого воспользуемся специальной утилитой “javah”, которая есть в JDK. Для начала откомпилируем наш Java-файл, используя “javac”.


javac Hello.java

Теперь воспользуемся “javah”.


javah Hello

В результате выполнения этих операций мы получим файл “Hello.h”


/* DO NOT EDIT THIS FILE - it is machine generated */

#include 

/* Header for class Hello */

#ifndef _Included_Hello

#define _Included_Hello

#ifdef __cplusplus

extern "C" {

#endif

/*

 * Class:     Hello

 * Method:    welcome

 * Signature: ()V

 */

JNIEXPORT void JNICALL Java_Hello_welcome

  (JNIEnv *, jclass, jstring, jint);

#ifdef __cplusplus

}

#endif

#endif

Как видно, так как мы пишем на C++, то добавлена строчка extern "C", которая об этом сообщает. Имена JNIEXPORT и JNICALL описаны в jni.h, они задают спецификаторы функций, экспортируемых из динамических библиотек .

В данном файле всё довольно понятно, всё как мы и предполагали (на самом деле не всё, но об этом позже).

Осталось для данного файла заголовка написать соответствующий файл c исходным кодом, где мы и опишем тело метода.

Вот как он будет выглядеть:


#include "Hello.h"

#include 

JNIEXPORT void JNICALL Java_Hello_welcome(JNIEnv* env, jclass cl, jstring str, jint value)

{

   const char * ccstr;

   ccstr = (*env).GetStringUTFChars(str, NULL);

   printf("Hello damn World, %s, %d!n", ccstr, (int)value);

}

Во всех вызовах JNI-функций есть указатель env, который является первым параметром любого платформенно-ориентированного метода. Параметр env указывает на таблицу функций, содержащую указатели функций.

Как наверно можно догадаться jstring, jint это по сути те же string, int, но только в понимании JNI (их соответствия).

Функция GetStringUTFChars() возвращает указатель const jbyte * на символы строки, представленные в формате UTF-8. Также не помешает после использования строк освобождать память. Если же этого не делать, то это может повлиять на работу метода (память не безгранична). Для этих целей нужно использовать метод ReleaseStringUTFChars().

Очень подробное описание всех функций (API JNI) можно найти сайте Oracle, по этой ссылке - http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html.

Как компилировать динамическую библиотеку каждый выбирает сам, это можно сделать вручную, или можно предоставить всё MVS.

Нужные вам библиотеки, необходимые для успешной компиляции C/C++, лежат в JDK/include.

Ну и осталось написать вызов функции из Java-программы:


class HelloTest {

   public static void main(String[] args)    {

      Hello.welcome("I'm a string", 100500);

   }

}

На этом закончим, хоть и было рассмотрено материала 7% от общего, но тут главное смысл, а дальше уже как по маслу, берёшь и намазываешь ;)

Pour de plus amples informations sur les optimisations de compilation, consultez notre Avertissement concernant les optimisations.
Catégories: