Search code examples
javac++windowsjava-native-interface

How to use JNI without having to add jvm.dll location into Environment Variables Path


One way to make JNI work is to go into Environment Variables and add into User or System Path the folder where jvm.dll is located (.../bin/server). But this means that on a client machine, when your app is launched, you need to check if the path is added, and if it isn't, to add it.

But instead of doing this, can you give the location to JavaVMOption, or using another way, but inside the code and not having to add that location to Environment Variables?

I'm using Eclipse Adoptium which will be added with the app files with a license agreement, so the location will be dependent to where the app is placed/installed, making the option with Environment Variables a bit annoying as the location could change, so you need to remove the old one and add the new one each time in this situation.

This is what I use to create the JVM, where javaLocation is std::string, and it is given as a parameter for the function which contains the location of the app folder:

    javaLocation.insert(0, "-Djava.class.path=");
    javaLocation.append("Data\\Java");
    JavaVMInitArgs vm_args;
    JavaVMOption* options = new JavaVMOption[1];
    options[0].optionString = &javaLocation[0];
    vm_args.version = JNI_VERSION_10;
    vm_args.nOptions = 1;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;
    JNIEnv* env = nullptr;
    jint rc = JNI_OK;
    if (jvm == nullptr) {
        rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
    }
    else {
        rc = jvm->AttachCurrentThread((void**)&env, NULL);
    }
    delete[] options;
    if (rc != JNI_OK) {
        if (rc == JNI_EVERSION)
            return "JNI_EVERSION";
        else if (rc == JNI_ENOMEM)
            return "JNI_ENOMEM";
        else if (rc == JNI_EINVAL)
            return "JNI_EINVAL";
        else if (rc == JNI_EEXIST)
            return "JNI_EEXIST";
        else
            return "JNI_FATALERROR";
    }
    return "JNI_CREATED";

This question was asked before in this thread and was marked as "This question already has answers here", but unfortunately the answer given there doesn't apply to JNI, and it is a general solution for DLL files.


Solution

  • Using the suggestion @Botje gave (thank you!), I tried yet again using LoadLibraryA, and this time I managed to make it work:

    #include <string>
    #include <iostream>
    #include <chrono>
    #include <thread>
    #include <jni.h>
    #include <Windows.h>
    
    JavaVM* jvm = nullptr;
    
    std::string createVM(std::string location) {
        std::string jvmLocation = location;
        jvmLocation.append("ThirdParty\\Eclipse Adoptium\\jre-17.0.7.7-hotspot\\bin\\servers\\jvm.dll");
        HMODULE hJVMDLL = LoadLibraryA(jvmLocation.c_str());
        typedef jint(JNICALL* fpCJV)(JavaVM**, void**, void*);
        if (hJVMDLL != NULL) {
            fpCJV JNI_CreateJavaVM = (fpCJV)::GetProcAddress(hJVMDLL, "JNI_CreateJavaVM");
            location.insert(0, "-Djava.class.path=");
            location.append("Data\\Java");
            JavaVMInitArgs vm_args;
            JavaVMOption* options = new JavaVMOption[1];
            options[0].optionString = &location[0];
            vm_args.version = JNI_VERSION_10;
            vm_args.nOptions = 1;
            vm_args.options = options;
            vm_args.ignoreUnrecognized = false;
            JNIEnv* env = nullptr;
            jint rc = JNI_OK;
            if (jvm == nullptr) {
                rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
            }
            else {
                rc = jvm->AttachCurrentThread((void**)&env, NULL);
            }
            delete[] options;
            if (rc != JNI_OK) {
                if (rc == JNI_EVERSION)
                    return "JNI_EVERSION";
                else if (rc == JNI_ENOMEM)
                    return "JNI_ENOMEM";
                else if (rc == JNI_EINVAL)
                    return "JNI_EINVAL";
                else if (rc == JNI_EEXIST)
                    return "JNI_EEXIST";
                else
                    return "JNI_FATALERROR";
            }
            return "JNI_CREATED";
        }
        else {
            return "ERROR_LOADING_DLL";
        }
    }
    
    //This is just a test for a function I have written in Java, which doesn't take any parameters, and returns a String
    std::string createIdentification() {
        JNIEnv* env;
        jvm->AttachCurrentThread((void**)&env, NULL);
        jclass jClass = env->FindClass("JavaMethods");
    
        if (jClass == nullptr) {
            return "ClassNotFound cI";
        }
        else {
            jmethodID methodID = env->GetStaticMethodID(jClass, "createIdentification", "()Ljava/lang/String;");
            if (methodID == nullptr) {
                return "MethodNotFound cI";
            }
            else {
                jboolean isCopy;
                jstring jResult = (jstring)env->CallStaticObjectMethod(jClass, methodID);
                const char* string = env->GetStringUTFChars(jResult, &isCopy);
                std::string result = string;
                env->ReleaseStringUTFChars(jResult, string);
    
                return result;
            }
        }
    }
    
    int main() {
        std::cout << createVM("D:\\Program Files\\MyApp\\").c_str() << std::endl;
        if (jvm != NULL) {
            std::cout << createIdentification().c_str();
        }
    
        std::this_thread::sleep_for(std::chrono::milliseconds(2000));
        return 0;
    }