Java dll как сделать

Обновлено: 04.07.2024

Я разрабатываю приложение Java, которое должно быть выпущено как банку. Эта программа зависит от внешних библиотек С++, называемых JNI. Чтобы загрузить их, я использую метод System.load с абсолютным путем, и это отлично работает.

Однако я действительно хочу "спрятать" их внутри JAR, поэтому я создал пакет для их сбора. Это заставляет меня загружать относительный путь - путь пакета. Благодаря такому подходу я разрешаю пользователю запускать JAR в любом каталоге, не беспокоясь о необходимости связывать библиотеки DLL или скучать с предыдущим процессом установки.

Это генерирует ожидаемое исключение:

Исключение в потоке "main" java.lang.UnsatisfiedLinkError: ожидающий абсолютный путь библиотеки

Как я могу заставить это работать?

Подход к копированию DLL в папку (объясняется ниже) работает только при запуске в среде eclipse. Запуск экспортированного JAR, двоичные файлы DLL хорошо созданы, но загрузка JNI создает следующее исключение:

Исключение в потоке "main" java.lang.reflect.InvocationTargetException

Я запускаю этот метод загрузки:

Я думаю, что это может быть проблема с правами доступа, но не знаю, как ее решить. Как вы думаете?

ОТВЕТЫ

Ответ 1

Я не верю, что вы можете загрузить DLL непосредственно из JAR. Вы должны предпринять промежуточный шаг по копированию DLL из JAR. Следующий код должен сделать это:

Ответ 2

В основном это должно работать. Так как это делает JNA, просто загрузите его и изучите код. У вас даже есть некоторые подсказки, чтобы сделать эту платформу независимой.

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

Ответ 3

Этот JarClassLoader, стремящийся решить ту же проблему:

Ответ 4

Вам нужно закодировать загрузчик классов с расположением DLL, но его можно загрузить без извлечения из банки. Достаточно простого перед выполнением вызова загрузки. В своем основном классе добавьте:

В частности, я столкнулся с той же проблемой с обработкой JNA и OSGi, как загружаются DLL.

Можно, но сложно и не нужно.

R-Magistr
2) Для их работы будет ли необходима Ява-машина или нет?

Для вашей задачи может хватить и BASIC'а.

1. Можно спать на потолке, но это потребует кучи усилий, а удовольствия- никакого.

Главное -- представлять, зачем такую хрень делать.

1) можно ли писать DLL на JAVA

Только на Java DLL написать невозможно. Можно на Java + C.
Это чисто теоретически. Потому что практически это -- бредовая идея, всё равно, что вырезать апендицит топором.

2) Для их работы будет ли необходима Ява-машина или нет?

Да, естественно. Под Java-машиной надо понимать JRE.

Начнём с того, что Java -- ни разу не мощный язык. Это популярный кроссплатформный объектно-ориентированный бейсик для хипстеров.

Мощность- это скорее возможность решить любую задачу. В пределах виртуальный машины- решим любую.

Присоединяюсь к вопросу по созданию java-DLL. Объясню задачу. Есть куча проектов на Delphi. Планируется переходить на java. Так же постоянно требуется добавлять/изменять функционал в существующие. Идеальный вариант: писать все новые функции на java, встраивая их в Delphi. В дальнейшем можно будет их использовать в новом проекте.
Пока нашёл как это сделать через JNI. Но мне это вариант не очень нравится. Причины:
1) Нужно ставить JRE, а это лишние действия на клиентском компе. К тому же через JNI удалось взаимодействовать только с 32-битной JVM.
2) Рядом с exe-шником будет валяться папка classes с байт-кодом java. Если бы можно было хотя бы jar собрать.

VVS_AMD
1) Нужно ставить JRE

JRE нужно НЕ для JNI,а для Java
Странное желание "переходить на java" и НЕ ставить JRE.
Любой врачь без проблем по фотографии дигноз поставил бы.
VVS_AMD
JNI удалось взаимодействовать только с 32-битной JVM.

Не верю ( C )
Если Host-приложение 32-битное, то логично, что и DLL (JVE) можно грузить только 32-битные (ограничение Windows)
Если Host-приложение само 64-битное, то. в общем аналогично )))

Хотите мешать 32 и 64 бита в одном приложении - сделать можно, но зачем? Любое средство между-процессорного взаимодействия. Можно родными средствами Windows (например COM), можно родными средствами Java - JRMI.

VVS_AMD
2) Рядом с exe-шником будет валяться папка classes с байт-кодом java. Если бы можно было хотя бы jar собрать.

Кто мешает собрать JAR ?
См. ответ на Вашу притензию N 1

Java сейчас не по хипстерски.
Надо брать Kotlin
К тому же Kotlin native не нужен JVM.

Затем, если будете переезжать на JVM, то с Kotlin это будет сделать легче.

Технология JNI позволяет из программы на Java обратиться к программе на C/C++ и наоборот.

JNI пример

Рассмотрим пример вызова функций в программе на С++ из программы на Java. Сначала сделаем простое приложение на Java.

Java приложение

Строим приложение: имя приложения JNI_App, главный класс — JNI_App_Class:

здесь метод fromDLL отмечен как native, этот метод будет реализован на C++.

мы из нашего приложения на Java обращаемся к программе на C++.

Прежде чем заняться реализацией метода fromDLL на C++ создадим заголовочный файл. Для этого используем утилиту javah из папки bin Java каталога.

Заголовочный файл

здесь утилита javah работает с нашим классом JNI_App_Class.class.

Если запустить на выполнение схему, то получим ошибку. Схему в реальном случае надо доработать, указав путь к классу, т.е. classpath. На моём компьютере этот класс находится в D:MySiteJavaCodeJNI_Appbuildclasses. Указав путь к классу JNI_App_Class.class, получаем:

javah -classpath .;D:MySiteJavaCodeJNI_Appbuildclasses -jni pack.JNI_App_Class

Ещё одно обстоятельство: заголовочный файл jni_md из C:Program FilesJavajdk1.6.0_16includewin32 копируем в C:Program FilesJavajdk1.6.0_16include, там находится jni.

Запускаем javah и получаем заголовочный файл на C++:

Настал момент создать dll на C++, в которой и будет использован наш заголовочный файл.

Как создать DLL?

Скопируем в папку этого проекта заготовленный в предыдущем разделе заголовочный файл pack_JNI_App_Class.h и присоединим его к проекту.

Теперь сделаем новый файл pack_JNI_App_Class.cpp, где реализуем функцию fromDLL:

Компилируем наше C++ приложение, получаем длл — JNI_DLL.dll.

java.library.path

Приложение на Java будет искать нашу длл в каталогах, указанных в java.library.path. Чтоб получить список этих каталогов, делаем так:

Мы можем поместить длл JNI_DLL.dll в любой из этих каталогов и приложение найдёт её там. Но можно изменить java.library.path. Просто добавим к PATH путь к новому каталогу, где и будем хранить наши длл. После изменения PATH надо перезапустить IDE.

Итак, создаём новый каталог MyJNILibrary, добавляем путь к нему в переменную среды PATH. Копию полученной длл JNI_DLL.dll помещаем в MyJNILibrary.

Теперь всё готово для обращения из приложения на Java к длл на C++. Запускаем java-приложение JNI_App и получаем результат из длл:

Окей. Как мне стало известно из изучения Java, что по сути Java не общается с самой операционной системой, она посылает запрос в JVM и обрабатывает его. Например получение Jav'ой доступу к принтеру. Но! Java не может получать доступ к регистру, автозапуску и прочим вещам. Окей я не буду вдаваться в подробности зачем, но просто есть необходимость.

Например у меня есть задача, определена точно ОС - Windows. И мне необходимо "хукать" нажатия клавиатуры, или производить эти нажатия самой программой. Я нашел решение - написание dll библиотеки на C++. Но как мне ее написать. Если мы пишем код на C++ подключая библиотеку windows.h для работы с winapi. И при этом скомпилированный этот код в виде библиотеки подрубаем в Java код. Ничего не потеряется?


2,883 5 5 золотых знаков 21 21 серебряный знак 51 51 бронзовый знак Производить нажатия можно с помощью класса java.awt.Robot. А чтобы хукать, Вы правильно написали, необходимо нативно писать либу. Вот туториал по написанию на C stuf.ro/calling-c-code-from-java-using-jna @AndrewBystrov а вот именно вложенные в библиотеку, которую я создам, стандартные библиотеки они никаким образом не отделяться/потеряются? ну я не столь силен в jni, поэтому я могу только предположить, что не потеряются. Главное, чтобы они попали в либу, но это уже надо будет прописать в сборщике проекта ( ant/maven etc) Посмотрите на jnativehook, библиотека делает половину того, что вам нужно, может пригодиться, как источник информации. зачем создавать велосипед? Есть замечательная библиотека JNA, уверен, многие необходимые функции уже реализованы до вас (Работа с реестром, автозапуском, аппаратной частью). По поводу передачи управления - есть класс java.awt.Robot . p.s. JNA поддерживает не только Windows, но и различные вариации unix

Из JVM есть несколько дверей в операционную систему - JNI, JNA и JNR. JNI (Java Native Interface) - самый старый и чаще всего используемый способ взаимодействия с нативным кодом. Его минус в том, что он требует от программиста знания C/C++ и хорошего понимания принципов работы виртуальной машины. JNA (Java Native Access) - это надстройка над JNI изолирующая программиста от низкоуровневых деталей за счёт использования рефлексии и libffi. А где рефлексия, там низкая производительность и высокое количество рантайм ошибок. В JNR (Java Native Runtime) рефлексию заменили генерацией байткода, что существенно увеличило производительность. Это относительно молодой проект, я в нём ещё не копался и сказать ничего не могу. А вот пример примитивного кейлогера с использованием JNI показать могу.

Snitch.java

Компилируем командой javac -h . Snitch.java . Ключ -h указывает в каком каталоге создать заголовочный файл с определениями нативных методов.

snitch.c

Компилируем c'шный исходник в snitch.dll, запускаем нашу программу командой java Snitch и убеждаемся, что в C:\Temp\keyboard.txt регистрируются нажатые клавиши.

Чуть более подробно о процессе компиляции и объявлениях JNI-методов можно посмотреть в другом моём ответе.

При вызове библиотеки java dll для начинающих часто возникают ошибки, такие как не загружаемая библиотека и метод, который не найден (UnsatisfiedLinkError и т. Д.). Эта статья подробно анализирует общие проблемы и дает более полное решение.

Тело:

Написать нативный метод в Java для реализации вызова DLL, общий процесс выглядит следующим образом:

Затем с помощью команды javah создайте файл заголовка и получите:

Создайте проект C (платформа Eclipse), скопируйте указанный выше заголовочный файл и запишите файл .c или .cpp для реализации собственного метода

Постройте, получите файл dll, измените его на name.dll и поместите в проект java.

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

Ошибка типа A, файл dll не найден / не может быть загружен:

0.java.lang.UnsatisfiedLinkError: no XXX in java.library.path

1.java.lang.UnsatisfiedLinkError: Can't load library

2. UnsatisfiedLinkError: XXXXXXX не является допустимым приложением Win32.

Ошибка типа B, во всех успешно загруженных файлах DLL вызываемый метод не может быть найден:

Ошибка типа C, компилятор C.C / C ++ не может найти / не может проанализировать заголовочный файл jni.h:

Ошибка типа D, версия jvm конфликтует с dll:

0.ava.lang.UnsatisfiedLinkError: Невозможно загрузить 32-разрядную версию DLL IA на 64-разрядной платформе AMD. Этот тип ошибки легко понять, то есть 32-битная jvm использует 64-битную dll или наоборот. Это также относительно легко решить, обычно изменить соответствующую версию DLL или изменить версию JDK, но это более хлопотно.

Для вышеуказанных 4 типов проблем тип D относительно прост для решения и не будет объяснен снова.

Класс C: компилятор C.C / C ++ не может найти / не может проанализировать заголовочный файл jni.h

Этот тип ошибки не является проблемой написания нашего кода, это проблема платформы компиляции. Пока платформа компилятора знает, где найти этот заголовочный файл, это нормально. Для eclipse, если в качестве компилятора C используется mingw, лучшим решением будет поместить include / jni.h и include / win32 / jni_md.h в каталог jdk в папке mingw, в папке номера версии mingw. Например, в каталоге include мой каталог: D: \ Cbianyi \ mingw64 \ x86_64-w64-mingw32 \ include. Если вы используете VS6, вы можете добавить вышеуказанный каталог jdk в расположении ниже:


Если это VS2017, просто установите каталог включения. Лично рекомендую писать C-код, лучше использовать VS, VS6 также удобнее, чем Eclipse.

Класс A: ошибка поиска / загрузки dll

Мы загружаем dll, которая на самом деле включает в себя два процесса поиска и загрузки, из которых процесс поиска dll выполняется слоем java, а загрузка выполняется jvm.

A.0 и A.1 ищут dll: как правило, в ссылке "найти".

Есть два способа найти и загрузить dll: Общий метод - System.loadLibrary ("libname"), параметр - имя файла dll без суффикса (в дальнейшем именуемый sys.loadlib). Источник ошибки, как правило, в этом методе. Другой - использовать System.load («имя файла»), параметр - это абсолютный путь к dll с суффиксом (в дальнейшем именуемым sys.load). Если этот метод используется, он в принципе не ошибается, если путь не указан неправильно или есть проблема с самим файлом dll. Фактически, эти два метода в конечном итоге найдут dll по абсолютному пути, но для sys.loadlib java завершит это имя библиотеки как имя файла, а затем найдет его. Давайте посмотрим на исходный код.

Эти два метода встречаются в ClassLoader. LoadLibrary (Class <?> FromClass, String name, логическое значение isAbsolute) (в дальнейшем именуемый loader.load). При использовании метода sys.load параметр isAbsolute, передаваемый этому методу, имеет значение true со следующим исходным кодом:

Это показывает, что для sys.load, который дает абсолютный путь, java непосредственно найдет этот путь, если нет, он выдаст UnsatisfiedLinkError («Can't load library:» + name).

Если вы используете sys.loadlib, то isAbsolute имеет значение false, вы должны сначала завершить абсолютный путь. Процесс завершения является громоздким, и наиболее непосредственно связаны с нами:

Другими словами, завершенные пути являются содержимым двух массивов sys_path и use_paths. Если это не может быть найдено, он предложит UnsatisfiedLinkError ("нет" + имя + "в java.library.path"). Если мы не устанавливаем другие параметры, эти два пути представляют переменные среды jre и system соответственно. Очевидно, что если вы вызываете эти dll вне системы, вы можете поместить записанные dll в эти переменные среды. Кроме того, вы можете указать каталог java.library.path. Если указан этот каталог, в приведенном выше коде в usr_path будет только один каталог, а не системная переменная окружения. Есть много способов указать. Однако, поместив dll, который вы написали в переменную окружения (или установив ее как переменную окружения), и метод установки libpath, когда проект перемещается, изменяется и освобождается, dll не может быть найдена.

Решение, которое я дал, состоит в том, что вместо использования метода sys.loadlib дается «абсолютный путь». Точно так же мы не можем написать путь. Фактически, загрузка dll и входного потока файла, очень похожего, состоит в том, чтобы сначала найти файл, а затем прочитать его в память. Поэтому файл dll можно рассматривать как обычный файл ресурсов. Я считаю, что у программиста может быть несколько способов найти файл в проекте, независимо от того, как проект перемещен и выпущен. Метод, который я использую, состоит в том, чтобы сначала получить путь к классу, а затем соединить его в соответствии с относительными отношениями между путем к классу и путем к DLL. Таким образом, независимо от того, как проект перемещен и выпущен, проблем не будет. Следующим образом я помещу файл B32.dll для загрузки в каталог lib на том же уровне, что и Native:

Таким образом, вы можете гарантировать, чтобы найти DLL.

A.2 Загрузка DLL

Независимо от того, использовать ли относительный или абсолютный путь для поиска dll, когда эта dll найдена, jvm попытается загрузить dll в память через систему LoadLibrary системы Windows (в дальнейшем называемая win.load). Эта часть отделена от слоя Java. Чтобы подтвердить эту точку зрения, необходимы некоторые базовые знания о Си. Давайте сделаем отладку с ollydbg:


После добавления точки останова в bp LoadLibraryA после нескольких выпусков вы увидите:


Это FileName является именно той DLL, которая будет загружена.

Затем точка останова в ret, чтобы получить позицию отображения B32 в памяти:


Перейдите к 0x8B00000, чтобы увидеть память,


Можно определить, что B32.dll был успешно сопоставлен с памятью.

Кроме того, мы можем доказать, что пока имя файла B32.dll, jvm будет вызывать метод win.load и пытаться его загрузить. Создайте новый пустой текстовый файл, переименуйте его в B32.dll, по-прежнему отлаживая в соответствии с вышеуказанным способом, вы все равно можете получить:


Однако ret вернет 0, потому что это не действительный / легальный файл DLL, и программа сообщит об ошибке:



Кроме того, давайте проверим, что System.loadlibrary ("B32"), который загружается по имени файла, также завершит утверждение абсолютного пути. В этом предложении в исходном коде абсолютный путь не указан. Другие настройки не выполняются.

Параметры отладки следующие:


Параметры на рисунке: -Djava.library.path = "F: \ java32 \ jdk1.8.0_181 \ bin \ lib" Main, что означает указание java.library.path.

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


B32 картографический адрес:


Но при использовании sys.loadlib система автоматически завершает суффикс как dll. В это время при использовании .exe при поиске выдает ошибку, и более поздний процесс загрузки не будет.

Краткое описание проблем класса А. Сначала Java ищет файл на основе заданного абсолютного пути или дополненного пути, если это так, JVM пытается загрузить файл. В это время, если файл является легальным dll (win32) Файл, процесс загрузки считается успешным. Если файл не существует или не является легальным файлом DLL (win32), произойдет ошибка.

Ошибка типа B, во всех успешно загруженных файлах DLL вызываемый метод не может быть найден:

Когда Java вызывает собственный метод, он отправляется в экспортированную таблицу имен всех успешно загруженных библиотек, чтобы найти соответствующее имя метода, и, если оно принято, его можно вызвать. Если вы не можете найти его, вы сообщите о такой проблеме. Конечно, когда JVM работает, его невозможно найти при каждом вызове, но есть набор методов обработки. Данные просматриваются через таблицу сопоставления адресов. Эта часть слишком низкого уровня для изучения. Давайте рассмотрим наиболее тесно связанные с ошибками класса B: пока имя метода, вызываемого java, находится в экспортированной таблице имен dll, его можно успешно вызывать. Есть два места, чтобы отметить и проверить здесь:

Во-первых, его можно найти в таблице имен экспорта. Это не просто означает, что этот метод существует в таблице имен экспорта, он также включает и может быть найден системой. Этот момент я объяснил в первой статье:dll экспортирует стратегию поиска таблицы имен, Эта проблема была также обнаружена в этой Java, и я не знал ее раньше.

Во-вторых, при вызове для сравнения используется только имя метода, независимо от параметров метода, типа возвращаемого значения и т. Д. Например, я хочу вызвать в java метод без параметров и без возвращаемого значения типа void say (), а то, что экспортируется в dll, это 3 параметра, скажем, int (arg1, arg2, arg3) типа возврата int, Это также можно назвать.

В-третьих, он успешно вызывается, но метод не гарантированно успешно выполняется.

Давайте проверим следующее:

Уровень Java остается неизменным, мы загружаем B32.dll и пытаемся вызвать собственный метод void say (String s). После того, как этот метод скомпилирован заголовочным файлом, вы получите:

extern "C" JNIEXPORT void JNICALL Java_Native_say (JNIEnv *, jclass, jstring). Другими словами, найдите метод Java_Native_say в B32.dll. Extern "C", JNIEXPORT и JNICALL - базовые знания языка Си, если вы не понимаете, вы можете проверить это сами.

Сначала мы делаем «рутинный» процесс. Метод в C:

После компиляции Dll, проверьте структуру PE, чтобы получить адрес памяти всего метода:


Отладка dbg, в соответствии с методом отладки, приведенным выше, находит запись метода, точки останова и одношаговой операции. Видно, что программа здесь запускается. Пять вызовов соответствуют put, printf, printf, env-> GetStringUTFChars и вставляет в исходный код C. За исключением того, что четвертый cal является преобразованием строки, остальные четыре будут получать каждый раз, когда они выполняются. Соответствующий вывод.

Это показывает, что метод Java, вызывающий dll, в памяти, должен перейти к соответствующему адресу памяти метода.

Исходя из этого, давайте проверим, что «при вызове для сравнения используется только имя метода, независимо от параметров метода, типа возвращаемого значения и т. Д.»

Слой Java остается неизменным, и мы удаляем оригинальный B32.dll. В окнах системного каталога просто найдите dll.


Вставьте его в наш каталог lib и переименуйте в B32.dll.

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


Имя исходного первого элемента - «AccConvertAccessMaskToActrlAccess». Затем проверьте адрес функции еще раз:


Хорошо, начните отладку dbg. Получите адрес изображения этого псевдо "B32.dll", добавьте RVA и установите точку останова:


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

Хорошо, теперь вы можете быть уверены, что метод вызова dll в java и метод вызова dll в программе на C в основном одинаковы: все они находятся в таблице экспорта dll и выполняются способом C.

Для нормальной разработки, согласно автоматически сгенерированному файлу .h, непосредственно скопируйте объявление метода, а затем сгенерируйте dll, почему я не могу его найти?

На самом деле, строго в соответствии с методом, который я дал выше, если вы компилируете 32-битную DLL, вы не можете найти ее. Для extern "C" JNIEXPORT void JNICALL Java_Native_say (JNIEnv *, jclass, jstring) это определение метода, независимо от eclipse, VS6, VS2017, именем экспортируемой таблицы будет _Java_Native_say @ 12, и вы не получите Java_Native_say. Я скомпилировал шестнадцатеричные данные и изменил имя метода, как я делал выше. Этот метод, очевидно, не работает. Если существует много методов экспорта, не было бы очень трудным изменить их один за другим. В Eclipse для редактирования C используется mingw. Если он 32-битный, то есть параметры, которые можно изменить, чтобы напрямую генерировать метод без дополнительных символов, для VS я не нашел метод. Если это 64-битная платформа, будь то Eclipse или VS, она будет напрямую генерировать «Java_Native_say».

Кроме того, скажем немного больше. При написании собственного исходного кода, если он не объявлен в файле заголовка или в .cpp / c, а только определен, тогда extern "C" JNIEXPORT void JNICALL Java_Native_say (JNIEnv *, jclass, jstring) должен быть завершен , Слово не может быть меньше: если оно объявлено сначала, а затем определено, жизнь должна быть гарантированно завершена, когда она определена, extern "C" JNIEXPORT может быть опущен, просто напишите void JNICALL Java_Native_say (JNIEnv *, jclass, jstring).

Наконец, после понимания содержания вопроса AB выше, мы можем предположить или проверить:

1. System.load / loadLibrary не должен быть записан в классе, где объявлен нативный метод, и не должен быть записан в static <>. В любом месте кода просто вызовите sys.load перед вызовом нативного метода. Просто поместите его в static <> собственного класса, чтобы гарантировать, что процесс загрузки не будет пропущен.

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

Суммируйте основные моменты:

Во-первых: укажите точный путь, чтобы java мог найти файл dll; легальная dll, чтобы jvm мог успешно загрузить dll.

Второе: точное имя метода таблицы имен экспорта dll, чтобы JVM могла найти адрес памяти, на котором расположен собственный метод, и завершить вызов.

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