tl;dr: C++ plugin needs to call Java .jar library. How to deploy this to users without too much headache?
I\'m writing a Qt plugin for a Qt applica
Some more detailed notes to add to @hmjd's answer, a lot of these details tripped me up.
This is the command line I used to compile and link a test program:
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
A few things to note:
jni.h
, etc.)/EHsc
to avoid this warning: "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\INCLUDE\xlocale(323) : warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc"jvm.lib
, even though it should be delay loaded.delayimp.lib
has to be included./LIBPATH
needs to go after the /link
option so the linker gets it. This is the path to the .lib file./DELAYLOAD
gets the .dll file, not the .lib file! If you accidentally give it the .lib file you don't get a useful error message, it just says "LINK : warning LNK4199: /DELAYLOAD:jvm.lib ignored; no imports found from jvm.lib".In the .cpp file, #include "windows.h"
, figure out which directory has the jvm.dll
, and make a call such as this:
std::string temp = "C:\\Program Files (x86)\\Java\\jdk1.7.0_02\\jre\\bin\\client";
SetDllDirectory(temp.c_str());
Do that before calling any function from the library, so when LoadLibrary() is called it knows where to find the DLL. An alternative is to modify the PATH
variable with SetEnvironmentVariable()
but SetDllDirectory()
seems like a better choice.
Somewhere in your startup code, add code like this:
__try
{
// call a function from the DLL to make sure it can be loaded
::JNI_CreateJavaVM(...);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
// If not, fail
}
Best to put that in its own function somewhere, since the __try
/__except
Structured Exception Handling (SEH) stuff doesn't tolerate sharing a function with code that might doing object unwinding.
Without the SEH stuff, the program just crashes if the DLL can't be found.
Some links that were helpful:
A possible solution for Windows only.
See DELAYLOAD on how to do this but it is just adding /DELAYLOAD:jvm.dll
and Delayimp.lib
to the linker command. It means that the jvm.dll
will not be loaded when the QT plugin is loaded but at the point it is required (note it does not require using LoadLibrary()
and GetProcAddress()
). (I don't know if there is a similar feature on Linux or Mac)
This could be either a registry value, a configuration file or an environment variable specifically for the plugin (definitely not environment variables JAVA_HOME
or JRE_HOME
that other applications may depend on). Example environment variables:
Before the QT plugin invokes any functions dependent on the JRE it modifies its PATH
environment variable by inserting, for example, %DANQT_32_JRE_HOME%\bin\server;%DANQT_32_JRE_HOME%\bin\client;
at the start of the value for PATH
. This means when the QT plugin performs its first action that requires the JRE it will be loaded from the inserted directories. (Different environment variables for 64-bit). As for bin\server
and bin\client
my understanding is that these are essentially the same but the server
performs more during initialisation for runtime performance reasons.
I am unsure of compatibility if the QT plugin was built against JRE 6 and JRE 7 was installed. If there are compatibility issues then make it a prerequisite installation requirement or, if permitted (I am unsure of legalities), ship the jvm.dll
with the QT plugin.
Since I can't figure out an equivalent to /DELAYLOAD
on Linux (asked here: How can I make lazy/delay loading work in Linux?), I'm stuck using dlopen()
. It's not as bad as I thought it would be -- I can use the technique described here: Alternatives to dlsym() and dlopen() in C++ . In fact, it seems like the jni.h header was designed for this approach. Probably what I should have done in the first place, on Windows too.