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包很多,内存将更大
网友评论