手动加载本机库以规避限制性环境
Manually loading native libraries to circumvent a restrictive environment
我正在维护一个 Java Swing 应用程序,它需要连接到 Microsoft SQL 服务器的一个实例。出于各种原因,我选择用 jTDS 替换正在使用的本机 SQL 服务器驱动程序(上述 Microsoft 驱动程序当时无法正常工作,并且在现场显然也失败了)。当我尝试 运行 IDE 之外的可执行文件 .jar 时,我 运行 遇到了问题,因为我缺少适当的 ntlmauth.dll 依赖项。
在继续之前,请务必注意此应用程序是在极其受限(仅Windows)的环境中开发和使用的:
- 我无法安装任何需要 Windows UAC 身份验证的软件
- 我的用户无法安装或运行任何需要 UAC 身份验证的软件
- 目前这意味着我无法将文件写入 System32 或 JAVA_HOME,并且无法使用任何类型的 ProcessBuilder tomfoolery 来启动另一个 JVM 以及我需要的任何命令行参数
- 我无法使用可执行文件 wrappers/installers,它只需要第一次使用 UAC 权限 installation/setup
我正在尝试的解决方案是 this one and this one to check it 的组合——本质上是将 .dll 打包到 .jar 中,然后将其解压并在必要时加载——就像我使用的大多数其他解决方案一样'发现已不符合上述限制条件;但是,我 运行 遇到了一个问题,即使在本机库表面上 "loaded," 我得到一个例外,说它不是。
我的预启动代码:
private static final String LIB_BIN = "/lib-bin/";
private static final String JTDS_AUTH = "ntlmauth";
// load required JTDS binaries
static {
logger.info("Attempting to load library {}.dll", JTDS_AUTH);
try {
System.loadLibrary(JTDS_AUTH);
} catch (UnsatisfiedLinkError e) {
loadFromJar();
}
try {
// do some quick checks to make sure that went ok
NativeLibraries nl = new NativeLibraries();
logger.debug("Loaded libraries: {}", nl.getLoadedLibraries().toString());
} catch (NoSuchFieldException ex) {
logger.info("Native library checker load failed", ex);
}
}
/**
* When packaged into JAR extracts DLLs, places these into
*/
private static void loadFromJar() {
// we need to put DLL in temp dir
String path = ***;
loadLib(path, JTDS_AUTH);
}
/**
* Puts library to temp dir and loads to memory
*/
private static void loadLib(String path, String name) {
name = name + ".dll";
try {
// have to use a stream
InputStream in = net.sourceforge.jtds.jdbc.JtdsConnection.class.getResourceAsStream(LIB_BIN + name);
// always write to different location
File fileOut = new File(System.getProperty("java.io.tmpdir") + "/" + path + LIB_BIN + name);
logger.info("Writing dll to: " + fileOut.getAbsolutePath());
OutputStream out = FileUtils.openOutputStream(fileOut);
IOUtils.copy(in, out);
in.close();
out.close();
System.load(fileOut.toString());
} catch (Exception e) {
logger.error("Exception with native library loader", e);
JOptionPane.showMessageDialog(null, "Exception loading native libraries: " + e.getLocalizedMessage(), "Exception", JOptionPane.ERROR_MESSAGE);
}
}
如您所见,我基本上逐字复制了第一个 link 的解决方案,并进行了一些小的修改,只是为了尝试获得应用程序 运行ning。我还从第二个 link 复制了 class 并将其命名为 NativeLibraries,该方法的调用是相当不相关的,但它显示在日志中。
无论如何,这里是启动应用程序时日志输出的相关位:
2015-07-20 12:32:33 INFO - Attempting to load library ntlmauth.dll
2015-07-20 12:32:33 INFO - Writing dll to: C:\Users\***\lib-bin\ntlmauth.dll
2015-07-20 12:32:33 DEBUG - Loaded libraries: [C:\Program Files\Java\jre1.8.0_45\bin\zip.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_d3d.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_sw.dll, C:\Program Files\Java\jre1.8.0_45\bin\msvcr100.dll, C:\Program Files\Java\jre1.8.0_45\bin\glass.dll, C:\Program Files\Java\jre1.8.0_45\bin\net.dll, C:\Users\***\lib-bin\ntlmauth.dll]
2015-07-20 12:32:33 INFO - Application startup
***
2015-07-20 12:32:36 ERROR - Database exception
java.sql.SQLException: I/O Error: SSO Failed: Native SSPI library not loaded. Check the java.library.path system property.
at net.sourceforge.jtds.jdbc.TdsCore.login(TdsCore.java:654) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.JtdsConnection.<init>(JtdsConnection.java:371) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.Driver.connect(Driver.java:184) ~[jtds-1.3.1.jar:1.3.1]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]
从日志的第三行可以看出该库确实是 "loaded,"(如果您不想滚动,这是最后一个条目)。但是,我只是简单地使用了 class,我觉得这可能是在使用本机库(我也尝试了 TdsCore class 但无济于事),因为展示如何执行此操作的示例只是使用随机 class 来自需要库的包。
我在这里遗漏了什么吗?我对 JNI 或 ClassLoader 的内部工作原理不是很有经验,所以我可能只是加载错误。任何意见或建议将不胜感激!
好吧,我想出了一个解决方法:我最终使用了 JarClassLoader。这基本上需要将我的所有依赖项(包括 Java 和本机)复制到我的主 .jar 中的 "libraries" 文件夹中,并在 IDE 中禁用 .jar 签名。然后,应用程序由一个新的 class 运行 简单地创建一个新的 JarClassLoader 对象并 运行 使用 "invokeMain" 方法——网站上有一个示例。在我用头撞墙几天之后,整个过程只用了大约三分钟。
希望有一天这对某人有所帮助!
我正在维护一个 Java Swing 应用程序,它需要连接到 Microsoft SQL 服务器的一个实例。出于各种原因,我选择用 jTDS 替换正在使用的本机 SQL 服务器驱动程序(上述 Microsoft 驱动程序当时无法正常工作,并且在现场显然也失败了)。当我尝试 运行 IDE 之外的可执行文件 .jar 时,我 运行 遇到了问题,因为我缺少适当的 ntlmauth.dll 依赖项。
在继续之前,请务必注意此应用程序是在极其受限(仅Windows)的环境中开发和使用的:
- 我无法安装任何需要 Windows UAC 身份验证的软件
- 我的用户无法安装或运行任何需要 UAC 身份验证的软件
- 目前这意味着我无法将文件写入 System32 或 JAVA_HOME,并且无法使用任何类型的 ProcessBuilder tomfoolery 来启动另一个 JVM 以及我需要的任何命令行参数
- 我无法使用可执行文件 wrappers/installers,它只需要第一次使用 UAC 权限 installation/setup
我正在尝试的解决方案是 this one and this one to check it 的组合——本质上是将 .dll 打包到 .jar 中,然后将其解压并在必要时加载——就像我使用的大多数其他解决方案一样'发现已不符合上述限制条件;但是,我 运行 遇到了一个问题,即使在本机库表面上 "loaded," 我得到一个例外,说它不是。
我的预启动代码:
private static final String LIB_BIN = "/lib-bin/";
private static final String JTDS_AUTH = "ntlmauth";
// load required JTDS binaries
static {
logger.info("Attempting to load library {}.dll", JTDS_AUTH);
try {
System.loadLibrary(JTDS_AUTH);
} catch (UnsatisfiedLinkError e) {
loadFromJar();
}
try {
// do some quick checks to make sure that went ok
NativeLibraries nl = new NativeLibraries();
logger.debug("Loaded libraries: {}", nl.getLoadedLibraries().toString());
} catch (NoSuchFieldException ex) {
logger.info("Native library checker load failed", ex);
}
}
/**
* When packaged into JAR extracts DLLs, places these into
*/
private static void loadFromJar() {
// we need to put DLL in temp dir
String path = ***;
loadLib(path, JTDS_AUTH);
}
/**
* Puts library to temp dir and loads to memory
*/
private static void loadLib(String path, String name) {
name = name + ".dll";
try {
// have to use a stream
InputStream in = net.sourceforge.jtds.jdbc.JtdsConnection.class.getResourceAsStream(LIB_BIN + name);
// always write to different location
File fileOut = new File(System.getProperty("java.io.tmpdir") + "/" + path + LIB_BIN + name);
logger.info("Writing dll to: " + fileOut.getAbsolutePath());
OutputStream out = FileUtils.openOutputStream(fileOut);
IOUtils.copy(in, out);
in.close();
out.close();
System.load(fileOut.toString());
} catch (Exception e) {
logger.error("Exception with native library loader", e);
JOptionPane.showMessageDialog(null, "Exception loading native libraries: " + e.getLocalizedMessage(), "Exception", JOptionPane.ERROR_MESSAGE);
}
}
如您所见,我基本上逐字复制了第一个 link 的解决方案,并进行了一些小的修改,只是为了尝试获得应用程序 运行ning。我还从第二个 link 复制了 class 并将其命名为 NativeLibraries,该方法的调用是相当不相关的,但它显示在日志中。
无论如何,这里是启动应用程序时日志输出的相关位:
2015-07-20 12:32:33 INFO - Attempting to load library ntlmauth.dll
2015-07-20 12:32:33 INFO - Writing dll to: C:\Users\***\lib-bin\ntlmauth.dll
2015-07-20 12:32:33 DEBUG - Loaded libraries: [C:\Program Files\Java\jre1.8.0_45\bin\zip.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_d3d.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_sw.dll, C:\Program Files\Java\jre1.8.0_45\bin\msvcr100.dll, C:\Program Files\Java\jre1.8.0_45\bin\glass.dll, C:\Program Files\Java\jre1.8.0_45\bin\net.dll, C:\Users\***\lib-bin\ntlmauth.dll]
2015-07-20 12:32:33 INFO - Application startup
***
2015-07-20 12:32:36 ERROR - Database exception
java.sql.SQLException: I/O Error: SSO Failed: Native SSPI library not loaded. Check the java.library.path system property.
at net.sourceforge.jtds.jdbc.TdsCore.login(TdsCore.java:654) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.JtdsConnection.<init>(JtdsConnection.java:371) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.Driver.connect(Driver.java:184) ~[jtds-1.3.1.jar:1.3.1]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]
从日志的第三行可以看出该库确实是 "loaded,"(如果您不想滚动,这是最后一个条目)。但是,我只是简单地使用了 class,我觉得这可能是在使用本机库(我也尝试了 TdsCore class 但无济于事),因为展示如何执行此操作的示例只是使用随机 class 来自需要库的包。
我在这里遗漏了什么吗?我对 JNI 或 ClassLoader 的内部工作原理不是很有经验,所以我可能只是加载错误。任何意见或建议将不胜感激!
好吧,我想出了一个解决方法:我最终使用了 JarClassLoader。这基本上需要将我的所有依赖项(包括 Java 和本机)复制到我的主 .jar 中的 "libraries" 文件夹中,并在 IDE 中禁用 .jar 签名。然后,应用程序由一个新的 class 运行 简单地创建一个新的 JarClassLoader 对象并 运行 使用 "invokeMain" 方法——网站上有一个示例。在我用头撞墙几天之后,整个过程只用了大约三分钟。
希望有一天这对某人有所帮助!