Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplicity for executing Java from C++

Background info: I am used to program in Java and I know how to use Eclipse and Visual Studio.

Final objective: to create a GUI, preferably in Visual Studio, which executes Java functions.

What I wish to accomplish from this question: a button in C++ which, on click, executes a Java function and returns the results to C++. (probably by invoking a JVM)

I've currently considered the following datastructures:

  • Sharing data through 'common' files such as .txt files (but then how do I start the Java functions?)
  • Opening a socket (seems too complicated for this problem)
  • Connecting through a server (too complicated)
  • Invoking a JVM from C++ which then executes the Java file (I think this is the most reasonable way but this needs a lot of code)

Now I know about the existence of Jace, JNI and SWIG but I think they are very handy for making complicated programs, not easy interfaces. I don't want to make a complicated program hence I feel that learning all their commands is quite bothersome.

I have also read up on a lot of Stack Exchange questions asking the exact same thing but all of them seem to give very complicated answers.

So here is my question:

What is the absolute simplest way to execute a (if necessary: precompiled) Java function from C++ where the C++ code passes some arguments to this Java function

Thanks in advance.

like image 785
Jean-Paul Avatar asked Jun 25 '13 19:06

Jean-Paul


People also ask

What runs Java compiled code?

In Java, programs are not compiled into executable files; they are compiled into bytecode (as discussed earlier), which the JVM (Java Virtual Machine) then executes at runtime. Java source code is compiled into bytecode when we use the javac compiler.


1 Answers

Invoking a JVM from C++ which then executes the Java file (I think this is the most reasonable way but this needs a lot of code)

Yes, it definitely is the most reasonable way. And with JNI and the invocation API it's not even that much code.

Finding the jvm.dll

You could try things like hardcoding the path to the Oracle JVM's jvm.dll or searching for a file called jvm.dll in the programs folder, but all that is obviously extremely hacky. However, there is apparently a pretty easy solution: The registry. The key HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment contains a REG_SZ called CurrentVersion. You can read the value of this key (currently it's 1.7) and open a child key with that name (HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 in this example). That key will then contain a REG_SZ called RuntimeLib which is the path to your jvm.dll. Don't worry about Program files vs Program files (x86). WOW64 will automatically redirect your registry query to HKLM\SOFTWARE\Wow6432Node if you're a 32bit process on a 64bit windows and that key contains the path to the 32 bit jvm.dll. Code:

#include <Windows.h>
#include <jni.h> // C:\Program Files\Java\jdk1.7.0_10\include\jni.h

// ...

DWORD retval;
// fetch jvm.dll path from registry
HKEY jKey;
if (retval = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\JavaSoft\\Java Runtime Environment"), 0, KEY_READ, &jKey))
{
    RegCloseKey(jKey);
    // assuming you're using C++/CLI
    throw gcnew System::ComponentModel::Win32Exception(retval);
}

TCHAR versionString[16]; // version numbers shouldn't be longer than 16 chars
DWORD bufsize = 16 * sizeof(TCHAR);
if (retval = RegGetValue(jKey, NULL, TEXT("CurrentVersion"), RRF_RT_REG_SZ, NULL, versionString, &bufsize))
{
    RegCloseKey(jKey);
    // assuming you're using C++/CLI
    throw gcnew System::ComponentModel::Win32Exception(retval);
}

TCHAR* dllpath = new TCHAR[512];
bufsize = 512 * sizeof(TCHAR);
retval = RegGetValue(jKey, versionString, TEXT("RuntimeLib"), RRF_RT_REG_SZ, NULL, dllpath, &bufsize)
RegCloseKey(jKey);
if (retval)
{
    delete[] dllpath;
    // assuming you're using C++/CLI
    throw gcnew System::ComponentModel::Win32Exception(retval);
}

Loading the jvm.dll and getting the CreateJavaVM function

This part is pretty straightforward, you just use LoadLibrary and GetProcAddress:

HMODULE jniModule = LoadLibrary(dllpath);
delete[] dllpath;
if (jniModule == NULL)
    throw gcnew System::ComponentModel::Win32Exception();
typedef int (JNICALL * JNI_CreateJavaVM)(JavaVM** jvm, JNIEnv** env, JavaVMInitArgs* initargs);
JNI_CreateJavaVM createJavaVM = (JNI_CreateJavaVM)GetProcAddress(jniModule, "JNI_CreateJavaVM");

Creating the JVM

Now you can invoke that function:

JavaVMInitArgs initArgs;
initArgs.version = JNI_VERSION_1_6;
initArgs.nOptions = 0;
JavaVM* jvm;
JNIEnv* env;
if ((retval = createJavaVM(&jvm, &env, &initArgs)) != JNI_OK)
    throw gcnew System::Exception(); // beyond the scope of this answer

Congratulations! There's now a JVM running right inside your process! You would probably launch the JVM at the startup of your application. Unless you are 100% sure that you will only ever invoke Java code from the thread that just created the JVM, you can throw away the env pointer, but you have to keep the jvm pointer.

Getting the JNI environment (optional)

So now you created the JVM and your application is up and running and then somebody clicks that button. Now you want to invoke Java code. If you are 100% sure that you are right now on the thread that created the JVM in the previous step and you still have the env pointer, then you can skip this. Otherwise, perform a quick check if the current thread is attached to the JVM and attach it if it isn't:

JNIEnv* env;
bool mustDetach = false;
jint retval = jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (retval == JNI_EDETACHED)
{
    JavaVMAttachArgs args;
    args.version = JNI_VERSION_1_6;
    args.name = NULL;
    args.group = NULL;
    retval = jvm->AttachCurrentThread(&env, &args);
    mustDetach = true; // to clean up afterwards
}
if (retval != JNI_OK)
    throw gcnew System::Exception(); // should never happen
invokeJavaCode(env); // next step
if (mustDetach)
    jvm->DetachCurrentThread();

Invoking Java code

Now you are right there, you want to invoke that Java code and you even have the env pointer. You want the easiest solution, so this is how you call a static method:

jclass clazz = env->FindClass("com/myself/MyClass");
if (clazz == NULL)
    throw gcnew System::Exception();
jmethodID mid = env->GetStaticMethodID(clazz, "myStaticMethod", "<signature>");
if (mid == NULL)
    throw gcnew System::Exception();
<type> returnedValue = env->CallStatic<type>Method(clazz, mid, <arguments>);

You can use javap -s (command line tool) to determine a method's signature. <type> can be any primitive type (it must match the return type of the Java method). The arguments can be of any primitive type, as long as they match the arguments of the Java method.

The end

And there you have it: The easiest way to invoke Java code from C++ on Windows (actually only the first two parts are windows-specific...). Oh, and also the most efficient one. Screw databases and files. Using 127.0.0.1 sockets would be an option but that's significantly less efficient and probably not less work than this. Wow, this answer is a bit longer than I expected. Hopefully it helps.

like image 175
main-- Avatar answered Sep 29 '22 09:09

main--