Load jni library java

JNI, загрузка нативных библиотек. Меняем java.library.path на лету

В подмножестве экосистемы Java, относящейся в основном к JNI (без которого никуда не деться, если приходиться интегрироваться с каким-то legacy или просто редким и специфическим кодом, написанном на С или каком-то другом языке), есть такое понятие, как java.library.path. Вкратце, это в некотором роде аналог classpath, только не для Java классов и *.jar файлов, а для нативных библиотек — системное свойство, которое указывает JVM, где искать эти самые нативные библиотеки (.dll в винде или .so под юниксами).

Свойство это устанавливается один раз, перед запуском JVM, через глобальные system properties, или как ключ -Dname=value для JVM, и после этого оно становится read-only. Точнее, менять-то его можно, но никакого эффекта на работу программы это не окажет, т.к. после того как вы обновите это свойство, JVM не перечитает его и не будет использовать новое значение.

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

Однако, возможность менять java.library.path на лету была бы очень кстати — тогда бы не пришлись много раз генерить, переписывать и перезаписывать скрипты для запуска JBoss-a, например, чтобы отразить в них все нужные пути ДО старта аппсервера.

И такая возможность, изменять эти пути, по которым JVM ищет нативные библиотеки, на самом деле есть. Конкретные приемы показаны тут — blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically и еще вот тут — nicklothian.com/blog/2008/11/19/modify-javalibrarypath-at-runtime.

Читайте также:  Selenium webdriver methods python

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

* This source code was highlighted with Source Code Highlighter .

public static void loadLibrary( String libname) Runtime.getRuntime().loadLibrary0(getCallerClass(), libname);
>

* This source code was highlighted with Source Code Highlighter .

synchronized void loadLibrary0(Class fromClass, String libname) // Проверяем, разрешено ли загружать данную конкретную библиотеку
SecurityManager security = System.getSecurityManager();
if (security != null ) security.checkLink(libname);
>
if (libname.indexOf(( int ) File .separatorChar) != -1) throw new UnsatisfiedLinkError( «Directory separator» +
«should not appear in library name: » + libname);
>
ClassLoader.loadLibrary(fromClass, libname, false );
>

* This source code was highlighted with Source Code Highlighter .

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

// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];

* This source code was highlighted with Source Code Highlighter .

Код метода ClassLoader.loadLibrary(fromClass, libname, false), довольно длинный, и загроможденный многочисленными проверками, в сокращенном виде выглядит это так.

// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class fromClass, String name,
boolean isAbsolute)

ClassLoader loader = (fromClass == null ) ? null : fromClass.getClassLoader();
if (sys_paths == null ) // это то, что нам нужно
usr_paths = initializePath( «java.library.path» );

// а это для тех библиотек, которые загружаются из классов,
// загруженных из boot classpath.
sys_paths = initializePath( «sun.boot.library.path» );
>

// Дальше попытка загрузить библиотеку, и дальше,
// если найти ее так и не удалось, то —
// Oops, it failed
throw new UnsatisfiedLinkError( «no » + name + » in java.library.path» );
>

* This source code was highlighted with Source Code Highlighter .

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

Вы можете либо выставить в null свойство sys_paths у класслоадера, либо просто поменять свойства sys_paths / usr_paths, добавив к ним нужные пути к вашим нативным библиотекам.

Источник

JNI: Подружим Java и C++

Бывают моменты, когда в Java некоторые действия выполняются за пределами обычных Java-классов. Например, необходимо исполнить код, написанный на C/C++ или другом каком-нибудь языке.

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

Для наших целей существует возможность динамической загрузки нативных библиотек, вызываемая методом System.load() , о чем более подробно можно прочитать здесь.

Постановка задачи

Пусть нам необходимо реализовать класс, содержащий в себе нативный метод, выводящий на экран “Hello world”.

package ru.forwolk.test; public class JNIHelloWorld

Генерация заголовков

Сгенерируем заголовки данного класса для C++.

Сначала создадим папку в корне проекта, где будем собирать бинарники:

Затем, скомпилируем наш класс в данную директорию

javac -d bin/ src/ru/forwolk/test/JNIHelloWorld.java

В папке bin у нас появился class-файл. Вернее, в bin/ru/forwolk/test/. Переидем в папку bin и сгенерируем заголовки.

cd bin/ javah ru.forwolk.test.JNIHelloWorld 

Как видно, в нашей папке bin появился файл ru_forwolk_test_JNIHelloWorld.h. Для простоты переименуем его в JNIHelloWorld.h

mv ru_forwolk_test_JNIHelloWorld.h JNIHelloWorld.h

Открыв его, видим следующую картину:

JNIHelloWorld.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class ru_forwolk_test_JNIHelloWorld */ #ifndef _Included_ru_forwolk_test_JNIHelloWorld #define _Included_ru_forwolk_test_JNIHelloWorld #ifdef __cplusplus extern "C" < #endif /* * Class: ru_forwolk_test_JNIHelloWorld * Method: printHelloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_ru_forwolk_test_JNIHelloWorld_printHelloWorld (JNIEnv *, jobject); #ifdef __cplusplus >#endif #endif 

Реализация на C++

Создадим файл с исходниками JNIHelloWorld.cpp. Я для этой цели создал проект в Clion, в который вставил необходимый файл. Реализуем наш метод.

JNIHelloWorld.cpp #include #include "JNIHelloWorld.h" JNIEXPORT void JNICALL Java_ru_forwolk_test_JNIHelloWorld_printHelloWorld (JNIEnv *, jobject)

Чтобы в Clion все работало корректно, необходимо в файл CMakeLists.txt добавить библиотеки Java:

// Вместо $JAVA_HOME -- путь до Java include_directories($JAVA_HOME/include) include_directories($JAVA_HOME/include/linux) link_directories($JAVA_HOME/include) link_directories($JAVA_HOME/include/linux) 
g++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC JNIHelloWorld.cpp -shared -o helloworld.so -Wl,-soname -Wl,--no-whole-archive

Загрузка в Java

В корневой папке проекта появился файл helloworld.so. Переместим его в папку bin/ проекта Java.

Теперь необходимо загрузить нашу библиотеку. Хорошей практикой будет статическая загрузка библиотеки прямо в классе. Добавим загрузку прямо в класс JNIHelloWorld

Теперь мы можем полноценно использовать данный класс. Давайте проверим.

public static void main(String[] args)
Hello world! Process finished with exit source 0 

Передача параметров

А что делать, если нам надо не только выполнять какой-то код, а также передавать параметры и получать ответ? Рассмотрим еще один метод, выполняющий умножение двух чисел. Добавим в класс JNIHelloWorld метод

native int multiply(int a, int b); 

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

/* * Class: ru_forwolk_test_JNIHelloWorld * Method: multiply * Signature: (II)I */ JNIEXPORT jint JNICALL Java_ru_forwolk_test_JNIHelloWorld_multiply (JNIEnv *, jobject, jint, jint); 

Реализуем метод в JNIHelloWorld.cpp

JNIEXPORT jint JNICALL Java_ru_forwolk_test_JNIHelloWorld_multiply (JNIEnv *, jobject, jint a, jint b)

Опять же произведем описанные выше действия по подтягиванию библиотеки, добавим строку в main по выводу результата произведения двух чисел и запустим

public static void main(String[] args)
4 Hello world! Process finished with exit source 0 

Заключение

Мы с вами рассмотрели возможность Java использовать код, написанный на C/C++. Это можно применять в различных целях, например, для увеличения скорости исполнения кода, для защиты кода от прямого вмешательства и для прочих целей. Очень надеюсь, что данная статья поможет вам разобраться в основах JNI.

Весь код я выложил в открытый доступ. В директории cpp разместил класс C++ без лишних файлов проекта Clion.

Источник

How to Load Native JNI Library from JAR

The JNI (Java Native Interface) is a framework that provides a bridge between Java and native applications. Java applications can define native methods which are implemented in dynamic library written in other languages such as C or C++. The dynamic library is able to call both static and dynamic methods. More information about JNI can be found on Wiki or in tutorial Beginning JNI with NetBeans (for Linux).

The problem is that for loading such a dynamic library you have to call method System.load(String filename) which requires an absolute filename. This approach is just fine if you have dynamic library outside the application’s JAR archive, but when bundling dynamic library within the JAR it is necessary to extract the library into filesystem before loading it. And that’s exactly what my code does.

Our simple JNI class could look like this:

public class HelloJNI  static  System.load("/path/to/my/library.so"); > public native void hello(); >

To extract the library before loading it it’s necessary to add some code into the static section. I wrapped it into a static method inside simple class called NativeUtils. I decided to put it into separate class in order to have space for adding more features (like choosing the right dynamic library for host OS and architecture). The class can be found on my Github.

The code is commented and self-explaining, so I don’t have to write too much about it. Just three notes:

  • The file path is passed as string, not as instance of File. It is because File transforms the abstract path to system-specific (absolute path decision, directory delimiters) one, which could cause problems. It must be an absolute path (starting with ‘/’) and the filename has to be at least three characters long (due to restrictions of File.createTempFile(String prefix, String suffix).
  • The temporary file is stored into temp directory specified by java.io.tmpdir (by default it’s the operating system’s temporary directory). It should be automatically deleted when the application exits.
  • Although the code has some try-finally section (to be sure that streams are closed properly in case an exception is thrown), it does not catch exceptions. The exception has to be handled by the application. I belive this approach is cleaner and has some benefits.

Final usage is pretty simple. 🙂 Just call method loadLibraryFromJar and handle exception somehow:

import cz.adamh.NativeUtils; public class HelloJNI  static  try  NativeUtils.loadLibraryFromJar("/resources/libHelloJNI.so"); > catch (IOException e)  e.printStackTrace(); // This is probably not the best way to handle exception :-) > > public native void hello(); >

Edited 2013-04-02: Lofi came with a workaround to release and delete our DLL from temporary directory on Widnows.

Get the whole code from my Github!

Источник

Оцените статью