Джавёнок

На днях узнал о таком вот проекте. Это крохотная джава-машина. Сразу как узнал, захотелось пощупать её на практике. Вот, собственно впечатления.

Java ради Java

Попробуем поставить джава-машинку и запустит с её помощью простенькую программку. Репозиторий этого малыша хостится на git:

$ git clone git://oss.readytalk.com/avian.git

Убедитесь, что в ваших переменных окружения прописан путь к JAVA_HOME. Если его нет — укажите вручную, где лежит ваш JDK. Заходим в папку с машиной и собираем её:

$ cd avian
$ make && make test

Все должно быть «success». Все. Машина готова. Если у вас 32-битный linux, то VM лежит в build/build/linux-i386/avian. Поглядев повнимальенее видим, что это:

-rwxr-xr-x 1 serge serge 907064 Dec 24 18:45 build/linux-i386/avian*
build/linux-i386/avian: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped

ldd build/linux-i386/avian
	linux-gate.so.1 =>  (0xffffe000)
	libm.so.6 => /lib/libm.so.6 (0xb775a000)
	libz.so.1 => /lib/libz.so.1 (0xb7747000)
	libpthread.so.0 => /lib/libpthread.so.0 (0xb772e000)
	libdl.so.2 => /lib/libdl.so.2 (0xb772a000)
	libc.so.6 => /lib/libc.so.6 (0xb75e4000)
	/lib/ld-linux.so.2 (0xb7794000)

То есть, это файлик размером в метр, зависящий только от zlib и pthread (не считая стандартных сишных библиотек). Avian понимает стандартные опции машины java (-cp, -jar, -D).
Теперь можно написать простенький hello world:

public class Test {
	public static void main(String args[]) {
		System.out.println("Hello world!\n");
	}
}

Собираем как умеем и запускаем сначала openjdk, а потом avian:

$ javac Test.java
$ java Test
$ build/libux-i386/avian Test

В результате каждая из машин выводит «Hello world». Если померять время, то можно увидеть, что avian грузится раз в 10 быстрее openJDK. Если кто-то проведет более серьезные тесты — будет интересно посмотреть на результаты.

Java для людей с предубеждениями и без JVM

Итак, что мы имеем? Кросс-платформенную JVM (linux/windows/macosx), весом в метр, которая поддерживает классы из java.lang, java.io, java.util. Немного, но основные классы есть. Можно использовать классы OpenJDK.

Так вот. Каждый, я думаю, встречал людей которые видя очередное java-приложение кричат: «О, это еще джаву ставить ради неё!» или «О, эта фигня написана на джаве! Это же тормоз!».
При этом на python+pygtk смотрят почему-то нормально.

К чему это я? В основном avian расчитан на распространение java-приложения вместе с виртуальной машиной. Примеры с их сайта показывают, что статический бинарник avian + java-программа, работающая с окнами и графикой могут весить 700 кб и довольно быстро (ну не медленнее питона+pygtk) выполняться. Не верите? Скачайте отсюда примеры и позапускайте (это обычные бинарники). Лично у меня осталось приятное впечатление.

А если хочется написать самому что-нибудь эдакое да покроссплатформеннее?

Я вот попробовал. Итак, структура проекта:

ls -R
.:
build.xml  driver/  src/

./driver:
driver.cpp

./src:
demo/

./src/demo:
Hello.java

В корне проекта build.xml — для того, чтобы удобно все это билдить Ant’ом. В папке driver — они файлик на c++, который стартует (как я понял) джава-машину. В папке src разворачиваем наш hello-world на джаве.

С исходником hello-world’а, думаю, все ясно:

package demo;

public class Hello {
	public static void main(String[] args) {
		System.out.println("Hello World. This is avian demo");
	}
}

«Драйвер» джава-машины выглядит вот так (внимание! в нем hardcoded имя нашего стартового класса — demo.Hello):

#include "stdint.h"
#include "jni.h"

#ifdef __MINGW32__
#  define EXPORT __declspec(dllexport)
#  define SYMBOL(x) binary_boot_jar_##x
#else
#  define EXPORT __attribute__ ((visibility("default")))
#  define SYMBOL(x) _binary_boot_jar_##x
#endif

extern "C" {

  extern const uint8_t SYMBOL(start)[];
  extern const uint8_t SYMBOL(end)[];

  EXPORT const uint8_t*
  bootJar(unsigned* size)
  {
    *size = SYMBOL(end) - SYMBOL(start);
    return SYMBOL(start);
  }

} // extern "C"

int
main(int ac, const char** av)
{
  JavaVMInitArgs vmArgs;
  vmArgs.version = JNI_VERSION_1_2;
  vmArgs.nOptions = 1;
  vmArgs.ignoreUnrecognized = JNI_TRUE;

  JavaVMOption options[vmArgs.nOptions];
  vmArgs.options = options;

  options[0].optionString = const_cast<char*>("-Xbootclasspath:[bootJar]");

  JavaVM* vm;
  void* env;
  JNI_CreateJavaVM(&vm, &env, &vmArgs);
  JNIEnv* e = static_cast<JNIEnv*>(env);

  jclass c = e->FindClass("demo.Hello");
  if (not e->ExceptionCheck()) {
    jmethodID m = e->GetStaticMethodID(c, "main", "([Ljava/lang/String;)V");
    if (not e->ExceptionCheck()) {
      jclass stringClass = e->FindClass("java/lang/String");
      if (not e->ExceptionCheck()) {
        jobjectArray a = e->NewObjectArray(ac-1, stringClass, 0);
        if (not e->ExceptionCheck()) {
          for (int i = 1; i < ac; ++i) {
            e->SetObjectArrayElement(a, i-1, e->NewStringUTF(av[i]));
          }
          
          e->CallStaticVoidMethod(c, m, a);
        }
      }
    }
  }

  int exitCode = 0;
  if (e->ExceptionCheck()) {
    exitCode = -1;
    e->ExceptionDescribe();
  }

  vm->DestroyJavaVM();

  return exitCode;
}

Да, этот исходник я честно забрал со страниц avian’а. А вот, собственно, наш билд-файл. Он по порядку делает все, что надо чтобы из исходников на джаве получить нативный бинарник. Сразу оговорюсь, что я джаву люблю чисто платонически и никаких серьезных дел с ней не имел, а потому написание build-файлов для меня еще покрыто страшной тайной. Так что, если что-то не так — смело говорите.

<project name="Hello world" default="dist" basedir=".">

	<!-- Specify here target platform and archtecture. Also show where avian is placed -->
	<property name="platform"  value="linux"/>
	<property name="arch"  value="i386"/>
	<property name="avian" location="../avian/build/${platform}-${arch}"/>

	<!-- Project directory layout -->
	<property name="src" location="src"/>
	<property name="build" location="build"/>
	<property name="build-avian" location="${build}/avian"/>
	<property name="build-classes" location="${build}/classes"/>
	<property name="dist"  location="dist"/>
	<property environment="env"/>

	<!-- Prepare for building - create directories and copy necessary files -->
	<target name="init">
		<tstamp/>
		<mkdir dir="${build}"/>
		<mkdir dir="${build-avian}"/>
		<mkdir dir="${build-classes}"/>
		<exec executable="ar" dir="${build-avian}">
			<arg value="x"/>
			<arg value="${avian}/libavian.a"/>
		</exec>
		<copy tofile="${build-avian}/boot.jar" file="${avian}/classpath.jar" />
	</target>

	<!-- Compile only java files -->
	<target name="compile" depends="init" description="compile the source ">
		<javac bootclasspath="${build-avian}/boot.jar" includeAntRuntime="false" srcdir="${src}" destdir="${build-classes}"/>
	</target>

	<!-- Archive classes to boot.jar, convert it to binary, build driver and link all this stuff -->
	<target name="dist" depends="compile" description="Link a single binary">
		<jar compress="false" update="true" destfile="${build-avian}/boot.jar" basedir="${build-classes}"/>
		<exec executable="${avian}/binaryToObject">
			<arg line="${build-avian}/boot.jar ${build-avian}/boot-jar.o"/>
			<arg line="_binary_boot_jar_start _binary_boot_jar_end"/>
			<arg line="${platform} ${arch}"/>
		</exec>
		<exec executable="g++">
			<arg line="-I${env.JAVA_HOME}/include -I${env.JAVA_HOME}/include/linux"/>
			<arg value="-D_JNI_IMPLEMENTATION_"/>
			<arg value="-c"/>
			<arg value="driver/driver.cpp"/>
			<arg value="-o"/>
			<arg value="${build-avian}/driver.o"/>
		</exec>
		<exec executable="sh">
			<arg value="-c"/>
			<arg value="g++ -rdynamic ${build-avian}/*.o -ldl -lpthread -lz -o ${build}/hello"/>
		</exec>
	</target>

	<!-- Cleanup -->
	<target name="clean">
		<delete dir="${build}"/>
		<delete dir="${dist}"/>
	</target>
</project>

Теперь можно собрать проект:

$ ant # Ждем когда завершится
$ build/hello
Hello World. This is avian demo

Получилось. Бинарник, конечно, с метр ростом (если strip-нуть). Да еще и libstdc++ зачем-то потянул. Ну ниче, первый блин как-никак.
Главное, что все собралось, а значит можно писать настольные проги на джаве и легко их билдить. А что? SWT есть, основные классы есть. Даже JNI говорят есть. Да еще под ARM собирается. А то натыкают всяких Tomboy на C# под линукс.. Тьфу!

Исправим положение?

P.S. Попробовал добавить Proguard и SWT (оказалось на удивление нетрудно), да еще и upx-ом сжимать.
Итого: прога, которая показывает одно окошко занимает 600Кб и стартует все так же мгновенно.

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s