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

标签 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 header 进行编译并动态链接到 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 转换为 native 代码库(无论如何我没有源代码),而且我听说 gcj 可能无法胜任这项任务(并且可能在 Windows 上不会有太大帮助)。

最佳答案

要添加到@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:警告 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,程序就会崩溃。

一些有用的链接:

关于java - 如何部署混合 C++/Java (JNI) 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9120694/

相关文章:

c++ - 如何添加到 map <int, list <int>>

c++ - 如何释放在 GLU_TESS_COMBINE 回调中分配的内存

qt - 如果 QScrollArea 衍生品是从 .ui 文件创建的,则它是空的

c++ - 确定 QKeyEvent 的来源

java - 在 Tomcat9 上安装 PHP7 Servlet

java - java中的自更新游戏

java - 如何获取activiti中的 Activity 进程列表?

java - ImageIO 在读取期间为 60x45、0.5MB 抛出 IIOException - JPEG 图像文件

c++ - 促进类(class)交流c++

c++ - 为什么 QFontMetrics::lineSpacing() 小于字符边界框的高度?