有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

tl;DR:C++插件需要调用java。jar库。如何将其部署到用户身上而不会让人感到太头痛

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

我编译并运行了一个简单的“hello world”JNI示例。我将CMake脚本更新为“find_package(需要JNI)”等,因此它可以根据JNI进行编译。针对JVM库的h头和动态链接

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

当我将插件发送给我的用户时,它将如何工作?他们将需要安装一个JRE以获得合适的体系结构。但这并不意味着JRE库目录将位于它们的路径中。如果没有,插件就退出,不加载

在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) 个答案

  1. # 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. 添加^ {CD2}}以避免此警告:“C:\Script文件(x86)\微软Visual Studio 10 \vc包含\xLaLaLE(323):警告C45 30:C++异常处理程序,但未启用语义。指定/EHSC”
    3. 仍然需要包括jvm.lib,即使它应该延迟加载
    4. delayimp.lib必须包括在内
    5. /LIBPATH需要跟随/link选项,以便链接器获得它。这是通往的道路。lib文件
    6. /DELAYLOAD获取。dll文件,而不是。lib文件!如果你不小心给了它。lib文件您没有收到有用的错误消息,它只是说“LINK:warning LNK4199:/DELAYLOAD:jvm.lib被忽略;没有从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. # 3 楼答案

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

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

    请参阅DELAYLOAD了解如何执行此操作,但它只是将/DELAYLOAD:jvm.dllDelayimp.lib添加到链接器命令中。这意味着jvm.dll在加载QT插件时不会加载,而是在需要时加载(注意,需要使用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插件一起提供