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

如何部署混合C++/Java(JNI)应用程序?,java,c++,qt,plugins,java-native-interface,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上,CM

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库目录将位于它们的路径中。如果没有,插件就退出,不加载

在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上可能不会有多大帮助)。

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

使用延迟DLL加载功能构建QT插件 请参见如何执行此操作,但它只是将
/DELAYLOAD:jvm.dll
Delayimp.lib
添加到链接器命令中。这意味着加载QT插件时,不会加载
jvm.dll
,而是在需要时加载(注意需要使用
LoadLibrary()
GetProcAddress()
)。(我不知道Linux或Mac上是否有类似的功能)

提供一种机制来通知QT插件要使用什么JRE 这可以是一个注册表值、一个配置文件或一个专门用于插件的环境变量(绝对不是其他应用程序可能依赖的环境变量
JAVA\u HOME
JRE\u HOME
)。环境变量示例:

  • DANQT\u 32\u JRE\u HOME=C:\Program Files(x86)\Java\jre7(用于32位JRE)
  • DANQT_64_JRE_HOME=C:\Program Files\Java\jre7(用于64位JRE)
QT插件修改其路径 在QT插件调用依赖于JRE的任何函数之前,它通过插入例如
%DANQT\u 32\u JRE\u HOME%\bin\server;%来修改其
路径
环境变量DANQT_32_JRE_HOME%\bin\client位于
路径
的值的开头。这意味着当QT插件执行其第一个需要JRE的操作时,它将从插入的目录中加载。(64位的环境变量不同)。至于
bin\server
bin\client
我的理解是,它们本质上是相同的,但出于运行时性能的原因,
server
在初始化过程中执行得更多


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

在@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
需要注意的几点:

  • 包括头文件的路径(
    jni.h
    等)
  • Audio>代码> /EHSC < /C> >避免此警告:“C:\Script文件(x86)\微软Visual Studio 10 \VC包含\xLaLaLE(323):警告C45 30:C++异常处理程序,但未启用语义。指定/EHSC”
  • 仍然需要包含
    jvm.lib
    ,即使它应该延迟加载
  • delayimp.lib
    必须包含在内
  • /LIBPATH
    需要跟随
    /link
    选项,以便链接器获得它。这是.lib文件的路径
  • /DELAYLOAD
    获取.dll文件,而不是.lib文件!如果您不小心将.lib文件提供给它,您不会得到有用的错误消息,它只会说“LINK:warning LNK4199:/DELAYLOAD:jvm.lib被忽略;没有从jvm.lib找到导入”
  • 在.cpp文件中,
    #包括“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
    }
    
    最好把它放在它自己的函数中,因为除了结构化异常处理(SEH)之外的
    \uu try
    /
    \uuu不允许与可能进行对象展开的代码共享函数

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

    一些有用的链接:


    由于我无法在Linux上找到一个与
    /DELAYLOAD
    等价的版本(这里问:),所以我一直在使用
    dlopen()
    。这并不像我想象的那么糟糕——我可以用这个技巧来描述