Java: загрузить библиотеку, которая зависит от других библиотек

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

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

Java-код


public static final int setEnv(String key, String value) {
        if (key == null) {
            throw new NullPointerException("key cannot be null");
        }
        if (value == null) {
            throw new NullPointerException("value cannot be null");
        }
        return nativeSetEnv(key, value);
    }

public static final native int nativeSetEnv(String key, String value);

Код JNI (C)

    JNIEXPORT jint JNICALL Java_Test_nativeSetEnv(JNIEnv *env, jclass cls, jstring key, jstring value) {
    const char *nativeKey = NULL;
    const char *nativeValue = NULL;
    nativeKey = (*env)->GetStringUTFChars(env, key, NULL);
    nativeValue = (*env)->GetStringUTFChars(env, value, NULL);
    int result = setenv(nativeKey, nativeValue, 1);
    return (jint) result;
}

У меня также есть соответствующие нативные методы для получения переменной среды.

Я могу успешно обновить LD_LIBRARY_PATH (это утверждение основано на выводе подпрограммы C getenv().

Я все еще не могу загрузить свою родную библиотеку. Зависимые сторонние библиотеки по-прежнему не обнаружены.

Любая помощь/указатели приветствуются. Я использую Linux 64 бит.

Редактировать:

Я написал SSCE (на C), чтобы проверить, работает ли динамический загрузчик. Вот это СССЭ.

#include 
#include 
#include 
#include 

int main(int argc, const char* const argv[]) {

    const char* const dependentLibPath = "...:";
    const char* const sharedLibrary = "...";
    char *newLibPath = NULL;
    char *originalLibPath = NULL;
    int l1, l2, result;
    void* handle = NULL;

    originalLibPath = getenv("LD_LIBRARY_PATH");
    fprintf(stdout,"\nOriginal library path =%s\n",originalLibPath);
    l1 = strlen(originalLibPath);
    l2 = strlen(dependentLibPath);
    newLibPath = (char *)malloc((l1+l2)*sizeof(char));
    strcpy(newLibPath,dependentLibPath);
    strcat(newLibPath,originalLibPath);
    fprintf(stdout,"\nNew library path =%s\n",newLibPath);

    result = setenv("LD_LIBRARY_PATH", newLibPath, 1);
    if(result!=0) {
        fprintf(stderr,"\nEnvironment could not be updated\n");
        exit(1);
    }    
    newLibPath = getenv("LD_LIBRARY_PATH");
    fprintf(stdout,"\nNew library path from the env =%s\n",newLibPath);

    handle = dlopen(sharedLibrary, RTLD_NOW);
    if(handle==NULL) {
        fprintf(stderr,"\nCould not load the shared library: %s\n",dlerror());
        exit(1);        
    }
    fprintf(stdout,"\n The shared library was successfully loaded.\n");

    result = dlclose(handle);
    if(result!=0) {
        fprintf(stderr,"\nCould not unload the shared library: %s\n",dlerror());
        exit(1);
    }

    return 0;
}

Код C также не работает. Судя по всему, динамический загрузчик не перечитывает переменную окружения LD_LIBRARY_PATH. Мне нужно выяснить, как заставить динамический загрузчик перечитать переменную среды LD_LIBRARY_PATH.


person Santosh Tiwari    schedule 29.04.2011    source источник
comment
Я действительно не понимаю, почему это не работает, так как я сделал что-то действительно похожее (под Windows), и это работает как шарм. Кстати, пробовали ли вы (только в целях отладки) загружать библиотеки с помощью System.load(...), помещая их в каталог lib по умолчанию, просто чтобы посмотреть, не сломаны ли библиотеки (а не код nativeSetEnv). Однако хороший вопрос (+1)   -  person gd1    schedule 30.04.2011
comment
... и это не по теме, но я думаю, вам следует освободить память, выделенную GetStringUTFChars   -  person gd1    schedule 30.04.2011
comment
@Giacomo: это работает, если я устанавливаю LD_LIBRARY_PATH при запуске своего приложения. Я освобожу выделенную память. Спасибо, что указали на это. :)   -  person Santosh Tiwari    schedule 02.05.2011
comment
Ненавижу говорить вам об этом, но есть радикальное решение, которое может помочь вам преодолеть эту проблему. Отбросьте JNI, создайте собственный исполняемый файл, который является посредником между приложением Java и этими библиотеками. Я помню, что после того, как я много боролся с этими грязными вещами JNI (например, с динамической загрузкой зависимых библиотек), я создал трейт-объединение между Java и динамическими библиотеками с собственным исполняемым файлом, который взаимодействует с Java с помощью stdin и stdout, а в редких случаях использует файловую систему (временные файлы). Неприятный, но очень простой в реализации.   -  person gd1    schedule 02.05.2011
comment
Так что мой первоначальный комментарий был неверным. Это было давно. После того, как я много боролся с JNI, я сделал то, что сказал в своем предыдущем. комментарий. Это может быть сложнее или проще в зависимости от того, сколько данных используется Java и нативным материалом. См. класс ProcessBuilder, если вам интересно.   -  person gd1    schedule 02.05.2011


Ответы (3)


Смотрите принятый ответ здесь:

Изменение LD_LIBRARY_PATH во время выполнения для ctypes

Другими словами, то, что вы пытаетесь сделать, невозможно. Вам потребуется запустить новый процесс с обновленным LD_LIBRARY_PATH (например, используйте ProcessBuilder и обновить environment() для объединения нужного каталога)

person Brett Kail    schedule 29.04.2011
comment
Мы говорим о Java, а не о Python. - person Peter Knego; 30.04.2011
comment
Конечно, но суть остается: код динамического загрузчика, запускаемый как часть нативного исполняемого файла Java, уже прочитал переменную окружения LD_LIBRARY_PATH. - person Brett Kail; 30.04.2011
comment
Я проверил то, что вы сказали, и, скорее всего, вы правы. Теперь, как мне заставить динамический загрузчик перечитать переменную среды LD_LIBRARY_PATH? Спасибо. - person Santosh Tiwari; 02.05.2011
comment
Вы не можете; динамический загрузчик так не работает. Вам нужно будет запустить новый процесс с обновленным LD_LIBRARY_PATH (например, используйте ProcessBuilder и update environment() для объединения необходимого каталога). Обратите внимание, что все остальные ответы предполагают, что вы обновляете системное свойство java.library.path, которое, как вы, вероятно, заметили, упускает из виду: ваша проблема связана с зависимостями между собственными библиотеками (а не с начальной загрузкой собственной библиотеки из Java), поэтому обновление системного свойства Java не поможет. - person Brett Kail; 03.05.2011

Это хак, используемый для программного управления путем к библиотеке JVM. ПРИМЕЧАНИЕ. Он зависит от внутренних компонентов реализации ClassLoader, поэтому он может работать не на всех JVM/версиях.

String currentPath = System.getProperty("java.library.path");
System.setProperty( "java.library.path", currentPath + ":/path/to/my/libs" );

// this forces JVM to reload "java.library.path" property
Field fieldSysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
fieldSysPath.setAccessible( true );
fieldSysPath.set( null, null );

В этом коде используются разделители путей к файлам в стиле UNIX ('/') и разделители путей к библиотекам (':'). Для межплатформенного способа сделать это используйте свойства системы, чтобы получить системные разделители: http://download.oracle.com/javase/tutorial/essential/environment/sysprop.html

person Peter Knego    schedule 30.04.2011
comment
Если я не ошибаюсь, JVM использует java.library.path для поиска разделяемых библиотек. Зависимые библиотеки ищутся с использованием LD_LIBRARY_PATH в Linux и PATH в Windows. В моем конкретном случае происходит сбой загрузки зависимых общих библиотек. - person Santosh Tiwari; 02.05.2011
comment
Да, ты прав. Только что посмотрел: kalblogs.blogspot.com/2009/01/java.html - person Peter Knego; 03.05.2011

Я успешно реализовал нечто подобное для CollabNet Subversion Edge, которое зависит от библиотек SIGAR во ВСЕХ операционных системах. (мы поддерживаем Windows/Linux/Sparc как 32-битные, так и 64-битные)...

Subversion Edge — это веб-приложение, которое помогает управлять репозиториями Subversion через веб-консоль и использует SIGAR для библиотек SIGAR, что помогает нам предоставлять пользователям значения данных непосредственно из ОС... Вам необходимо обновить значение свойства «java.library. путь" во время выполнения. (https://ctf.open.collab.net/integration/viewvc/viewvc.cgi/trunk/console/grails-app/services/com/collabnet/svnedge/console/OperatingSystemService.groovy?revision=1890&root=svnedge&system=exsy1005&view=markup Обратите внимание, что URL-адрес представляет собой код Groovy, но здесь я изменил его на Java)...

Следующий пример представляет собой реализацию в приведенном выше URL-адресе... (В Windows ваш пользователь должен будет перезагрузить компьютер, если он/она загрузил библиотеки после или загрузил их с помощью вашего приложения)... Файл "java.library. path" обновит путь пользователя "usr_paths" вместо системного пути "sys_paths" (при использовании последнего может возникнуть исключение разрешений в зависимости от ОС).

133/**
134 * Updates the java.library.path at run-time.
135 * @param libraryDirPath
136 */
137 public void addDirToJavaLibraryPathAtRuntime(String libraryDirPath) 
138    throws Exception {
139    try {
140         Field field = ClassLoader.class.getDeclaredField("usr_paths");
141         field.setAccessible(true);
142         String[] paths = (String[])field.get(null);
143         for (int i = 0; i < paths.length; i++) {
144             if (libraryDirPath.equals(paths[i])) {
145                 return;
146             }
147         }
148         String[] tmp = new String[paths.length+1];
149         System.arraycopy(paths,0,tmp,0,paths.length);
150         tmp[paths.length] = libraryDirPath;
151         field.set(null,tmp);
152         String javaLib = "java.library.path";
153         System.setProperty(javaLib, System.getProperty(javaLib) +
154             File.pathSeparator + libraryDirPath);
155 
156     } catch (IllegalAccessException e) {
157         throw new IOException("Failed to get permissions to set " +
158             "library path to " + libraryDirPath);
159     } catch (NoSuchFieldException e) {
160         throw new IOException("Failed to get field handle to set " +
161            "library path to " + libraryDirPath);
162     }
163 }

Класс консоли Bootstrap services (приложение Groovy on Grails) запускает службу и выполняет ее с полным путем к каталогу библиотеки... Серверам на основе UNiX не нужно перезапускать сервер, чтобы получить библиотеки, но Windows делает это. нужен перезапуск сервера после установки. В вашем случае вы бы назвали это следующим образом:

     String appHomePath = "/YOUR/PATH/HERE/TO/YOUR/LIBRARY/DIRECTORY";
     String yourLib = new File(appHomePath, "SUBDIRECTORY/").getCanonicalPath();
124  try {
125      addDirToJavaLibraryPathAtRuntime(yourLib);
126  } catch (Exception e) {
127      log.error("Error adding the MY Libraries at " + yourLib + " " +
128            "java.library.path: " + e.message);
129  }

Для каждой ОС, в которую вы отправляете свое приложение, просто убедитесь, что вы предоставили соответствующую версию библиотек для конкретной платформы (32-разрядная версия Linux, 64-разрядная версия Windows и т. д.).

person Marcello de Sales    schedule 30.04.2011
comment
Вы разместили то же решение, что и я. Мы стараемся этого не делать. - person Peter Knego; 30.04.2011