Simplicity for executing Java from C++

后端 未结 1 446
孤城傲影
孤城傲影 2021-02-06 05:18

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 S

相关标签:
1条回答
  • 2021-02-06 06:18

    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.

    0 讨论(0)
提交回复
热议问题