美文网首页
C++启动JVM

C++启动JVM

作者: 文泰ChrisTwain | 来源:发表于2022-09-27 22:36 被阅读0次

    1.业务背景

    通过C++进程可直接加载JVM并运行Jar里面的Java代码,也就是通过C++的进程,比如一个exe加载一个JVM,JVM里面再通过JNI实现java代码与c++代码互调,可实现java程序直接运行在exe。当然有装JDK,jar包也能直接启动。
    官网API指导https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html

    2.环境准备

    (1)新建Java工程,生成Jar包
    (2)新建Visual Studio c++工程
    (3)下载windows JDK,拷贝\jdk1.8.0_202\jre\bin和\jdk1.8.0_202\jre\lib作为c++工程JVM的依赖,以及导入\jdk1.8.0_202\include中的jni.h和jni_md.h作为头文件

    3.代码实现

    (1)启动JVM

    // 以下两个变量初始化JVM时赋值,可保存成全局变量方便使用
    static JavaVM *jvm = NULL;  \\ 虚拟机在JNI层的代表,可用于获取线程对应的JNIEnv
    static JNIEnv *env = NULL;   \\ 这里表示主线程JNIEnv(通常调用JNI_CreateJavaVM创建JVM的线程被称为主线程),封装JNI方法的指针,不同线程彼此独立且每个线程只有一个JNIEnv
    
    bool initJvm()
    {
        std::string filePath = util::getCurrentPath().append("\\MyJars\\");
        std::vector<std::string> files;
        // 获取该路径下的所有Jar文件,若有多个jar使用分号;分隔
        util::getFiles(filePath, files);
        int size = files.size();
    
        std::string jarPath;
        for (int i = 0; i < size; i++)
        {
            if (i == size - 1) {
                jarPath.append(files[i].c_str());
            } else {
                jarPath.append(files[i].c_str()).append(";");
            }
        }
    
        // JDK下jvm动态库的路径
        std::string dllPath = "D:\\Java\\jdk1.8.0_202\\jre\\bin\\server\\jvm.dll";
    
        // java虚拟机启动时接收的参数,每个参数单独一项
        int nOptionCount = 4;
        JavaVMOption vmOption[4];
        // 设置JVM最大允许分配的堆内存,按需分配
        vmOption[0].optionString = (char *)"-Xmx256M";
        // 设置classpath
        std::string jar_path = std::string("-Djava.class.path=").append(jarPath);
        vmOption[1].optionString = (char *)jar_path.c_str();
        vmOption[2].optionString = (char *)"-Xtrace";
        vmOption[3].optionString = (char *)"-XX:+CreateMinidumpOnCrash";
        JavaVMInitArgs vmInitArgs;
        vmInitArgs.version = JNI_VERSION_1_8;
        vmInitArgs.options = vmOption;
        vmInitArgs.nOptions = nOptionCount;
    
        // 忽略无法识别jvm的情况
        vmInitArgs.ignoreUnrecognized = JNI_TRUE;
        HINSTANCE jvmDLL = LoadLibraryA(dllPath.c_str());
        JNICREATEPROC jvmProcAddress = (JNICREATEPROC)GetProcAddress(jvmDLL, "JNI_CreateJavaVM");
        if (!jvmProcAddress) {
            FreeLibrary(jvmDLL);
            return false;
        }
    
        // 初始化JVM
        jint jvmProc = (jvmProcAddress)(&jvm, (void **)&env, &vmInitArgs);
        if (jvmProc < 0 || jvm == NULL || env == NULL) {
            FreeLibrary(jvmDLL);
            return false;
        }
        return true;
    }
    

    Util.h工具类

    #include <stdio.h>
    #include <stdlib.h>
    #include <string>
    #include <shlwapi.h>
    #include <io.h>
    #include <vector> 
    #pragma comment(lib, "Shlwapi.lib")
    
    class util {
    public:
        static std::string getCurrentPath()
        {
            char filePath[MAX_PATH] = { 0 };
            GetModuleFileNameA(NULL, filePath, MAX_PATH);
            PathRemoveFileSpecA(filePath);
            return filePath;
        }
    
        static void getFiles(std::string path, std::vector<std::string>& files)
        {
            // 文件句柄
            intptr_t hFile = 0;
            // 文件信息
            struct _finddata_t fileinfo;
            std::string p;
            if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
            {
                do
                {
                    if ((fileinfo.attrib &  _A_SUBDIR))
                    {
                        if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
                            getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
                    }
                    else
                    {
                        files.push_back(p.assign(path).append("\\").append(fileinfo.name));
                    }
                } while (_findnext(hFile, &fileinfo) == 0);
                _findclose(hFile);
            }
        }
    };
    

    (2)加载Java启动类并启动Main方法

    void callMain()
    {
        const char* className = "com/test/HelloWorld";
        const char* methodName = "main";
        const char* methodSign = "([Ljava/lang/String;)V";
    
        JNIEnv *subEnv = NULL;
        if (Get_env(&subEnv)) {
            //LOGGER_DEBUG(L"get_env fail");
            return;
        }
        jclass subEnv = subEnv->FindClass(className);
        if (subEnv->ExceptionCheck() == JNI_TRUE || targetClass == NULL) {
            subEnv->ExceptionDescribe();
            subEnv->ExceptionClear();
            //LOGGER_DEBUG(L"find class fail");
            return;
        }
        jmethodID targetMethod = env->GetStaticMethodID(targetClass, methodName, methodSign);
        if (subEnv->ExceptionCheck() == JNI_TRUE || targetMethod == NULL) {
            subEnv->ExceptionDescribe();
            subEnv->ExceptionClear();
            //LOGGER_DEBUG(L"find method fail");
            return;
        }
    
        if (subEnv != NULL && targetClass != NULL && targetMethod != NULL) {
            subEnv->CallStaticVoidMethod(targetClass, targetMethod);
            Release_env(subEnv);
        }
    }
    

    获取线程对应的JNIEnv

    // 若为主线程可直接使用初始化JVM时保留的env变量,判断主线程可先保存主线程的线程号,当调用此方法是通过GetCurrentThreadId()判断是否当前为主线程
    bool Get_env(JNIEnv **env)
    {
        int status = -1;
        if (jvm != NULL) {
            status = jvm->GetEnv((void**)env, JNI_VERSION_1_8);
        } else {
            // LOGGER_DEBUG(L"jvm is NULL");
            return false;
        }
        if (status == JNI_EDETACHED) {
            status = jvm->AttachCurrentThread((void **)env, NULL);
        }
        if (status != JNI_OK) {
            // LOGGER_DEBUG(L"get_env FAILED");
            return false;
        }
        return true;
    }
    
    
    void Release_env(JNIEnv *env)
    {
        int status = jvm->GetEnv((void**)&env, JNI_VERSION_1_8);
        if (status == JNI_OK) {
            jvm->DetachCurrentThread();
        } else {
            // LOGGER_DEBUG(L"NEED NOT DETACH");
        }
    }
    

    释放资源

    MyClass::~MyClass()
    {
        jvm->DetachCurrentThread();
        jvm->DestroyJavaVM();
        FreeLibrary(jvmDLL);
    }
    

    4.内存测试

    exe加载完JVM后内存大概10M,若jar包很多,内存将更大

    参考:
    C/C++启动JVM
    浅谈JNIEnv和JavaVM

    相关文章

      网友评论

          本文标题:C++启动JVM

          本文链接:https://www.haomeiwen.com/subject/zhpfartx.html