Автономная кроссплатформенная монолитная программа на Java

Я люблю desktop-приложения. Признаваться в этом нынче, похоже, стыднее, чем в связях с иностранной разведкой, но это так. Нет, это не значит, что я не люблю интернет-технологии. Более того, некоторые я не только уважаю, а даже более-менее знаю. Но, тем не менее, я скучаю по тем временам, когда программа писалась на одном компьютере, потом компилировалась и запускалась на других, разных компьютерах. Тогда везде (почти) была одна система — Windows с одной и той же API, почти не было проблем совместимости на уровне приложений, никто не материл разработчиков браузеров — все берегли нервы на разработчиков WinAPI, которые умудрялись создавать конфликты даже внутри нее одной. Но это я, конечно, иронизирую, а если серьезно — иногда и сейчас хочется написать просто desktop-приложение, да так, чтобы работало оно на всех популярных системах. Трудно? Если подумать и покопать, то не очень.

Еще я люблю языки высокого уровня с аккуратной архитектурой и строгой типизацией. Мои фавориты — Java и C#. Оба они предоставляют разработчику множество преимуществ по сравнению с C++, оба избавляют от ряда забот. Чем приходится платить? Тем, что таскаешь за собой тяжелую колоду, которая называется Oracle JVM, .NET или mono. Все три колоды весят сотни мегабайт и лицензию имеют такую, что каждый пользователь вынужден качать эту штуку сам, не путая при этом разрядность своего компьютера, а главное — программа на Java не может быть совместима со всеми версиями JVM разом, не так ли? И вот — мы приходим к тому, что просто скинуть программку другу (или миллиону друзей) и не заботиться о том, что она у него не запустится, не выходит. Приходится делать хитрые сетапы, вбивать костыли, и это я еще не упомянул .NET — однажды я видел у друга сразу 3 установленных версии, причем все три были нужны разным приложениям…

Стоп! А давайте напишем программу на Java, но так, чтобы она не требовала установки на машину какой-либо JVM, чтобы одним касанием собиралась под Windows, Linux и OS X и чтобы при этом занимала совсем чуть-чуть; так, чтобы никто даже не понял, что она написана, скажем, не на C. Невозможно? Совсем наоборот! (И нет, я имею в виду не gcj, который лишает Java всех ее прелестей. Рефлексия будет работать и даже сторонние jar вы сможете запускать).


Разумеется, я не волшебник. Я только нашел один волшебный артефакт. Называется он Avian, лежит по адресу oss.readytalk.com/avian и представляет собой легковесную, но полноценную стороннюю реализацию JVM, о которой Oracle, возможно, даже не слышали. Он поддерживает кучу платформ и архитектур, имеет лицензию «бери и делай, что хочешь», и — нет, я не имею ни малейшего отношения к этому проекту, я даже не контрибьютор, я только научился им пользоваться и хочу поделиться этим могучим знанием с уважаемыми обитателями Хабра. Стоит также отметить, что он является JIT-компилятором, то есть имеет конкурентно-высокую производительность (хотя я пока что не измерял ее).

Avian можно встроить в ваше приложение вместе с его весьма урезанной, но терпимой по функционалу стандартной библиотекой классов, причем «потяжелеет» программа всего только на мегабайт с гаком. Давайте соберем такую программу вместе.

0. Среда


Для построения нам прежде всего понадобятся утилиты командной строки unix-разработчика, в частности — компилятор g++. Тут сложнее всего придется Windows-пользователям. Раньше я под Windows использовал MinGW32 с ее замечательной средой MSYS, эмулирующей unix-терминал. Компиляторы, входящие в MinGW32 32-битные, что в некотором роде ограничивает полученную программу. В комментариях мне подсказали, что уже давно существует удобная mingw-w64, которая регулярно обновляется и в которой есть не только MSYS, но даже и git.

Здесь и далее я спрятал платформозависимые инструкции под спойлеры для удобства.

Windows
Чтобы скачать MinGW, идем на sourceforge.net/projects/mingwbuilds и там скачиваем два архива: x64-X.X.X-release-posix-seh-revX.7z и external-binary-packages/msys+7za+wget+svn+git+mercurial+cvs-revX.7z

После того, как они скачаны, распаковываем оба из них в удобные нам директории. Далее необходимо указать среде MSYS, где находится MinGW. Для этого идем в msys/etc и там в файле fstab прописываем путь к /mingw так, как это показано в файле fstab.sample У меня получилось вот так:
c:/mingw64         /mingw


После установки всего перечисленного вы открываете терминал MSYS. Для этого необходимо запустить файл msys\msys.bat (рядом с ним лежат две иконки). Все дальнейшие действия мы будем делать из этого терминала, так как он, во-первых, поддерживает формат unix-команд и unix-пути, а, во-вторых, в нем прописаны все необходимые параметры окружения.

OS X
Под OS X вам придется скачать XCode 4 и в его настройках, в разделе Downloads, установить Command Line Tools. Затем вы просто открываете окно терминала через Launcher.

Linux
В linux, основанном на Debian, просто откроем терминал и пишем:

> sudo apt-get install build-essential

После этого необходимо закрыть окно терминала и открыть его снова, чтобы загрузились новые параметры среды.


Итогом в каждой операционной системе должно быть то, что вы вводите в командной строке

> g++

и в ответ видите что-то вроде

g++: fatal error: no input files

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

1. Avian


Для начала предложим нашему g++ собрать Avian.
Открываем: oss.readytalk.com/avian. Выбираем ссылку status. На открывшейся странице скачиваем Avian 0.6. Несмотря на скромный номер версии, программа совершенно стабильна (во всяком случае, мне ни разу не удалось уронить ее, а в их багтрекере значатся весьма заковыристые баги, означающие высокую стабильность того, что есть).

Распакуем исходник Avian, скачанный нами, в некоторую папку (пусть это будет ~/Projects).

> cd ~/Projects

Под Windows папка "~" в среде MSYS присоединяется не к домашней папке пользователя, а к папке C:\MinGW64\msys\home\username. В нашем случае, раз мы хотим максимально дистанцироваться от платформы, это даже преимущество.

Допустим, скачанный архив называется avian-0.6.tar.bz2 и лежит в ~/Downloads, тогда распаковываем его в текущую папку, набрав

> tar -xjf ~/Downloads/avian-0.6.tar.bz2

В Windows путь необходимо указывать в формате mingw с прямыми слешами в виде /c/Users/username/Downloads/avian-0.6.tar.bz2. Разумеется, вы можете воспользоваться одним из ста альтернативных способов распаковать архив — главное, чтобы он попал в текущую папку. В итоге в ней появится распакованная из архива подпапка avian. Зайдем в нее:

> cd avian

Теперь можно попробовать запустить команду make, но, если запустить сборку прямо сейчас, скорее всего, мы получим сообщение о том, что не найден zlib. Что-нибудь вроде zlib.h: No such file or directory.

Windows
Под Windows придется слегка попотеть и воспользоваться методом, предложенным авторами Avian, а именно — подсунуть библиотеку zlib из их специального вспомогательного репозитария win64. Для этого вам потребуется установленный в системе git. К счастью, git входит в установленную нами сборку msys. К сожалению, на момент написания этой статьи сборка сделана криво — в ней не хватает файла msys-crypto-0.9.8.dll, который придется найти в Google и положить рядом с его бесполезным братом msys-crypto-1.0.0.dll, которым эта сборка укомплектована.

Далее необходимо из папки avian выполнить команду

> git clone git://oss.readytalk.com/win64.git ../win64

которая положит рядом с папкой avian папку win64 со всеми библиотеками, которые могут понадобиться avian-у, в частности, с zlib.

OS X
Под OS X, насколько я помню, эта библиотека устанавливается автоматически (возможно, с инструментарием разработчика, который мы уже поставили).

Linux
Под linux эта проблема решается простым

> sudo apt-get install zlib1g-dev


Однако, установив zlib и снова набрав make, мы получим еще одну ошибку — сборщик Avian не находит программу /bin/javac. Java-разработчики, вероятно, узнают эту программу — это компилятор Java. Так как Avian — только виртуальная машина, компилятор мы по-прежнему используем официальный — от Oracle. При сборке самой VM он нужен для того, чтобы собрать из исходных java-файлов классы маленькой стандартной библиотеки Avian, такие, например, как System, ArrayList или HashMap. Соответственно, на машине разработчика всё равно должна стоять JDK — как при сборке Avian, так и при сборке приложений, которые будут его использовать. Причем ставить желательно JDK7, с которой совместим Avian 0.6. Пользователю ваших приложений она, как и JRE, будет уже не нужна (собственно, ради этого и стараемся).

Windows
В Windows идём на сайт Oracle и качаем нужный дистрибутив, а затем устанавливаем.

OS X
В OS X, как и в Windows, идём на сайт Oracle и качаем нужный дистрибутив, а затем устанавливаем.

Linux
В Linux обходимся привычной мантрой

> sudo apt-get install openjdk-7-jdk

(Возможно, в вашем дистрибутиве не будет OpenJDK или пакет будет называться как-то иначе. Но Linux есть Linux — ищите и обрящете.)

Для того, чтобы увидеть, куда вы только что положили ваше java-окружение для разработчиков, make читает переменную среды JAVA_HOME, которую нам сейчас надо правильно задать. Эта же самая переменная с тем же значением потребуется нам впоследствии для того, чтобы собирать наш собственный проект.

Windows
Ваш путь в MinGW под Windows, скорее всего, будет что-то вроде /c/Program\ Files/Java/jdk1.7.0_07/ (вы его указывали при установке JDK7).

OS X
Под OS X ваша JDK7 установится в /Library/Java/JavaVirtualMachines/jdk1.7.0_17.jdk/Contents/Home

Linux
У меня в Linux это выглядит так:

> update-java-alternatives -l
java-1.7.0-openjdk-amd64 1071 /usr/lib/jvm/java-1.7.0-openjdk-amd64
> export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64


И вот, наконец, звёздный час — мы собираем Avian:

> make

Если вы всё проделали правильно, вы увидите последовательность строчек вида compiling build/<имя_вашей_платформы>/<какой-то файл>, а затем linking build/<имя_вашей_платформы>/<какой-то файл>. По окончании сборочного процесса мы получим много файлов в папке build/<имя_вашей_платформы>, но заинтересуют нас отсюда только:

  • classpath.jar — та самая маленькая библиотека базовых классов, которую вы будете использовать в своих программах. Существует возможность собрать Avian с использованием библиотеки OpenJDK, но, во-первых, как я понимаю, у нее менее добрая лицензия, а во-вторых, она существенно тяжелее.
  • binaryToObject(.exe) — необходимая для встраивания Avian в ваши приложения утилита, назначение которой будет раскрыто позже.
  • libavian.a — самый главный файл. Это и есть, собственно, встраиваемая виртуальная машина. У меня он занимает аж 23 мегабайта, но не пугайтесь. Он заметно похудеет после некоторых несложных и, что важно, совершенно безвредных и автоматических манипуляций, суть которых также будет раскрыта позднее.


2. Кроссплатформенный независимый монолитный привет на Java


Мы скомпилировали весь необходимый сторонний код. Теперь займёмся созданием своего собственного.

Задача в том, чтобы сделать программу, которая будет написана на Java, которая при этом будет содержать как можно меньше платформозависимых закладок и которая будет собираться в один exe-файл, не требующий специальной установки сам по себе и работающий на любой «чистой» системе без установки каких-либо зависимостей.

2.1. Немного про JNI

Начнем, пожалуй, с теории. Обсудим, как JVM взаимодействует с системой. Любая виртуальная машина создается, в первую очередь, для того, чтобы абстрагироваться от внешней среды. Поэтому неудивительно, что самым узким местом в реализации VM является как раз вызов системных функций. Современная программа не может даже «чихнуть» без участия ОС. Читать/писать на диск — системная функция. Вывод текста в консоль — системная функция. Нарисовать окошко на экране — а вы сами как думаете?

Фактически, единственное, что приложение может делать «внутри себя» — это расчеты и принятие решений. Именно эти действия — арифметика и логика — являются функциями VM. Как только надо сделать что-то еще, она зовёт внешнюю среду. Но как? В случае Java для этого существует JNI (Java Native Interface). Суть его весьма проста. Программа, написанная на Java, содержит в себе заголовок функции, помеченный модификатором native. Например,

package packagename;
{
	class ClassName
	{
		void native foo();
	}
}

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

extern "C" JNIEXPORT void JNICALL Java_packagename_ClassName_foo(JNIEnv * env, jobject caller)
{
	…
}

При вызове в Java-коде функции foo() мы фактически вызываем функцию из native-библиотеки, передав ей указатель на среду JNIEnv — объект, позволяющий «общаться» с данными и кодом внутри VM, и указатель на объект, из которого вызвана функция, — jobject caller (если бы функция была статической, вместо дескриптора объекта здесь бы был дескриптор класса jclass caller_class). Людям, хорошо знакомым с Java, но не изучавшим JNI, можно объяснить этот принцип взаимодействия так: JNI позволяет внешнему native-коду выполнять рефлексию над программой на Java. Если хотите изучить эту технологию подробнее, милости прошу в специальный раздел на официальном сайте Oracle.

2.2. JNI «наборот»

Зачем был весь этот ликбез? Затем, что в данный момент перед нами стоит весьма занятная, почти обратная, задача. Нам надо запустить native-исполняемый файл, который, будучи статически слинкован с библиотекой libavian.a, будет содержать JVM прямо внутри себя. Помимо этого, он будет содержать внутри себя все необходимые java-классы, включая и «точку входа» — класс вида

class Application
{
	public static void main(String... args)
	{
		…
	}
}

Звучит это всё довольно пугающе, однако задача эта вполне простая. Необходимо написать довольно несложный код на C, который вытащит библиотеку классов Avian (с добавленным в нее нашим классом Application) изнутри собственного бинарного файла и скормит ее JVM вместе с параметрами командной строки с помощью всё того же JNI. Затем мы линкуем этот C-файл специальным образом, чтобы всё оказалось на своих местах, и наслаждаемся результатом.

2.3. Новый проект и библиотеки

Сейчас мы притащим и разложим по полочкам все нужные нам для дальнейшей работы компоненты. То, что я буду описывать здесь — это мой собственный подход. Разумеется, вы вольны сделать всё иначе, так как вам заблагорассудится. Но если вы хотите в итоге получить в точности то, что я выложил на GitHub (ссылка будет в конце), постарайтесь делать всё в точности.

Создаем папку crossbase где захотим (я создал ее в Projects, рядом с avian и win32)

> mkdir crossbase && cd crossbase

Внутри создаем подпапку libs

> mkdir lib && cd lib

Внутри создаем подпапку с именем вашей текущей OS. Им должно быть «linux», «win32» или «osx».

> mkdir win-x86_64 && cd win-x86_64

В эту папку необходимо скопировать libavian.a, который мы собрали ранее. У меня это выглядит так:

> cp ../../../avian/build/windows-i386/libavian.a ./

Кроме того, в системе Windows, где нет zlib, в эту же папку придется скопировать еще и libz.a:

> cp ../../../win-x86_64/lib/libz.a ./

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

Помимо библиотек, нам понадобится classpath.jar, который также был собран вместе с avian.

> cd ..
> mkdir java && cd java
> cp ../../../avian/build/windows-i386/classpath.jar ./

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

> cd ../..	

(мы снова в папке crossbase, где мы создали lib)

> mkdir -p tools/win-x86_64 && cd tools/win-x86_64

Имя win-x86_64 назначено внутренней папке по тому же принципу, что и в прошлый раз. Кидаем сюда binaryToObject. (в Windows он, разумеется, имеет расширение exe)

> cp ../../../avian/build/windows-i386/binaryToObject/binaryToObject.exe ./

Можно запустить его и увидеть usage:

usage: c:\Users\imizus\Projects\crossbase\crossbase\tools\win32\binaryToObject.exe <input file> <output file> <start name> <end name> <platform> <architecture> [<alignment> [{writable|executable}...]]


2.4. Код программы

А теперь приступим к написанию кода. Создадим новый исходный файл на C++ (вы можете воспользоваться любым текстовым редактором, какой вам нравится, я использую eclipse, в котором можно редактировать и C++, и Java в рамках одного проекта, хотя для этого его придется немного настроить).

> mkdir -p src/cpp && cd src/cpp

Внутри создаем файл main.cpp со следующим содержанием (приведу его целиком, а потом объясню, что там к чему):

#include <stdint.h>
#include <string.h>

#ifdef __MINGW32__
#include <windows.h>
#endif

#include <jni.h>

#if (defined __MINGW32__)
#  define EXPORT __declspec(dllexport)
#else
#  define EXPORT __attribute__ ((visibility("default"))) \
  __attribute__ ((used))
#endif

#if (! defined __x86_64__) && (defined __MINGW32__)
#  define SYMBOL(x) binary_boot_jar_##x
#else
#  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 argc, const char** argv)
{
#ifdef __MINGW32__
	// For Windows: Getting command line as a wide string
	int wac = 0;
	wchar_t** wav;
	wav = CommandLineToArgvW(GetCommandLineW(), &wac);
#else
	// For other OS: Getting command line as a plain string (encoded in UTF8)
	int wac = argc;
	const char** wav = argv;
#endif

	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("crossbase/Application");
	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(wac - 1, stringClass, 0);
				if (not e->ExceptionCheck())
				{
					for (int i = 1; i < wac; ++i)
					{
#ifdef __MINGW32__
						// For Windows: Sending wide string to Java
						int arglen = wcslen(wav[i]);
						jstring arg = e->NewString((jchar*) (wav[i]), arglen);
#else
						// For other OS: Sending UTF8-encoded string to Java
						int arglen = strlen(wav[i]);
						jstring arg = e->NewStringUTF((char*) (wav[i]));
#endif
						e->SetObjectArrayElement(a, i - 1, arg);
					}

					e->CallStaticVoidMethod(c, m, a);
				}
			}
		}
	}

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

	vm->DestroyJavaVM();

	return exitCode;
}


__MINGW32__ — символ препроцессора, который (как неожиданно!) автоматически задается внутри среды MinGW32. Он позволяет нам отличить Windows, который, как вы, думаю, уже успели заметить, сильно непохож на все прочие системы. В частности, только под Windows нам понадобится специальная системная API, которую мы подключаем строкой #include <windows.h>. На остальных платформах мы обходимся стандартными библиотеками POSIX и ANSI C++. Зачем понадобится? Станет ясно чуть позже. Будем просматривать код по порядку.

#if (defined __MINGW32__)
#  define EXPORT __declspec(dllexport)
#else
#  define EXPORT __attribute__ ((visibility("default"))) \
  __attribute__ ((used))
#endif

Этот код знаком и понятен всем, кто писал кроссплатформенные динамические библиотеки с использованием gcc. Суть его в том, что в разных операционных системах по-разному описываются функции, которые должны быть экспортированы из библиотеки. «Причем тут динамическая библиотека, ведь мы же исполняемый файл собираем?» — спросите вы. В ответ я напомню, что взаимодействие Avian с платформозависимым кодом осуществляется через механизм JNI, подразумевающий вызов функции из библиотеки. Иными словами, для вашего Java-кода исполняемый файл это не только пусковая программа, а еще и динамическая библиотека функций.

Следующая часть — это странноватая магия:
#if (! defined __x86_64__) && (defined __MINGW32__)
#  define SYMBOL(x) binary_boot_jar_##x
#else
#  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"

Давайте разберемся. Мы декларируем некую экспортную функцию (посмотрим на extern "C" и директиву EXPORT, которую мы только что ввели. Имя функции — bootJar. Запомним это имя и посмотрим, что она делает. Если мысленно разобрать директивы препроцессора, то увидим, что она вычисляет расстояние между некими _binary_boot_jar_start и _binary_boot_jar_end (В MinGW32 они не будут иметь подчерк вначале). Сами эти символы декларированы как extern, то есть их должен подставить линковщик. Загадочная деятельность, не правда ли?

На самом деле, как мы увидим ниже, всё довольно просто, если знаешь, что делать. Так как Avian разрабатывался для встраивания его в приложения, авторы предусмотрели возможность добавления библиотеки классов непосредственно в исполняемый файл с последующей ее загрузкой оттуда. Для этого надо всего лишь преобразовать библиотеку в объектный файл. Да-да, я тоже поначалу удивился, но это очень элегантная идея. В объектном файле, содержащем наш jar, когда мы его создадим, будет декларировано 2 символа, указывающих на начало (_binary_boot_jar_start) и конец (_binary_boot_jar_end) этого jar-файла. А функция bootJar будет использована Avian-ом, чтобы узнать, где он начинается и какую длину имеет. Забегая вперед, скажу, что имя этой функции передается строкой

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

Наконец-то мы дошли до точки входа — функции main. В ее задачу входит:
  • Считать строку параметров
  • Загрузить Avian, передав ему библиотеку классов
  • Вызвать функцию main из класса crossbase.Application, передав ей параметры командной строки
  • Красиво вылететь с ошибкой, если что-либо из вышеперечисленного не удастся


Поехали с начала фцнкции:

#ifdef __MINGW32__
	// For Windows: Getting command line as a wide string
	int wac = 0;
	wchar_t** wav;
	wav = CommandLineToArgvW(GetCommandLineW(), &wac);
#else
	// For other OS: Getting command line as a plain string (encoded in UTF8)
	int wac = argc;
	const char** wav = argv;
#endif

Здесь как всегда отличился Windows. Когда повсеместно было принято решение переходить от старых неудобных однобайтных кодировок к более сложным, все ОС перешли к удобной UTF-8, а любимое детище Microsoft перешло на фиксированную двухбайтную. При этом они вообще не позаботились о том, какая кодировка используется, например, в именах файлов. Но кодировка нас сейчас тоже не очень заботит. Нам надо передать строку параметров в Java (в которой тоже принят двухбайтный char). Поэтому для Windows мы вызываем API-функцию (ради которой мы и тащили windows.h), которая выдаст нам строку параметров в правильной двухбайтной кодировке. Так мы получим возможность, например, открывать файлы с кириллицей в названии. Во всех прочих системах мы просто читаем параметры из аргументов функции main.

Далее следует создание виртуальной машины Java:

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);

Еще мы вытаскиваем указатель на объект JNIEnv, который будем использовать, чтобы командовать только что созданной Java-машиной.

Дальнейший код читается как стих Маяковского, если только немного знать JNI.

jclass c = e->FindClass("crossbase/Application");
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(wac - 1, stringClass, 0);
			if (not e->ExceptionCheck())
			{
				for (int i = 1; i < wac; ++i)
				{
#ifdef __MINGW32__
					// For Windows: Sending wide string to Java
					int arglen = wcslen(wav[i]);
					jstring arg = e->NewString((jchar*) (wav[i]), arglen);
#else
					// For other OS: Sending UTF8-encoded string to Java
					int arglen = strlen(wav[i]);
					jstring arg = e->NewStringUTF((char*) (wav[i]));
#endif
					e->SetObjectArrayElement(a, i - 1, arg);
				}

				e->CallStaticVoidMethod(c, m, a);
			}
		}
	}
}

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

Возьмём класс crossbase/Application. Если смогли, найдем в нем статический метод main с сигнатурой ([Ljava/lang/String;)V. Если смогли, достанем из стандартной библиотеки класс java/lang/String. Если смогли, создадим массив объектов этого класса (они и будут параметрами). Если смогли, то во всех операционных системах создаем java-строку из каждого параметра, заданного в кодировке UTF-8, а в Windows создаем напрямую, используя двухбайтное представление.

Если мы что-нибудь не смогли, выдаем ошибку пользователю.

Вот, собственно, и весь «пусковой механизм». Теперь нам надо создать нашу программу на Java. Она, как минимум, должна содержать класс crossbase.Application с методом public static void main(String... args).

Создадим в нашей папке crossbase/src подпапку java, в ней — подпапку crossbase (это — имя пакета), а внутри создадим файл Application.java следующего содержания:

package crossbase;

public class Application
{
	public static void main(String... args)
	{
		System.out.println("This is a crossplatform monolith application with Java code inside. Freedom to Java apps!");
		for (int i = 0; i < args.length; i++)
		{
			System.out.println("args[" + i + "] = " + args[i]);
		}
	}
}

Если вы хоть немного знаете Java, то, думаю, комментарии здесь излишни. Скажу только, что в стандартной библиотеке классов Avian нету средств форматирования строк (которые никто не мешает тихонько утянуть, к примеру, из OpenJDK).

2.5. Сборка


Теперь перейдем к задаче сборки нашего проекта. Я использую make, потому что он есть всегда и везде, где есть gcc. А еще он достаточно мощный, чтобы написать на нем почти любую автоматизированную систему сборки. Нет, правда. Можно по пальцам перечислить, что мне не удавалось сделать на make и это едва ли были жизненно важные вещи. Наш Makefile будет лежать прямо в папке crossbase и выглядеть он будет вот так:

UNAME := $(shell uname)
ARCH := $(shell uname -m)

SRC = src
BIN = bin
OBJ = obj

JAVA_SOURCE_PATH = $(SRC)/java
JAVA_CLASSPATH = $(BIN)/java
CPP_SOURCE_PATH = $(SRC)/cpp
OBJECTS = $(OBJ)


DEBUG_OPTIMIZE = -O3 #-O0 -g

ifeq ($(UNAME), Darwin)	# OS X
  PLATFORM_ARCH = darwin x86_64
  PLATFORM_LIBS = osx-x86_64
  PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/darwin"
  PLATFORM_GENERAL_LINKER_OPTIONS = -framework Carbon
  PLATFORM_CONSOLE_OPTION = 
  EXE_EXT=
  STRIP_OPTIONS=-S -x
  RDYNAMIC=-rdynamic
else ifeq ($(UNAME) $(ARCH), Linux x86_64)	# linux on PC
  PLATFORM_ARCH = linux x86_64
  PLATFORM_LIBS = linux-x86_64
  PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/linux"
  PLATFORM_GENERAL_LINKER_OPTIONS = -lpthread -ldl
  PLATFORM_CONSOLE_OPTION = 
  EXE_EXT=
  STRIP_OPTIONS=--strip-all
  RDYNAMIC=-rdynamic
else ifeq ($(OS), Windows_NT)	# Windows
  PLATFORM_ARCH = windows x86_64
  PLATFORM_LIBS = win-x86_64
  PLATFORM_GENERAL_INCLUDES = -I"$(JAVA_HOME)/include" -I"$(JAVA_HOME)/include/win32"
  PLATFORM_GENERAL_LINKER_OPTIONS = -static -lmingw32 -lmingwthrd -lws2_32 -mwindows -static-libgcc -static-libstdc++
  PLATFORM_CONSOLE_OPTION = -mconsole
  EXE_EXT=.exe
  STRIP_OPTIONS=--strip-all
  RDYNAMIC=
endif

JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1')
JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES))))

CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1')
CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES))))

all: $(BIN)/crossbase

$(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java
	@echo $(PLATFORM_GENERAL_INCLUDES)
	if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi
	"$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $<

$(OBJ)/%.o: $(SRC)/cpp/%.cpp
	@echo $(PLATFORM_GENERAL_INCLUDES)
	mkdir -p $(OBJ)
	g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@

$(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS)
	mkdir -p $(BIN);
	@echo $(PLATFORM_GENERAL_INCLUDES)

	# Extracting libavian objects
	( \
	    cd $(OBJ); \
	    mkdir -p libavian; \
	    cd libavian; \
	    ar x ../../lib/$(PLATFORM_LIBS)/libavian.a; \
	)

	# Making the java class library
	cp lib/java/classpath.jar $(BIN)/boot.jar; \
	( \
	    cd $(BIN); \
	    "$(JAVA_HOME)/bin/jar" u0f boot.jar -C java .; \
	)

	# Making an object file from the java class library
	tools/$(PLATFORM_LIBS)/binaryToObject $(BIN)/boot.jar $(OBJ)/boot.jar.o _binary_boot_jar_start _binary_boot_jar_end $(PLATFORM_ARCH); \
	g++ $(RDYNAMIC) $(DEBUG_OPTIMIZE) -Llib/$(PLATFORM_LIBS) $(OBJ)/boot.jar.o $(CPP_OBJECTS) $(OBJ)/libavian/*.o $(PLATFORM_GENERAL_LINKER_OPTIONS) $(PLATFORM_CONSOLE_OPTION) -lm -lz -o $@
	strip $(STRIP_OPTIONS) $@$(EXE_EXT)


clean:
	rm -rf $(OBJ)
	rm -rf $(BIN)

.PHONY: all

Будьте осторожны! Не путайте табуляции с пробелами, в make табуляцией выделяются команды внутри правила сборки, а пробел синтаксическим элементом не является. Присмотримся немного, как он работает. Единственная более-менее мозгодробительная конструкция — назначение вот этих переменных:

JAVA_FILES = $(shell cd $(JAVA_SOURCE_PATH); find . -name \*.java | awk '{ sub(/.\//,"") }; 1')
JAVA_CLASSES := $(addprefix $(JAVA_CLASSPATH)/,$(addsuffix .class,$(basename $(JAVA_FILES))))

CPP_FILES = $(shell cd $(CPP_SOURCE_PATH); find . -name \*.cpp | awk '{ sub(/.\//,"") }; 1')
CPP_OBJECTS := $(addprefix $(OBJECTS)/,$(addsuffix .o,$(basename $(CPP_FILES))))

Здесь мы с помощью unix-команды find отыскиваем все файлы .java в папке $(JAVA_SOURCE_PATH). Эти файлы нам предстоит компилировать. Далее мы откусываем от них расширение и заменяем его на .class, а пусть заменяем на $(JAVA_CLASSPATH), получая таким образом имена файлов классов, которые надо получить, т.е. имена целей. Аналогично мы поступаем с файлами .cpp и .o. Далее в makefile мы видим следующие правила сборки:

$(JAVA_CLASSPATH)/%.class: $(JAVA_SOURCE_PATH)/%.java
	@echo $(PLATFORM_GENERAL_INCLUDES)
	if [ ! -d "$(dir $@)" ]; then mkdir -p "$(dir $@)"; fi
	"$(JAVA_HOME)/bin/javac" -sourcepath "$(JAVA_SOURCE_PATH)" -classpath "$(JAVA_CLASSPATH)" -d "$(JAVA_CLASSPATH)" $<

$(OBJ)/%.o: $(SRC)/cpp/%.cpp
	@echo $(PLATFORM_GENERAL_INCLUDES)
	mkdir -p $(OBJ)
	g++ $(DEBUG_OPTIMIZE) -D_JNI_IMPLEMENTATION_ -c $(PLATFORM_GENERAL_INCLUDES) $< -o $@

Эти правила объясняют, как скомпилировать исходный файл в целевой. И, наконец, взглянем на цель

$(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS)
	...

Здесь мы видим зависимость от всех найденных файлов. То есть makefile написан таким образом, чтобы собирать все java и cpp файлы, предложенные ему в правильных папках.

Остальные существенные моменты:

  • -static-libgcc и -static-libstdc++ необходимы в mingw, чтобы собираемый файл содержал в себе стандартные библиотеки C и C++. В противном случае он будет слинкован с ними динамически и потребует таскать за собой пару DLL.
  • -mconsole нужен в системе Windows, чтобы система выдала программе консольный ввод-вывод при запуске. Этот параметр для GUI-приложения надо убрать.
  • Опция -rdynamic не поддерживается gcc под Windows в силу особенностей платформы.


Пробежимся вскользь по основному правилу сборки — $(BIN)/crossbase: $(JAVA_CLASSES) $(CPP_OBJECTS). Сперва мы распаковываем все объектные файлы из libavian.a, чтобы впоследствии передать их линковщику поименно. Поведение странное, но не бессмысленное. В Windows это решает какую-то странную проблему с линковкой (я не разобрался достаточно хорошо). Далее мы берем наш classpath.jar, добавляем к нему наши скомпилированные классы из bin/java и пакуем всё вместе в bin/boot.jar. Затем мы вызываем binaryToObject, который создает из нашего boot.jar объектный файл obj/boot.jar.o с символами _binary_boot_jar_start и _binary_boot_jar_end (которые мы импортировали в main.o). И, наконец, мы линкуем всё это безобразие вместе. И, наконец, выполняем волшебную команду strip, в параметрах которой, на этот раз, отличилась OS X, где они не такие, как в MinGW и в Linux. Назначение команды — выкинуть из исполняемого файла всякие левые символы. До ее отработки crossbase весит более 9 мегабайт, после — менее полутора.

3. Момент триумфа


Зайдя в папку crossbase/bin, запускаем из консоли наш crossbase, передав ему параметры.

> ./crossbase Привет Хабр!

This is a crossplatform monolith application with Java code inside. Freedom to Java apps!
args[0] = Привет
args[1] = Хабр!


Получившийся у нас проект лежит на моём GitHub-е.

4. Итоги и смысл


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

Изначально я планировал сделать в статье главу, посвященную добавлению к этому «бутерброду» кроссплатформенного пользовательского интерфейса SWT (того, который используется в Eclipse), но потом решил, что она будет слишком уж длинной и увесистой. Если господам читателям интересно, напишу об этом отдельно. Благодарю за внимание!

P.S.
Получив от хабровчан много отзывов, я доработал статью и программу. Спасибо всем за советы и поправки.

Комментарии 80

    –23
    > Еще я люблю языки высокого уровня с аккуратной архитектурой и строгой типизацией. Это — Java и C#.

    С тем, что у Ruby и Python типизация тоже строгая, спорить нельзя. Выходит, вы считаете их архитектуру неаккуратной?

    Или вы имели в виду «Мне довелось работать с Java и C#, и я ценю их за аккуратную архитектуру и строгую типизацию»? ;)
      +4
      Каюсь, должен был уточнить. Python почти не знаю (хотя его синтаксис и подход меня, честно говоря, не ввоодушевляет, но это — дело вкуса), с Ruby пока дела не имел вовсе.
        –1
        Не слишком понятно, где у Python строгая типизация? Про «утиную» слышал, но строгая?.. Я так думаю, имелось в виду строгая==статическая.

        И да, Python я знаю и постоянно пользую, и мне нравится.
        Про Ruby знаю только, что он есть :)
          +1
          Строгая — отсутствует автоматическое приведение типов. Статическая — в переменную одного типа невозможно записать значение другого типа. Явная (противоположность «утиной») — тип переменной указывается избыточно при ее объявлении.
            0
            Пардон, ниже поправили, что «утиная» — разновидность динамической типизации.
            +3
            Вам стоит освежить свои знания, прочитайте на википедии определения этих 5 терминов.
            Есть строгая (сильная) и слабая типизация.
            Есть статическая и динамическая (утиная — частный случай динамической).

            Некоторые языки обладают строгой, но динамической типизацией. И именно такими языками являются Ruby и Python.
          +2
          Или вы имели в виду «Мне довелось работать с Java и C#, и я ценю их за аккуратную архитектуру и строгую типизацию»?

          наверно это и имелось ввиду, но зачем вы вобще заострили внимание на этом?
            –2
            Не пробовали использовать компилятор gcj (The GNU Java compiler)? С помощью него, в частности, можно jar-файл превратить в машинный код.
              +3
              > "(И нет, я имею в виду не gcj, который лишает Java всех ее прелестей. Рефлексия будет работать и даже сторонние jar вы сможете запускать)."

              Вероятно пробовал.
                +3
                Действительно пробовал. В другой раз буду внимательнее читать статью.
                  0
                  Помимо этого, насколько мне известно, он очень слабо поддерживается и в последний раз, когда я его пробовал под MinGW, он вообще не мог собрать тривиальную программу из-за какой-то баги.
                    0
                    AOT компиляцию java программы с помощью gcj под MIPS архитектуру так и подавно не удалось запустить
                +5
                Что касается разработки Desktop-приложений, то Qt Creator (C++) мне кажется достаточно удобным. Опробовано для Linux/Windows и впечатление осталось положительное. Не приходится самому писать большой Makefile, есть графический редактор форм (Qt Designer), встроенное средство для перевода (Qt Linguist), встроенная документация (Qt Assistant). Насколько помню разработку под Java, инструмента с аналогичным набором функций для этого нет.
                  0
                  Ну почему же? Есть Eclipse, есть Idea, даже Netbeans. Они спокойно понимают javadoc(встроенная документация), не нужно прописывать команды java компиляции, настраиваешь билд и оно все делает за тебя. Дизайнеры так же имеются(swing, swt, android). Переводчика конечно нету, но в целом, как IDE, мне кажется IDEA/Eclipse все же лучше Qt Creator(приходилось на практике видеть и то и другое).
                    0
                    Java любят потому что почти весь Enterprise сегмент на нем сидит, особенно для тонких-толстных клиентов через WebStart
                    +2
                    однажды я видел у друга сразу 3 установленных версии, причем все три были нужны разным приложениям

                    Вы счастливый человек, если вас это удивляет. Одна наша корпоративная система требует три версии фреймворка определенных сервиспаков. Правда она больше похожа на франкенштейна, чем на единую систему. Но тем не менее.
                      +5
                      Меня это не то чтобы удивляет… просто как-то это грустно. Убивает идею портируемости на корню. Согласны?
                        0
                        автоматическая портируемость любого приложения сложнее тетриса — миф
                          +1
                          Я где-то писал про «автоматическую»? Определенные усилия приложить несомненно придется — кто же спорит? Просто они должны быть невелики по сравнению с усилиями для написания приложения.

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

                          К тому же в данном случае я лишь имел в виду лишь то, что портированное приложение должно быть «лёгким», а не тянуть за собой целый фреймворк. А то проще сразу поставить VirtualBox и не мучаться
                      0
                      Статья навеяла воспоминания студенческих годов — мы работали в разных лабораторных, на разных OS — FreeBSD, Windows и разные линуксы, а дома у меня был Mac. Тогда и родилась у меня идея и мой будущий диплом — ICQ клиент на Java, который просто запускался с флешки и работал на любой ос, с подтягиванием истории и настроек. Вышло классно, если интересен код — обращайтесь — вышлю без проблем.
                        0
                        Напишите статью?
                          0
                          К сожалению, нет. Java не мой основной язык и был выбран чисто как инструмент. Да и было это 5 лет назад, надо вспоминать. Сходу помню одну неприятность — отсутствие unsigned типов, а протокол бинарный.
                            0
                            Обещают unsigned типы в jdk8. Читайте блог. Хотя конечно, пока jvm восьмерки станет production ready по стабильности сравнимой с jvm 6 версии, пройдет немало времени. Парсить бинарный протокол можно и без unsigned типов, но не так комфортно.
                              0
                              Конечно можно парсить, я же все-таки написал клиент :) Это просто неприятность, которую легко обойти. Но я уже давно не пишу на Java и не слежу за изменениями.
                        –1
                        А что Вы можете сказать по поводу объема потребляемой памяти при запуске приложения, собранного таким образом? Т.е. если я запускаю, приложение, написанное на Java с использованием самой обыкновенной JMV, оно на моей машине кушает 600 МБ, причем это не само приложение, по сути, столько потребляет, а виртуальная машина. Есть ли выигрыш по памяти при использовании avian?

                        Плюс еще интересует, насколько ограничивает avian использование сторонних Java-библиотек в приложении?
                          0
                          По первому вопросу:

                          Берем мою программу, добавляем к ней цикл вида
                          		double k = 1;
                          		for (int i = 0; i < 100000; i++)
                          			for (int j = 0; j < 100000; j++)
                          			{
                          				k = i * j;
                          				System.out.println(k);
                          			}
                          		
                          

                          Пока она мучается, выводя циферки, смотрим потребление памяти

                          Физической — 9.1Мб, виртуальной — 67Мб (многовато, конечно, но терпимо).

                          По второму вопросу:
                          Сам avian может быть собран с OpenJDK, причем в этом случае под ним можно успешно запускать eclipse (отнюдь не простое приложение). JVM, как я понимаю, — весьма простая вещь, если обрубить с нее всевозможные расширения, дополнения и мегафичи. А сторонние jar (включая и содержащие нативные библиотеки) грузить можно вполне успешно.
                            0
                            «С использованием самой обыкновенной JVM» не добавляет ясности вашему посту. У любой популярной JVM есть опции для управления памятью, вы можете дать столько, сколько сами решите. Если у вас по дефолту так настроено, чтобы 600 Мб отъедать, кто вам виноват?
                              0
                              Буду Вам признателен, если поможете ограничить объем потребляемой памяти. Я пробовал опции запуска -Xmn, если не ошибаюсь, но существенного влияния они не оказали, т.к. ограничивают heap, а не саму JVM. Если подскажете опцию запуска вирт машины, которая запретит ей есть больше 200 MB, скажем, Вы меня спасете)
                                +1
                                Не знаю, откуда у вас такие цифры и что вообще за система у вас и какую версию Java вы используете. Проверял по таскменеджеру с тривиальным приложением с бесконечным циклом.

                                WinXP 32bit:
                                (HotSpot 1.6.0.21) java -client -Xms2M Test --> Mem Usage 6 848K, VM Size 17 384K.
                                (HotSpot 1.6.0.21) java -server -Xms2M Test --> Mem Usage 6 944K, VM Size 27 648K.
                                (HotSpot 1.7.0.15) java -Xms2M Test --> Mem Usage 9 688K, VM Size 27 380K
                                (JRockit 1.6.0.31) java -Xms8M Test --> Mem Usage 17 376K, VM Size 37 844K

                                Win2k3 server 64bit:
                                (HotSpot 32bit 1.6.0.21) java -client -Xms2M Test --> Mem Usage 8 412K, VM Size 18 004K.
                                (HotSpot 32bit 1.6.0.21) java -server -Xms2M Test --> Mem Usage 8 600K, VM Size 29 088K.
                                (HotSpot 64-bit 1.7.0.7) java -Xms2M -XX:+UseParNewGC Test --> Mem Usage 12 536K, VM Size 39 652K.

                                В общем, не знаю, где вы 200 Мб взяли
                                  –1
                                  запустите, пожалуйста, что-то сложнее helloworld-a, например eclipse
                                    0
                                    Но зачем? Такой задачи не стояло.
                                      0
                                      Ну видимо у меня беда именно в самой программе. По сути там 1,5 строки, но с использованием HtmlUnit.
                                        +2
                                        >но с использованием HtmlUnit
                                        Отлично…
                                        Что-то вроде:
                                        — у меня 3 строчки на крутом С++, которые загружают фотошоп с картинкой в 5Гб, так вот С++ отстой, он жрет столько памяти в 3-х строчках! Посоветуйте опции, чтобы ограничить его в 100Мб.
                                          0
                                          Погодите, я не говорил, что C++ отстой, впрочем, как и Java. Я лишь ищу способ минимизировать объем занимаемой памяти за счет тюнинга среды исполнения (т.к. я не могу модифицировать HtmlUnit, естественно, а в самой программе изменять нечего из-за того, что она в 1,5 строки).
                                            0
                                            Вообще-то это был гипертрофированный стеб, по поводу того, что вы говорите «Java сама по себе потребляет 600Мб, хотя прога в 3 строчки», но при этом подключаете достаточно ресурсоемкий фреймворк. ))
                                  0
                                  моё гугл-фу подсказывает
                                  -Xmx
                                    0
                                    Вот именно с этой опцией, выставленной в -Xmx64M программа отъедает больше 600 метров…
                              –10
                              Идея конечно интересная со standalone JVM — но интересно как раз было бы увидеть скажем Swing/SWT аппликацию с использованием этой JVM.
                                +12
                                Апплика́ция (лат. applicātiō — прикладывание, присоединение) — способ получения изображения; техника декоративно-прикладного искусства.
                                  –9
                                  Вы меня конечно задавили под аппликацией понималось вот это: en.wikipedia.org/wiki/Application. Еще более конкретно вот это: en.wikipedia.org/wiki/Application_software. Я честно думал, что из контекста это понятно тем более ИТшникам.

                                  Очень часто программисты говорят «аппликачка», «аппликейшн», «аппликация», «веб-аппликация» и т.п. просто потому что это проще. Не нужно делать context-switch и все понимают о чем речь.
                                    +9
                                    Если application называть «аппликация», а не «аппликейшн», то скоро на file будут говорить «филé». Уж слишком режет слух.
                                    0
                                    Господа минусующие не корысти ради но тем ни менее пожалуйста поясните чем вам не угодил мой комментарий. Я не выражаю какой-либо агрессии/издевок etc. Мне просто интересно что вызывает столько негатива, если это простой вопрос автору топика?
                                      +2
                                      Посмотрите www.youtube.com/watch?v=3W40tBACFbI
                                      "- Почему Рон Фини этого не делает?
                                      — Чего?
                                      — Не путает английские слова и русские?
                                      Я ответил:
                                      — Потому что Фини в совершенстве знает оба языка..."
                                    +2
                                    Постараюсь в ближайшее время аккуратно прикрутить к демке SWT и написать про это.
                                      0
                                      Да, было бы отлично. Потому что как я уже сказал выше, идея интересная но так же было бы интересно увидеть так сказать в «боевых» условиях данный подход.
                                    +1
                                    На самом деле если тщательно почистить jre оверхед получается не такой уж и большой. В свое вермя чистил sun jre 1.6 для десктоп приложения на Swing, и jre занимал около 10 мб не запакованный. В наше время это крохи.
                                      +1
                                      Плюсую
                                      «Все три колоды весят сотни мегабайт» — не правда ведь. Я понимаю, папка Windows регулярно пухнет на гигатайбы от необходимости держать несколько версий .NET фреймворка, но вот с JRE таких проблем практически не бывает. Даже если держать Java5, Java6 и Java7 версии одновременно (что, к счастью, весьма редко бывает действительно нужным) — потянет меньше чем на 300 МБ, а уж одна то версия JRE и сотни метров не съест.
                                        0
                                        гигатайб — это клёво
                                      0
                                      что то этот Avian не собирается — заткнулся на языках, неизвесно каких:

                                      make
                                      compiling classpath classes
                                      Note: Some input files use unchecked or unsafe operations.
                                      Note: Recompile with -Xlint:unchecked for details.
                                      .
                                      .
                                      .
                                      compiling build/linux-x86_64/compile-x86-asm.o
                                      compiling build/linux-x86_64/java-lang.o
                                      classpath/java-lang.cpp:16:17: fatal error: jni.h: No such file or directory
                                      compilation terminated.
                                      
                                      

                                        +4
                                        Пардон. Сам дурак. Корявый JAVA_HOME был указан.
                                        0
                                        Со сборкой (под Windows) у вас как-то неуверенно получилось:

                                        — Mingw можно брать из mingw-builds. Там есть и х64. Qt его рекомендует.

                                        — Библиотеки для Mingw (например zlib) можно устанавливать, используя mingw-get.

                                        — Копировать архивы (.a) совсем не обязательно, достаточно, чтобы они были доступны по стандартным путям поиска линкновщика (gcc --verbose hellow_world.cpp 2>&1 | grep LIBRARY), или их можно указать через директиву
                                        -L/path/to/search/for/libs.
                                          0
                                          Всё так. Но во-первых, если бы я еще описывал тут, как собрать рабочий MinGW 64 с MSYS, статья, и без того длинноватая для описания простого метода, стала бы вообще огромной. А в 90 процентах случаев, по моему опыту, 64 битность приложения не нужна (разумеется, если вы не пишете злобную математику или какой-нибудь фотошоп).

                                          Насчет сторонних библиотек: я взял те, что рекомендовали разработчики Avian. Просто на всякий случай. zlib собирается своими руками и очень просто или его действительно можно взять из MinGW, да. Но билд-процедура Avian требует, чтобы он лежал в ../win32/lib, насколько я помню.

                                          Что же касается копирования архивов, то я это сделал для того, чтобы всё это закоммитить на GitHub. Теперь вы можете слить себе crossbase и, только правя Java-код, собрать готовое приложение.
                                            0
                                            У mingw-builds вся фишка в том, что он уже собранный. Причем есть несколько разных конфигураций на выбор.

                                            А так я не с целью придраться писал, а в качестве дополнения.
                                        0
                                        Спасибо, буду знать. Честно говоря, не пользовался…
                                          +1
                                          а у XCode можно однажды после установки Command Line Tools просто найти во временных файлах и сохранить их архив (Xcode.CLTools......dmg). Архив всего около 180-200мб, для сборки из командной строки гораздо сподручнее чем вся XCode целиком
                                            0
                                            Небольшая поправка:
                                            какой смысл объявлять
                                            int main(int argc, const char** argv)
                                            
                                            для того, чтобы потом сделать
                                                int wac = 0;
                                                wchar_t** wav;
                                                wav = CommandLineToArgvW(GetCommandLineW(), &wac);
                                            
                                            ?

                                            Под Windows обычно пишут
                                            int wmain(int argc, const wchar_t** argv)
                                            

                                              0
                                              Согласен. Хотя не очень хочется ставить декларацию функции под #define. Не люблю макроопределения, которые идут вразрез со структурой кода. Хотя в данном случае, думаю, поправить стоит.

                                              Вы абсолютно уверены в том, что результат будет в точности тот же? (А то честно говоря, я эту штуку сочинял довольно давно, для одного своего проекта, и тогда основательно намучался с этими кодировками)
                                                0
                                                Работать оно так будет, по крайней мере такое я делал для сборки MSVS и Intel C/C++. Съест ли такое MinGW — хз, но проверить не помешает.
                                                  +1
                                                  Не будет:
                                                  $ cat >2.cpp
                                                  int wmain(int argc, const wchar_t** argv)
                                                  {}
                                                  $ g++ --version
                                                  g++ (GCC) 4.4.0
                                                  Copyright (C) 2009 Free Software Foundation, Inc.
                                                  This is free software; see the source for copying conditions.  There is NO
                                                  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
                                                  $ g++ 2.cpp
                                                  c:/mingw/bin/../lib/gcc/mingw32/4.4.0/../../../libmingw32.a(main.o):main.c:(.text+0x104): undefined reference to `WinMain@16'
                                                  collect2: ld returned 1 exit status
                                                  

                                                    0
                                                    Спасибо большое. Я помню, что там какие-то проблемы были.
                                                      0
                                                      Почему-то мне казалось, что у них оно давно реализовано…
                                                0
                                                Интересная тема! Жду продолжения вашей статьи и «вызревания» avian.
                                                Жаль что пока много телодвижений надо делать, чтобы собрать приложение с jvm в один бинарник и поддержка MIPS архитектуры пока только в Post-1.0 Ideas
                                                  0
                                                  Что касается «вызревания», замечу, что наблюдаю за ним я уже более чем полгода, коммитят они почти что ежедневно, но номер версии за это время сменился только с 0.5 до 0.6. Ребята, повидимому, очень ответственные, код вылизывают до блеска. На мой маленький баг-тикет ответили в тот же день (бага оказалась в Windows, а не в Avian). Так что возможно версии 1 ждать придется долго.
                                                    0
                                                    Отлично что проект развивается! До вашей статьи не встречал эту jvm. Теперь буду следить и за ней.

                                                    Кажется что главное ее применение это embedded решения с памятью 32/64Мб и архитектурой MIPS, ARM.
                                                    Даже то что поддерживает ARM архитектуру уже отлично. Если удасться запустить приложение собранное с jetty на raspberry pi будет уже победа!
                                                      0
                                                      Могу предоставить автору статьи доступ к raspberry pi (model B) для экспериментов, авось чего и выйдет…
                                                        0
                                                        Пообщайтесь с автором bigfatbrowncat в личку, может это его и заинтересует. Я все жду своей rpi, которую мне друг из Лондона подарил) Вторая попытка доставить ее в Россию через путешествующий народ. Уже даже контроллер CNC станока собрал без нее, на mini itx x86
                                                  –4
                                                  >Avian можно встроить в ваше приложение вместе с его весьма урезанной, но терпимой по функционалу стандартной библиотекой классов
                                                  и вдоволь нажравшись кактуса получаем урезаное нечто, в котором не факт что получится использовать нужные библиотеки + пиздецовое GUI на свинге (если оно вообще заработает).

                                                  Намного проще разрабатывать используя QtCreator или даже Lazarus (дада то самый паскаль).
                                                    0
                                                    С паскалем давно дела не имел, он сейчас очень далек от мейнстрима. Что же касается Qt, единственное его преимущество, на мой взгляд, заключается в его больших возможностях. Но после нескольких проектов писать верхний управляющий слой UI на C++ я бы никому не советовал. Слишком многие неочевидные вещи надо держать в голове, слишком много шансов создать трудновылавливаемую ошибку в коде. Я тратил по 4-5 часов на поиск ошибки в коде на C++, причем ошибка была такого сорта, какого в Java в принципе не может быть. Но это, конечно, уже дело вкуса. Я лишь хотел продемонстрировать способ.
                                                  0
                                                  Оч интересно! А SWT к ней прикрутить получается? И что там с бенчмарками?
                                                    0
                                                    Я таки не понял, результатом сборки получается бинарный файл? А как один бинарный файл запускается на разных ОС где тупо разный формат исполняемого бинарного файла?
                                                      0
                                                      Собрать, разумеется, придется под каждую ось свой. Но переделывать Java-код для пересборки не нужно. Все различия в системных API учтены внутри Java-библиотеки.
                                                        0
                                                        Как, учтено даже различие консольной и системной кодировок?!
                                                      0
                                                      А JavaFX 2 прикрутить?
                                                        –3
                                                        фап-фап-фап
                                                          0
                                                          Дык, давнно же уже есть Excelsior JET (http://www.excelsior-usa.com/jet.html). Единственное, поддержки Мака нет, а так вон для некоммерческого использования бесплатно можно взять. Но конкуренция — это всегда хорошо, надеюсь в JET поскорее поддержка MacOS X появится.
                                                            0
                                                            там какое то ограничение 30 дней. а платить за это 2500$ только чтобы не
                                                            качать jre весит 50 mb и устанавливается в пару кликов, стоит того?
                                                              0
                                                              Что-то я не вижу никаких ограничений по времени для некоммерческих проектов (http://www.excelsior-usa.com/jetfree.html). А если уж речь идет о коммерческих проектах, Standard версия и Basic суппорт вполне достаточны, по крайней мере для меня, а это в два с небольшим раза дешевле. На наших десктопных SWING приложениях результирующий размер макс. 20MB. И дело не только в том, чтобы не качать JRE. Ну и JET — это сертифицированная Oracle реализация, так что можно не опасаться всяких подводных камней.
                                                            0
                                                            Внимание! Статья поправлена и доработана. Многие комментарии стали неактуальны, так как изложенные в них мысли я внедрил. Так что чем неактуальнее комментарий, тем сильнее я благодарен за него автору ;)

                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                            Самое читаемое