【问题标题】:Java: load a library that depends on other libsJava:加载依赖于其他库的库
【发布时间】:2011-08-15 20:04:37
【问题描述】:

我想在我的 java 应用程序中加载我自己的本地库。这些本机库依赖于第三方库(当我的应用程序安装在客户端计算机上时,这些库可能存在也可能不存在)。

在我的 java 应用程序中,我要求用户指定依赖库的位置。获得此信息后,我将使用它来使用 JNI 代码更新“LD_LIBRARY_PATH”环境变量。以下是我用来更改“LD_LIBRARY_PATH”环境变量的代码sn-p。

Java 代码

public static final int setEnv(String key, String value) { 如果(键==空){ throw new NullPointerException("key 不能为 null"); } 如果(值 == 空){ throw new NullPointerException("值不能为空"); } 返回nativeSetEnv(键,值); } 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; 常量字符 *nativeValue = NULL; nativeKey = (*env)->GetStringUTFChars(env, key, NULL); nativeValue = (*env)->GetStringUTFChars(env, value, NULL); int result = setenv(nativeKey, nativeValue, 1); 返回(jint)结果; }

我也有相应的本地方法来获取环境变量。

我可以成功更新 LD_LIBRARY_PATH(此断言基于 C 例程 getenv() 的输出。

我仍然无法加载我的原生库。仍然没有检测到依赖的第三方库。

感谢任何帮助/指针。我使用的是 64 位 Linux。

编辑:

我写了一个 SSCE(用 C 语言)来测试动态加载器是否工作。这是SSCE

#包括 #包括 #包括 #包括 int main(int argc, const char* const argv[]) { const char* constdependentLibPath = "...:"; const char* const sharedLibrary = "..."; 字符 *newLibPath = NULL; 字符 *originalLibPath = NULL; int l1,l2,结果; 无效*句柄 = NULL; originalLibPath = getenv("LD_LIBRARY_PATH"); fprintf(stdout,"\n原始库路径 =%s\n",originalLibPath); l1 = strlen(originalLibPath); l2 = strlen(dependentLibPath); newLibPath = (char *)malloc((l1+l2)*sizeof(char)); strcpy(newLibPath,dependentLibPath); strcat(newLibPath,originalLibPath); fprintf(stdout,"\n新库路径 =%s\n",newLibPath); 结果 = setenv("LD_LIBRARY_PATH", newLibPath, 1); 如果(结果!= 0){ fprintf(stderr,"\n环境无法更新\n"); 退出(1); } newLibPath = getenv("LD_LIBRARY_PATH"); fprintf(stdout,"\n来自 env 的新库路径 =%s\n",newLibPath); 句柄 = dlopen(sharedLibrary, RTLD_NOW); 如果(句柄==NULL){ fprintf(stderr,"\n无法加载共享库:%s\n",dlerror()); 退出(1); } fprintf(stdout,"\n 共享库加载成功。\n"); 结果 = dlclose(句柄); 如果(结果!= 0){ fprintf(stderr,"\n无法卸载共享库:%s\n",dlerror()); 退出(1); } 返回0; }

C 代码也不起作用。显然,动态加载程序没有重新读取 LD_LIBRARY_PATH 环境变量。我需要弄清楚如何强制动态加载器重新读取 LD_LIBRARY_PATH 环境变量。

【问题讨论】:

  • 我真的不明白为什么它不起作用,因为我做了一些非常相似的事情(在 Windows 下),它就像一个魅力。顺便说一句,您是否尝试过(仅出于调试目的)使用 System.load(...) 将库加载到“默认”库目录中,以查看库是否已损坏(而不是您的 nativeSetEnv 代码) .但是,好问题 (+1)
  • ..这是题外话,但我认为你应该释放由 GetStringUTFChars 分配的内存
  • @Giacomo:如果我在启动我的应用程序时设置了 LD_LIBRARY_PATH,它就可以工作。我将释放分配的内存。感谢您指出。 :)
  • 我讨厌这样告诉你,但是有一个激进的“解决方案”可以帮助你克服这个问题。删除 JNI,创建一个在 Java 应用程序和该库之间进行调解的本机可执行文件。我记得在与这些杂乱无章的 JNI 事情(比如动态加载依赖库)进行了很多斗争之后,我在 Java 和动态库之间创建了一个 trait-union,其中包含一个使用 stdin 和 stdout 与 Java 通信的本机可执行文件,并且在极少数情况下使用文件系统(临时文件)。讨厌但很容易实现。
  • 所以我原来的评论是错误的。那是很久以前。在与 JNI 进行了很多斗争之后,我已经按照我在上一篇中所说的那样做了。评论。这可能更难或更容易,具体取决于 Java 和本机共享的数据量。如果您有兴趣,请参阅 ProcessBuilder 类。

标签: java loadlibrary setenv


【解决方案1】:

在此处查看接受的答案:

Changing LD_LIBRARY_PATH at runtime for ctypes

换句话说,您尝试做的事情是不可能的。您需要使用更新的 LD_LIBRARY_PATH 启动一个新进程(例如,使用 ProcessBuilder 并更新 environment() 以连接必要的目录)

【讨论】:

  • 我们说的是 Java,而不是 Python。
  • 当然,但重点仍然存在:作为本机 java 可执行文件的一部分运行的动态加载程序代码已经读取了 LD_LIBRARY_PATH 环境变量。
  • 我测试了你所说的,你很可能是对的。现在,我如何强制动态加载器重新读取 LD_LIBRARY_PATH 环境变量?谢谢。
  • 你不能;动态加载器不能那样工作。您需要使用更新的 LD_LIBRARY_PATH 启动一个新进程(例如,使用 ProcessBuilder 和更新 environment() 来连接必要的目录)。请注意,所有其他答案都建议您更新 java.library.path 系统属性,您可能已经注意到它没有抓住重点:您的问题在于本机库之间的依赖关系(而不是来自本机库的初始加载) Java),因此更新 Java 系统属性无济于事。
【解决方案2】:

这是一种用于以编程方式操作 JVM 库路径的 hack。注意:它依赖于 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

【讨论】:

  • 如果我没记错的话,JVM使用“java.library.path”来搜索共享库。在 linux 上使用 LD_LIBRARY_PATH 搜索依赖库,在 windows 上使用 PATH 搜索。在我的具体情况下,依赖共享库的加载失败。
  • 是的,你是对的。刚刚查了一下:kalblogs.blogspot.com/2009/01/java.html
【解决方案3】:

我已经成功地为 CollabNet Subversion Edge 实现了类似的东西,这取决于所有操作系统中的SIGAR libraries(我们支持 32 位和 64 位的 Windows/Linux/Sparc)...

Subversion Edge 是一个 Web 应用程序,可帮助您通过 Web 控制台管理 Subversion 存储库,并使用 SIGAR 到 SIGAR 库,帮助我们直接从操作系统提供用户数据值...您需要更新属性“java”的值.library.path”在运行时。 (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 服务(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 等)提供匹配的库版本。

【讨论】:

  • 您发布了与我相同的解决方案。我们尽量避免这样做。
猜你喜欢
  • 2018-04-09
  • 2012-06-19
  • 2020-12-08
  • 2012-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多