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上没什么用。)
答案 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
有几点需要注意:
jni.h
等)/EHsc
以避免此警告:“c:\ Program Files(x86)\ Microsoft Visual Studio 10.0 \ VC \ INCLUDE \ xlocale(323):警告C4530:使用了C ++异常处理程序,但是展开语义是未启用。指定/ EHsc“jvm.lib
,即使它应该延迟加载。delayimp.lib
。/LIBPATH
需要追踪/link
选项,以便链接器获取它。这是 .lib 文件的路径。/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。另一种方法是使用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的可能解决方案。
有关如何执行此操作的信息,请参阅DELAYLOAD,但它只是将/DELAYLOAD:jvm.dll
和Delayimp.lib
添加到链接器命令。这意味着加载QT插件时不会加载jvm.dll
,但是在需要时(注意不要求使用LoadLibrary()
和{{1} })。 (我不知道Linux或Mac上是否有类似的功能)
这可以是注册表值,配置文件或专门用于插件的环境变量(绝对不是其他应用程序可能依赖的环境变量GetProcAddress()
或JAVA_HOME
)。示例环境变量:
在QT插件调用依赖于JRE的任何函数之前,它会通过在JRE_HOME
的值的开头插入PATH
来修改其%DANQT_32_JRE_HOME%\bin\server;%DANQT_32_JRE_HOME%\bin\client;
环境变量。这意味着当QT插件执行其需要JRE的第一个操作时,它将从插入的目录中加载。 (64位的不同环境变量)。对于PATH
和bin\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上做些什么。