java - 手动加载 native 库以规避限制性环境

标签 java jdbc dll java-native-interface classloader

我正在维护一个 Java Swing 应用程序,该应用程序需要连接到 Microsoft SQL Server 实例。由于各种原因,我选择用 jTDS 替换正在使用的 native SQL Server 驱动程序(前面提到的 Microsoft 驱动程序当时无法工作,并且显然在该领域也失败了)。当我尝试在 IDE 外部运行可执行文件 .jar 时,我遇到了问题,因为我缺少适当的 ntlmauth.dll 依赖项。

在继续之前,请务必注意此应用程序是在极其严格的(仅限 Windows)环境中开发和使用的:

  • 我无法安装任何需要 Windows UAC 身份验证的软件
  • 我的用户无法安装或运行任何需要 UAC 身份验证的软件
  • 这目前意味着我无法将文件写入 System32 或 JAVA_HOME,并且无法使用任何类型的 ProcessBuilder 愚蠢行为来使用我需要的任何命令行参数启动另一个 JVM
  • 我无法使用仅在首次安装/设置时需要 UAC 权限的可执行包装程序/安装程序

我正在尝试的解决方案是 this one 的组合和 this one to check it --本质上是将.dll打包在.jar中,然后提取它并在必要时加载它--因为我发现的大多数其他解决方案与上述限制不兼容;然而,我遇到了一个问题,即使在表面上“加载” native 库之后,我也会收到一个异常,说它没有。

我的预启动代码:

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);
    }
}

如您所见,我基本上逐字复制了第一个链接的解决方案,并进行了一些细微的修改,只是为了尝试让应用程序运行。我还从第二个链接复制了该类并将其命名为 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]    

从日志的第三行(如果您不想滚动的话,这是最后一个条目)可以看到该库确实已“加载”。但是,我只是使用了我觉得可能使用 native 库的类(我也尝试了 TdsCore 类,但无济于事),因为演示如何执行此操作的示例只是使用库所在包中的随机类需要在。

我在这里缺少什么吗?我对 JNI 或 ClassLoaders 的内部工作原理不是很有经验,所以我可能只是加载错误。任何意见或建议将不胜感激!

最佳答案

好吧,我想出了一个解决方法:我最终使用了 JarClassLoader 。这基本上需要将我的所有依赖项(Java 和 native )复制到我的主 .jar 中的“libraries”文件夹中,并在 IDE 中禁用 .jar 签名。然后,该应用程序由一个新类运行,该类只需创建一个新的 JarClassLoader 对象并运行“invokeMain”方法 - 网站上有一个示例。在我连续几天用头撞墙之后,整个过程大约花了三分钟。

希望有一天这可以帮助别人!

关于java - 手动加载 native 库以规避限制性环境,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31526417/

相关文章:

java - 我可以使用 spring DI 将 session 属性注入(inject)到我的 web 应用程序的 POJO 中吗

java - 在 Coldfusion 中使用 Apache Cassandra

java - 即使表中的数据不为零,使用简单算术运算和别名时 JDBC 查询也会返回零

mysql - MYSQL 的 JDBC 连接配置在 Jmeter 中不起作用

C# Dll 库不向 Delphi 应用程序返回输出参数值

python - 找到 dll,但无法从当前工作目录加载

java - 打印数字三角形

java - Groovy getter/setter 速记符号和 API 更改

dll - 从导入的 DLL 调用 Metatrader MQL4/MQL5 函数

java - 当要设置的列因行而异时,批量使用 JDBCPreparedStatement