Как jar переделать в dll

Обновлено: 04.07.2024

Java взаимодействует с операционной системой через методы, помеченные ключевым словом native, при помощи системных библиотек, загружаемых процедурой System.loadLibrary().

Загрузить системную библиотеку очень просто, а вот чтобы ее выгрузить, как оказалось, нужно приложить немало усилий. Как именно выгружаются системные библиотеки, и зачем это нужно я постараюсь рассказать.

Предположим, мы хотим сделать небольшую утилиту, которую будут запускать пользователи на своих компьютерах в локальной сети. Нам бы хотелось избавить пользователей от проблем с установкой и настройкой программы, но нет ресурсов на развертывание и поддержку централизованной инфраструктуры. В таких случаях обычно собирают программу вместе со всеми зависимостями в единый jar-файл. Это легко сделать при помощи maven-assembly-plugin или просто экспортировать из IDE Runnable jar. Запуск программы будет осуществляться командой:


К сожалению, это не работает, если одна из библиотек требует для своей работы системную динамическую библиотеку, проще говоря dll. Обычно в одном из классов такой библиотеки в статическом инициализаторе делается вызов System.loadLibrary(). Чтобы dll загрузилась, нужно положить ее в каталог, доступный через системное свойство JVM java.library.path. Как это ограничение можно обойти?

Запакуем dll внутрь jar-файла. Перед началом использования классов, требующих подгрузки dll, создадим временный каталог, извлечем библиотеку туда и добавим каталог в java.library.path. Выглядеть это будет примерно так:

К сожалению, приходится химичить с reflection, потому что стандартных методов расширить java.library.path Java не предоставляет.

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


Но на Windows это не работает. Загруженная в JVM библиотека блокирует dll-файл и каталог, в котором он лежит. Таким образом, чтобы решить задачу аккуратного завершения программы, надо выгрузить из JVM системную динамическую библиотеку.

Попытка решения

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


Как быстрое, но не самое красивое решение, я использовал планировщик. На выходе создаю xml-файл с заданием на выполнение через 1 минуту команды «cmd /c rd /s /q temp-dir» и загружаю задание в планировщик командой «schtasks -create taskName -xml taskFile.xml». К моменту выполнения задания программа уже завершена, и файлы никто не держит.

Самое же верное решение — это обеспечить выгрузку библиотеки средствами Java-машины. Документация говорит о том, что системная библиотека будет выгружена при удалении класса, а класс удаляется сборщиком мусора вместе с класслоадером, когда не осталось ни одного экземпляра из его классов. На мой взгляд, лучше всегда писать такой код, который полностью очищает после себя всю память и другие ресурсы. Потому что если код делает что-то полезное, рано или поздно захочется его переиспользовать и задеплоить на какой-нибудь сервер, где установлены и другие компоненты. Поэтому я решил потратить время на то, чтобы разобраться, как корректно программно выгрузить dll.

Использование класслоадера

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

Если dll загружена из системного загрузчика классов, то выгрузить ее уже не получится, поэтому необходимо создать свой класслоадер таким образом, чтобы класс, подтягивающий библиотеку, был загружен из него. Новый класслоадер должен быть связан с системным класслоадером через свойство parent, иначе в нем не будут доступны классы String, Object и другие необходимые в хозяйстве в вещи.

Не работает. При загрузке класса сначала производится попытка поднять его из родительского загрузчика, поэтому наш драйвер загрузился не так, как нам нужно. Для использования нового класслоадера, нужно JDBC-драйвер из jar-файла программы удалить, чтобы он не был доступен системному загрузчику. Значит, запаковываем библиотеку в виде вложенного jar-файла, а перед использованием разворачиваем его во временном каталоге (в том же, где и dll у нас лежит).

Мы получили объект, загруженный из нашего нового загрузчика, по окончании работы нам надо позакрывать все, что мы открывали, почистить все наши переменные, и, видимо, вызвать System.gc(), после чего уже пытаться чистить файлы. В этом месте имеет смысл инкапсулировать всю логику работы с загрузчиками классов в отдельном классе с явными методами инициализации.

Эксперименты со сборщиком мусора

Не работает. Каталог заблокирован процессом java. С этого момента я использовал такую технику:

  1. Ставлю на выходе System.in.read()
  2. Когда программа останавливается в этом месте, делаю дамп памяти из jvisualvm
  3. Смотрю дамп при помощи Eclipse Memory Analysis Tool или jhat
  4. Ищу экземпляры объектов, классы которых были загружены мои загрузчиком
  1. Локальные переменные
  2. DriverManager
  3. ResourceBundle
  4. ThreadLocals
  5. Исключения

Локальные переменные

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


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

DriverManager

JDBC-драйверы при загрузке их класса регистрируются в классе DriverManager методом registerDriver(). Судя по всему, перед выгрузкой надо вызвать метод deregisterDriver(). Пробуем.


Не работает. Heapdump не изменился. Смотрим в исходники класса DriverManager и обнаруживаем, что в методе deregisterDriver() стоит проверка на то, что вызов должен быть из класса, который принадлежит тому же класслоадеру, что и класс, вызвавший ранее registerDriver(). А registerDriver() вызван самим драйвером из статического инициализатора. Неожиданный поворот.

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


Интерфейс будет находиться в основном classpath-е, а релизация будет загружена новым загрузчиком из вспомогательного jar-файла вместе с JDBC-драйвером. Теоретически без интерфейса можно было бы обойтись, но тогда для вызова функции пришлось бы применять reflection. Используется прокси следующим образом:

ResourceBundle

Следующая зацепка на класслоадер, который я пытался выгрузить, обнаружилась в недрах класса ResourceBundle. К счастью, в отличие от DriverManager, ResourceBundle предоставляет специальную функцию clearCache(), которой класслоадер передается в качестве параметра.


Надо заметить, что, судя по исходникам, в ResourceBundle используются слабые ссылки, которые не должны препятствовать сборке мусора. Возможно, если очистить все остальные ссылки на наши объекты, то чистить этот кэш нет необходимости.

ThreadLocals

Последнее место, где обнаружились хвосты неиспользуемого драйвера, оказалось ThreadLocals. После истории с DriverManager-ом, очистка локальных поточных переменных кажется парой пустяков. Хотя тут не удалось обойтись без reflection.

Исключения

Мы рассчитываем на то, что код очистки можно поместить в блоке finally. На входе в этот блок у нас уже должно быть все закрыто автоматически при помощи механизма try-with-resources. Однако наш класслоадер по-прежнему не будет в этом месте удален из памяти, если из блока try выброшено исключение, класс которого загружен этим класслоадером.

Чтобы удалить из памяти нежелательный exception, его надо поймать и обработать, а если нужно ошибку все-таки выбросить наверх, то скопировать exception в другой класс. Вот как это сделал я в своей программе:

Java наносит ответный удар

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

Устранить проблему получилось вот таким приемом:


Наверное, в Java 1.7, которую я использовал, была какая-то особенность очистки объектов, которые лежат в PermGen. С настройками сборки мусора я не экспериментировал, потому что старался написать код, который будет одинаково работать в различном окружении, в том числе в серверах приложений.

После указанного приема код заработал как следует, библиотека выгружалась, каталоги удалялись. Однако после перехода на Java 8 проблема вернулась. Разобраться, в чем дело, не было времени, но судя по всему, изменилось что-то в поведении сборщика мусора.

Поэтому пришлось применить тяжелую артиллерию, а именно JMX:

Через HotSpotDiagnosticMXBean вызываем сохранение дампа памяти. В качестве имени файла указываем nul, что в Windows означает то же самое, что и /dev/null в Unix. Второй параметр указывает на то, что в дамп должны быть выгружены только живые объекты. Именно этот параметр заставляет JVM выполнить полную сборку мусора.

Вот после этого лайфхака проблема удаления библиотеки из временного каталога больше не возникала. Итоговый код очистки файлов выглядит так:

Проверка при помощи OSGI

Для проверки качества кода я написал свой JDBC-драйвер, который полностью убирает за собой. Он работает как обертка вокруг любого другого драйвера, подгружаемого из отдельного classpath.

Этот драйвер я вставил в сервис OSGI на Apache Felix.

При старте модуля через системную консоль Apache Felix, запущенную на Java 1.8.0_102, появляется временный каталог с dll-файлом. Файл заблокирован процессом java. Как только модуль останавливается, каталог удаляется автоматически. Если же вместо UnloadableDriver использовать DriverManager и обычную библиотеку из Embedded-Artifacts, то после обновления модуля возникает ошибка java.lang.UnsatisfiedLinkError: Native Library already loaded in another classloader.

Выводы

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

В Java существует немало мест, в которых можно случайно оставить ссылки на свои классы, и это является предпосылкой к утечкам памяти.

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

Особое внимание следует обратить на случаи, когда программа что-то загружает при помощи создаваемого во время выполнения нового загрузчика классов. Если останется хотя бы одна ссылка на один из загруженных классов, то класслоадер и все его классы останутся в памяти.

Чтобы обнаружить утечку памяти, надо сделать дамп и проанализировать при помощи специальных инструментов, таких как Eclipse MAT.

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

connvert jar to dll

Software Requirements

The JAR Files which is to be converted to DLL should be complied with JAVA JDK 1.7 or below. This limitation is because IKVM currently does not support converting JAR files compiled with JDK 1.8 or above.

JAR To DLL Conversion Command

The above command will convert all the JAR Files present in the folder path specified in the -recurse argument to a DLL file in the IKVM bin folder with the name specidied in -out argument.

Step by Step Example to Convert a Java Project Exported as JAR to DLL and use in Visual Studio Projects.

Step 1 - Create a new Java Project in Eclipse
  • Click File -> New -> Project. -> Java -> Java Project
  • Enter the project name in the "Project Name:" text box
  • Set the "Use an execution environment JRE" to JavaSE-1.7 or below
  • Click Finish

The Demo project is created as "CraftedForEveryoneJarToDll"

Create new java project

Create new java project

Step 2 - Create a new Package
  • Right Click "src" folder -> New -> Package
  • Enter the package name in "New Java Package" window which is opened
  • Click Finish

The Demo Package is created as "craftedforeveryone.com"

Step 3 - Create required Classes
  • Right Click newly created package -> New -> Class
  • Enter the Class name and click Finish

Here the class "Learning.java" with a sample code is created for demo purpose

Step 4 - Export the Project as JAR File
  • Right Click the Project Folder -> Export
  • Select Java -> JAR File and click Next
  • Choose the destination file name using Browse Button
  • Click Finish

Export Java Project

Export Java Project

Select JAR as export options

Select JAR Export location

Step 5 - Convert JAR File to DLL
  • Download and extract the IKVM.Net Library from https://www.ikvm.net/download.html
  • Copy the Exported JAR File to IKVM bin folder
  • Open the command prompt and navigate to IKVM bin folder
  • Execute the command ikvmc -target:library -out:CraftedForEveryoneJarToDLL.dll -recurse:"./*.jar"
  • All the JAR files in the IKVM bin folder will be converted to a single DLL file CraftedForEveryoneJarToDLL.dll

CraftedForEveryoneJarToDLL.dll is used only for demo purpose it can be replaced with your file name.

Step 6 - Reference the JAR file converted to DLL in Visual Studio Project
  • Create a new Visual Studio project or Open an existing project
  • Right click References and click Add Reference.
  • Browse to IKVM Bin folder and in the Reference Manager window
  • Add CraftedForEveryoneJarToDLL.dll and IKVM.Open.JDK.Core.dll as references
  • Click Ok

Add References in Visual Studio

Add References in Visual Studio

DLL References to be added

DLL References to be added

Step 7 - Execute the functions from the JAR file converted to DLL
  • Use the using keyword to import the packages from the JAR to Dll file
  • Write the suitable code as per your needs with objects of the class from JAR file

An example code is given below

Final output

Share this:

Like this:

Related

You may also like.

November 29, 2018

by Kaarthik · Published November 29, 2018 · Last modified February 23, 2019

Monitoring Windows CPU and RAM Utilization Using Perl via WMI

by Kaarthik · Published May 29, 2018 · Last modified May 30, 2018

Adding your own Custom Authorize Attribute to Asp. Net Core 2.2 and above

January 13, 2020

by Kaarthik · Published January 13, 2020 · Last modified January 17, 2020

6 Responses

why do I get this error

Please check if your Java Project Version is set to 1.7 or below.

If your project Java Version is 1.8 or above you will get this warning.

I keep getting this error:

fatal error IKVMC5015: Invalid path:

Could you please change the directory path to ikvmc folder before executing the command. That should resolve the issue.

I am using jdk1.7.0_97 and ikvm8.1.5717. Could you please help.

I an copying the jar files out side bin and in ikvmc folder and still seeing the error : fatal error IKVMC5015: Invalid path: Could you please suggest

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Используйте IKVM для преобразования пакета jar в файл dll и ссылки на него в сетевом проекте

1. Загрузите пакет jar

Сегодня, при использовании Baidu API для распознавания текста, я обнаружил ошибку в классе FileUtils в примере кода, и в VS нет прямого решения. Я думаю, что он должен вызывать некоторые файлы. Найдено в Интернете FileUtils Класс apache commons-io Под пакетом необходимо скачать этот пакет commons-io.jar Пакет. Дружественный адрес загрузки здесь.


После загрузки распакуйте его, чтобы увидеть commons-io-2.6.jar пакет


2. Используйте инструмент IKVM для преобразования пакета jar в библиотеку dll

Загрузите и разархивируйте как показано



Настройте переменные среды - укажите путь в [Переменные среды] -> [Системные переменные], путь - это каталог распакованного бина



Затем проверьте команду в командной строке ikvmc Действительно ли это, результат, показанный на рисунке, показывает, что конфигурация прошла успешно


Введите команду для генерации commons-io-2.6.jar из commons-io-2.6.jar


Тогда мы можем увидеть файл commons-io-2.6.dll в папке


3. Ввести библиотеку dll в VS

Мы хотим представить нашу преобразованную библиотеку dll в VS, а также представить три dll-файла, которые поставляются с IKVM. Эти три файла находятся в каталоге bin.

Добавить в [Проект] - [Добавить ссылку]


Final,It’s worth looking forward to, after all, how many sunsets and dawn.

Читайте также: