如何部署混合C ++ / Java(JNI)应用程序?

时间:2012-02-02 22:04:36

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

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目录(ies)将在其路径中。如果它们不是,那么插件就会挽救并且不会加载。

在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上没什么用。)

3 个答案:

答案 0 :(得分:2)

要添加到@ 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. 必须包含
  5. delayimp.lib
  6. /LIBPATH需要追踪/link选项,以便链接器获取它。这是 .lib 文件的路径。
  7. /DELAYLOAD获取 .dll 文件,而不是.lib文件!如果您不小心给它.lib文件,您没有收到有用的错误消息,它只是说“LINK:警告LNK4199:/DELAYLOAD:jvm.lib被忽略;没有从jvm.lib找到导入”。
  8. 在.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。另一种方法是使用PATH修改SetEnvironmentVariable()变量,但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,程序就会崩溃。

    一些有用的链接:

答案 1 :(得分:1)

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

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

有关如何执行此操作的信息,请参阅DELAYLOAD,但它只是将/DELAYLOAD:jvm.dllDelayimp.lib添加到链接器命令。这意味着加载QT插件时不会加载jvm.dll,但是在需要时(注意要求使用LoadLibrary()和{{1} })。 (我不知道Linux或Mac上是否有类似的功能)

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

这可以是注册表值,配置文件或专门用于插件的环境变量(绝对不是其他应用程序可能依赖的环境变量GetProcAddress()JAVA_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的任何函数之前,它会通过在JRE_HOME的值的开头插入PATH来修改其%DANQT_32_JRE_HOME%\bin\server;%DANQT_32_JRE_HOME%\bin\client;环境变量。这意味着当QT插件执行其需要JRE的第一个操作时,它将从插入的目录中加载。 (64位的不同环境变量)。对于PATHbin\server,我的理解是这些基本相同,但bin\client在初始化期间因运行时性能原因执行的更多。

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

答案 2 :(得分:0)

由于我无法在Linux上找到与/DELAYLOAD相当的内容(在此处询问:How can I make lazy/delay loading work in Linux?),因此我使用dlopen()。它没有我想象的那么糟糕 - 我可以使用这里描述的技术:Alternatives to dlsym() and dlopen() in C++。事实上,似乎jni.h标头是为这种方法设计的。也许我首先应该在Windows上做些什么。