Compile java to native code
А gcj-то оказывается не жив. Что-то с ним как-то совсем печально. Чем теперь компилять-то?
Идея: собрать Java-хелловорд (со свингом или gtk) в линуксовый бинарник. Что посоветуете? Похоже, что выбор ограничен Excelsior JET, gcj и ecj. Какие есть варианты?
Может, попробовать mono? Там живой AOT.
«Hello, World», как заказывали. А что в нём не подходит?
а в чем собсно проблема с gcj? Старый рантайм? Скомпили новый.
Просто создай линуксовый пакет и пропиши для него зависимость от jre или jdk, поставляемого в виде пакета для конкретного дистрибутива. Правда есть один неприятный момент. Многие предпочитают ставить официальный сановский.. оракуловский jdk из простого архива и самостоятельно прописывать $JAVA_HOME. Тогда для линукса установленной вручную таким способом явы может и не быть — не тащить же ее по-новой но уже в виде линуксового пакета?
Совет «забей на яву» тебе не подойдет?
Не знаю как на линуксе, но в виндовых инсталяторах и маковских образах сейчас принято тащить с собой целиком jre. Вот и весь ответ. Конечно, надо прибавить к размеру дистрибутива программы мегабайта 32, но что делать. А на линуксе может выручить зависимость от пакета jre, специально сделанного для дистрибутива. Это если по-взрослому делать.
Так что, еще раз подумай о моем совете 🙂
Да, есть еще один вариант. На сайте Vuze, к примеру, на странице Download было раньше две ссылки (не знаю, как сейчас). Первая ссылка «Get Java» вела на сайт оракула. Вторая ссылка скачивала либо инсталятор Vuze для винды, либо образ для мака, либо что-то там для линукса. Было написано, что необходимо сначала иметь установленную яву для запуска программы, и наверняка, это проверялось в виндовом инсталяторе.
Второй вариант. Виндовый инсталятор AnyLogic включает в себе целиком jre — об этом варианте я уже писал. Для маковского образа было написано на сайте, что «вы должны самостоятельно установить яву». Примерно тоже самое для линуксового архива.
Третий вариант. Посмотри программу jEdit. Давно не смотрел, но по-моему они полагаются, что пользователи (а это должны быть продвинутые пользователи, а иначе на черта им jEdit) должны сами установить jre. Что мне понравилось, у них всегда раньше был пакет *.jar, не зависящий от системы. Были и инсталяторы/образы/дебы для основных платформ.
В общем, я накидал тебе тут информации к размышлению о том, как распространять приложения на яве. Даже много лишнего. Думай сам.
Добавлю только то, что я бы мог воспользоваться Excelsior JET (раньше игрался с ним). Все остальное забудь. Либо этот новосибирский JET, либо полноценная jre. Все остальное должно быть заметно хуже по качеству, намного.
8 min read ‘>Применение JNA (Java Native Access) для доступа к «нативным» библиотекам COM-DLL Microsoft Windows. 8 min read
Применение JNA (Java Native Access) в Java-проектах для доступа к функциональности и объектам, так называемых «нативных» библиотек — COM-DLL Microsoft Windows, представляет собой большой интерес.
Основным преимуществом является сокращение времени разработки проекта, если вся необходимая функциональность уже содержится в какой-то стандартной библиотеке Microsoft Windows, либо есть сторонняя COM-DLL с необходимым набором решений, либо это уже применяемая клиентом COM-DLL бизнес-логики. Также невозможно переоценить возможность использования COM-DLL, работающей с объектами Microsoft .NET Framework, написанная, например, на C#.
Вторым, но не меньшим по значимости преимуществом является то, что в отличие от предыдущей технологии JNI (Java Native Interface), здесь не придется писать библиотеку-оболочку на C, а это и в правду сомнительное удовольствие.
Но обо всем по порядку. Рассмотрим задачу, которая и подвигла на изучение технологии JNA.
Для нужд проекта потребовалось получить список принтеров в системе, но также название их драйверов, т.к. имя принтера можно задать вручную какое угодно, хоть «MySuperPuperPrinter», а проекту требовалось что-то более надежное и стабильное. Результат решения, которое предлагает Java, оказался неправильным…
Воспользуемся библиотекой javax.print:
Моя система показывает следующий результат:
PrinterName = Samsung SCX-4×28 Series PCL6, PrinterMakeAndModel = null
PrinterName = Samsung CLX-3180 Series, PrinterMakeAndModel = null
PrinterName = PDF Creator, PrinterMakeAndModel = null
PrinterName = FinePrint, PrinterMakeAndModel = null
PrinterName = Fax, PrinterMakeAndModel = null
Где информация о драйвере или модели принтера? Ее нет.
Посмотрим, что говорит об этом Oracle:
For attributes like
javax.print.attribute.standard.PrinterMakeAndModel
javax.print.attribute.standard.PrinterLocation
javax.print.attribute.standard.PrinterInfo
javax.print.PrintService.getAttribute should return meaningful information obtained from the Windows OS, instead of empty strings.
This bug can be reproduced always.
EVALUATION 2003-02-18
In Windows, this information can be retrieved from PRINTER_INFO_X structure.
ОК, проверим последнюю версию Java 1.7.0_17 – результат тот же, т.е. проблема 2003 не исправлена.
Ну да ладно, в принципе Java не предназначена знать все тонкости операционной системы, на которой она работает. Поэтому, как там Microsoft Windows разбирается со своими принтерами, должен был бы знать какой-нибудь Microsoft Windows сервис. Он есть – это библиотека c:WindowsSystem32Winspool.drv и ее функция EnumPrinters. Функция перечисляет принтеры системы и складывает информацию как раз в структуру PRINTER_INFO_2.
Чтобы получить доступ к библиотеке Winspool.drv можно использовать JNI – для этого нам придется писать оболочку на С, h-файл со стороны JNI и т.д. Для неспециалиста в этой технологии достаточно трудоемкий и время затратный процесс, а уж о трудностях отладки я вообще молчу.
Посмотрим, что нам может предложить JNA.
Начнем с самого оригинального примера «Hello World», что мне приходилось видеть, от разработчика JNA.
Т.е. достаточно объявить интерфейс, расширяющий com.sun.jna.Library, объявить в нем необходимый метод с именем и сигнатурой, соответствующими функции из библиотеки (в данном случае это библиотека c:WindowsSystem32kernel32.dll и функция Beep), и загрузить библиотеку, используя этот интерфейс с помощью com.sun.jna.Native. Первым параметром выступает имя библиотеки, которое может содержать полный путь к ней в файловой системе, а вторым – класс интерфейса, который используется для связывания интерфейса и библиотеки посредством reflection.
Впечатляет, насколько просто можно услышать приветствие компьютера «Hello World», которое он пробибикает азбукой Морзе.
Также интересные примеры можно найти тут.
Теперь вернемся к задаче проекта – получить имена принтеров и их драйверов в Microsoft Windows. Как было сказано выше, нужно обратиться к Winspool.drv и посредством ее функции EnumPrinters получить набор структуры PRINTER_INFO_2 для каждого принтера в системе. К счастью JNA позаботился об обертке структур «нативных» библиотек и предоставила класс com.sun.jna.Structure.
Тестовый код выглядит так:
Что мы видим тут? Интерфейс Winspool теперь расширяет StdCallLibrary, а также содержит структуру PRINTER_INFO_2, наследницу Structure. Это необходимость, т.к. вызов Native.loadLibrary посредством reflection строит карту интерфейса и его структур и, если вынести эту структуру из интерфейса, то карта будет неправильной. Ладно, если бы была ошибка, так структура просто будет ошибочно заполнена методом EnumPrinters – например, вместо имени принтера в поле pPrinterName будет только первая буква имени.
У структуры PRINTER_INFO_2 есть еще пара особенностей: она должна содержать конструктор без параметров и переопределять метод protected List getFieldOrder() – строгая последовательность и именование полей.
Вот и все. Запустив тест, получим в консоли список принтеров и их драйверов:
PrinterName = Samsung SCX-4×28 Series PCL6, DriverName = Samsung SCX-4×28 Series PCL6
PrinterName = Samsung CLX-3180 Series, DriverName = Samsung CLX-3180 Series
PrinterName = PDF Creator, DriverName = CUSTPDF Writer
PrinterName = FinePrint, DriverName = FinePrint 7
PrinterName = Fax, DriverName = Microsoft Shared Fax Driver
Это именно то, что требовалось.
Как же можно еще упростить эту задачу? Выглядит все это все равно чересчур громоздко.
Например, нам не нужны все 21 поля структуры PRINTER_INFO_2, а только два – PrinterName и DriverName.
Посмотрим, как справляется с этим .NET – ведь это неотъемлемая часть Microsoft Windows.
Код для консольного приложения .NET будет выглядеть так:
Результат выводится на консоль:
PrinterName = Samsung SCX-4×28 Series PCL6, DriverName = Samsung SCX-4×28 Series PCL6
PrinterName = Samsung CLX-3180 Series, DriverName = Samsung CLX-3180 Series
PrinterName = PDF Creator, DriverName = CUSTPDF Writer
PrinterName = FinePrint, DriverName = FinePrint 7
PrinterName = Fax, DriverName = Microsoft Shared Fax Driver
Хм… То же самое. Значит мы на правильном пути.
Как теперь обернуть этот код в «нативную» библиотеку, получить к ней доступ через JNA и забрать результат в удобном для нашего проекта виде?
Начнем с того, что .NET не «нативная» среда — у нее свои CLR (Common Language Runtime), CIL (Common Intermediate Language) и CLI (Common Language Infrastructure-Standard), и .NET-DLL будет отлично линковаться другой библиотекой или программой .NET, но никак не как «нативная» библиотека к С- и Java-программам.
Ее можно подключить как nuget-плагин через Package Manager Console с помощью команды:
PM> Install-Package UnmanagedExports
Либо просто взять у производителя архив с плагином UnmanagedExportLibrary.zip и скопировать его в каталог шаблонов проектирования Microsoft Visual Studio.
Обычно это My DocumentsVisual Studio 20**TemplatesProjectTemplates.
Потом создать новый проект C# на базе шаблона UnmanagedExportLibrary и наша «нативная» библиотека Winspool.dll почти готова.
Добавим в нее статический класс Export и статическую функцию GetPrinterInfo:
Статический класс и статическая функция – это необходимое условие для применения атрибута DllExport из библиотеки RGiesecke.DllExport. Таким образом, функция станет «нативной» в библиотеке и к ней можно будет непосредственно обращаться через JNA.
Функция GetPrinterInfo собирает информацию о принтерах и их драйверах в строку с разделителем «;», т.е. если потом преобразовать эту строку по разделителю в массив строк, то по нечетным индексам будет имя принтера, а по четным – имя драйвера. Очень полезным для нас тут будет то, что DllExport может передавать простые типы в качестве параметров и возвращаемых значений функций, и к счастью для нас, что в этот список входит string.
Скомпилируем библиотеку Winspool.dll для платформы х86.
Код на Java выглядит так:
Выглядит значительно проще, не правда ли?
Но что-то все равно бросается в глаза своим несовершенством… Да, это расположение библиотеки Winspool.dll в папке lib, рядом с классом, и ее поиск по пути в файловой системе:
final String path = new File(«»).getAbsolutePath() + «\lib\Winspool.dll»;
В данном случае это не критично, но что делать, если проект как-то хитро компилируется, использует специальные папки для ресурсов, или динамически ориентирован на Java определенной разрядности – 64 бита или 32? Как сделать так, чтобы нам упростить поиск месторасположения библиотеки? И как подготовить «нативную» библиотеку для разной разрядности, да еще отследить в Java проекте, когда и какую использовать?
Вот тут выступает еще одно достоинство JNA – автоматическое сканирование и нахождение «нативных» библиотек.
Если сделать так:
1 – где-нибудь создать две папки — win32-x86 и win32-x86-64
2 — скомпилировать «нативную» библиотеку с атрибутом х86, и положить ее в папку win32-x86
3 — скомпилировать с атрибутом х64, и положить в win32-x86-64
4 — создать из этих двух папок zip-архив
5 — переименовать его в jar
6 — подключить его к Java-проекту
то JNA автоматически подключает версию «нативной» библиотеки соответственно разрядности Java.
Код станет еще проще, и нам не придется устраивать поиск библиотеки Winspool.dll, контроль соответствия разрядности Java и библиотеки Winspool.dll.
Ну и напоследок, можно сделать еще красивее. Благодаря тому, что «нативная» библиотека .NET может возвращать строку неограниченной длинны, то использование результата с разделителем выглядит не очень дружественно.
Что более всего подходит для передачи данных в виде строки? Правильно – XML. Попробуем передать в качестве результата «нативной» библиотеки XML-сериализованный объект, а на стороне Java десериализуем в свой точно такой же объект.
На C# есть несколько возможностей XML-сериализации объектов, но сразу сделаю замечание, что распространенная сериализация из библиотеки System.Xml.Serialization не работает, если использовать ее в «нативной» библиотеке.
Будет ошибка System.IO «Illegal characters in path» , хотя никакого IO при этом не используется – это какой-то баг .NET, который пока не исправлен и в .NET 4.5.
К счастью есть другой механизм XML-сериализации в библиотеке System.Runtime.Serialization.
Добавим к .NET-проекту класс PrintInfoList:
Здесь можно видеть корневой класс PrintInfoList, который является списком объектов PrintInfo. Стоит обратить внимание на использование атрибутов библиотеки System.Runtime.Serialization – DataContract и DataMember. Их применение должно быть совершенно очевидным – DataContract описывает объект, а DataMember – данные объекта, в данном случае это открытые поля. Кроме того, в конце метода десериализации в полученной XML-строке применена замена секции
. Это нужно для упрощения примера, иначе придется обеспечивать обработку всех Namespaces на стороне Java.
Далее в класс Export добавим новую функцию GetPrinterInfo2, возвращающую заполненный и сериализованный объект PrintInfoList в виде XML-строки:
Теперь вернемся к Java. Чтобы десериализовать XML-строку — результат метода GetPrinterInfo2 библиотеки Winspool.dll, создадим полный аналог объектов на Java:
Т.е. XML-строка десериализуется в экземпляр PrintInfoList, который представляет собой список объектов PrintInfo, и который теперь гораздо удобнее и нагляднее использовать в любых частях проекта.
На базе полученного шаблона «нативная» DLL -> JNA -> Java можно создавать много разных и интересных межплатформенных проектов. На C# можно писать библиотеки-оболочки, упрощающие работу с «внутренностями» Microsoft Windows, которые трудно реализуемы или вообще недоступны для Java, а качестве обмена между .NET и Java процессами использовать XML-сериализованные объекты. Ведь этот принцип можно использовать и в обратном направлении, т.е. от Java к .NET, и таким способом передавая параметры и прочие данные для обработки в «нативной» DLL через XML-сериализованные объекты.
Исходные коды и прочие ресурсы: