【问题标题】:How can I deploy a mixed C++/Java (JNI) application?如何部署混合 C++/Java (JNI) 应用程序?
【发布时间】:2012-02-02 22:04:35
【问题描述】:

tl;dr:C++ 插件需要调用 Java .jar 库。如何将其部署给用户而不会让人头疼?

我正在为一个 Qt 应用程序编写一个 Qt 插件。该插件需要调用现有的 Java 库。这需要跨平台(Win、Mac、Linux)和架构(32 位和 64 位 Intel,无 PPC)工作。

我编译并运行了一个简单的“hello world”JNI 示例。我将 CMake 脚本更新为“find_package(JNI REQUIRED)”等,因此它针对 jni.h 标头进行编译并动态链接到 JVM 库。

至少在 Windows 上,CMake 可以很好地找到在编译时使用的正确 JVM。我担心的是在运行时找到正确的 JRE(jvm.dll 等),因为我对用户计算机的控制较少。

当我将插件发送给我的用户时,它将如何工作?他们需要为正确的架构安装 JRE。但这并不意味着 JRE lib 目录将在他们的路径中。如果不是,插件就会退出并且不会加载。

在Windows上,64位JDK安装jvm.dll到:

C:\Program Files\Java\jre7\bin\server\jvm.dll

但是 32 位的 JDK 安装到:

C:\Program Files (x86)\Java\jre7\bin\client\jvm.dll

我了解 PF 与 PFx86 的区别,但我不明白服务器/客户端的东西。这些实际上是不同的 JRE 吗?

如果我针对一个 JRE 版本编译/链接并且用户有不同的版本,它会起作用吗?

我认为这在 Linux/Mac 上会更容易,但我还没有做到这一点。

感谢任何帮助。我不喜欢使用 JNI,但买不起 2000 美元的编译器来将 Java 变成本机代码库(反正我没有源代码),我听说 gcj 可能无法胜任这项任务(可能在 Windows 上没有多大帮助)。

【问题讨论】:

  • 您可以使用您的应用程序部署自己的 jre,我想这将是解决您的问题的最简单方法。这样您就可以确保您的本地库与 jre 一起使用,并且您不必为 dll 查找问题而苦恼。
  • 我认为许可证问题会阻止我部署自己的 JRE。关于如何做到这一点的任何建议?此外,在这种情况下,我可以控制应用程序,但假设我没有并且只是在编写插件怎么办?
  • 我以前从未这样做过,我只是在考虑 Open Office。如果我没记错的话,他们有一个包含 jre 的安装选项。如果您只是部署一个插件,我想您将不得不编写一个安装脚本,以确保使用正确的版本安装 java 并设置您的路径变量。
  • @Dan,插件是怎么安装的或者有什么配置?
  • @hmjd,在 Windows 上,它只是一个被复制到特定于应用程序的插件目录的 .dll。在 Mac/Linux 上基本相同,除了用户从源代码编译插件并将生成的 .so 或 .dylib 复制到位。

标签: java c++ qt plugins java-native-interface


【解决方案1】:

一些更详细的注释要添加到@hmjd 的答案中,其中很多细节让我感到困惑。

这是我用来编译和链接测试程序的命令行:

cl
  -I"C:\Program Files (x86)\Java\jdk1.7.0_02\include"
  -I"C:\Program Files (x86)\Java\jdk1.7.0_02\include\win32"
  /EHsc
  -MD
  Test.cpp
  jvm.lib
  delayimp.lib
  /link
  /LIBPATH:"C:\Program Files (x86)\Java\jdk1.7.0_02\lib"
  /DELAYLOAD:jvm.dll

需要注意的几点:

  1. 包括头文件的路径(jni.h 等)
  2. 添加/EHsc 以避免此警告:“c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : 警告 C4530: 使用 C++ 异常处理程序,但未启用展开语义. 指定 /EHsc"
  3. 仍然需要包含jvm.lib,即使它应该延迟加载。
  4. delayimp.lib 必须包含在内。
  5. /LIBPATH 需要在 /link 选项之后进行,以便链接器获取它。这是 .lib 文件的路径。
  6. /DELAYLOAD 获取 .dll 文件,而不是 .lib 文件!如果您不小心将 .lib 文件提供给它,您不会收到有用的错误消息,它只会显示“LINK : warning LNK4199: /DELAYLOAD:jvm.lib denied; no import found from jvm.lib”。

在.cpp文件#include "windows.h"中,找出哪个目录有jvm.dll,然后进行如下调用:

std::string temp = "C:\\Program Files (x86)\\Java\\jdk1.7.0_02\\jre\\bin\\client";
SetDllDirectory(temp.c_str());

在调用库中的任何函数之前执行此操作,因此当调用 LoadLibrary() 时,它知道在哪里可以找到 DLL。另一种方法是使用SetEnvironmentVariable() 修改PATH 变量,但SetDllDirectory() 似乎是更好的选择。

在你的启动代码中,添加如下代码:

__try
{
  // call a function from the DLL to make sure it can be loaded
  ::JNI_CreateJavaVM(...);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
  // If not, fail
}

最好将它放在它自己的函数中,因为__try/__except 结构化异常处理 (SEH) 东西不允许与可能进行对象展开的代码共享函数。

没有 SEH 的东西,如果找不到 DLL,程序就会崩溃。

一些有用的链接:

【讨论】:

    【解决方案2】:

    仅适用于 Windows 的可能解决方案。

    使用延迟 DLL 加载功能构建 QT 插件

    请参阅DELAYLOAD 了解如何执行此操作,但它只是将/DELAYLOAD:jvm.dllDelayimp.lib 添加到链接器命令中。这意味着在加载 QT 插件时不会加载 jvm.dll,但此时需要它(注意它确实需要使用 LoadLibrary()GetProcAddress())。 (不知道Linux或者Mac有没有类似的功能)

    提供一种机制来通知 QT 插件使用什么 JRE

    这可以是注册表值、配置文件或插件专用的环境变量(绝对不是其他应用程序可能依赖的环境变量JAVA_HOMEJRE_HOME)。示例环境变量:

    • DANQT_32_JRE_HOME=C:\Program Files (x86)\Java\jre7(用于 32 位 JRE)
    • DANQT_64_JRE_HOME=C:\Program Files\Java\jre7(用于 64 位 JRE)

    QT 插件修改其路径

    在 QT 插件调用任何依赖于 JRE 的函数之前,它会修改其 PATH 环境变量,例如,在 PATH 的值的开头插入 %DANQT_32_JRE_HOME%\bin\server;%DANQT_32_JRE_HOME%\bin\client;。这意味着当 QT 插件执行第一个需要 JRE 的操作时,它将从插入的目录中加载。 (64 位的不同环境变量)。至于bin\serverbin\client,我的理解是它们本质上是相同的,但出于运行时性能原因,server 在初始化期间执行得更多。

    如果 QT 插件是针对 JRE 6 构建的并且安装了 JRE 7,我不确定兼容性。如果存在兼容性问题,则将其作为先决条件安装要求,或者,如果允许(我不确定合法性),则将 jvm.dll 与 QT 插件一起发布。

    【讨论】:

    • 非常感谢您的帮助!这看起来是一个很好的解决方案。听起来 gcc 默认会延迟加载,所以我什至可能不需要在那里做任何事情(取决于我是否可以在加载发生之前调整适当的 PATH 变量)。如果这不起作用,我可以回到 QLibrary 并编写一堆包装函数。
    【解决方案3】:

    由于我无法在 Linux 上找到与 /DELAYLOAD 等效的内容(在此询问:How can I make lazy/delay loading work in Linux?),因此我无法使用 dlopen()。它并没有我想象的那么糟糕——我可以使用这里描述的技术:Alternatives to dlsym() and dlopen() in C++。事实上,jni.h 头文件似乎是为这种方法设计的。可能我一开始就应该在 Windows 上做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-02
      • 2013-07-27
      • 2011-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多