从 C++ 调用 JVM,然后执行 Java 文件(我认为这是最合理的方式,但这需要大量代码)
是的,这绝对是最合理的方式。使用 JNI 和调用 API,它甚至不需要那么多代码。
查找 jvm.dll
您可以尝试硬编码 Oracle JVM 的路径jvm.dll
或搜索jvm.dll
程序文件夹中调用的文件,但所有这些显然都非常麻烦。但是,显然有一个非常简单的解决方案:注册表。密钥HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment
包含一个REG_SZ
被调用的CurrentVersion
. 您可以读取此键的值(当前为1.7
)并打开具有该名称的子键(HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7
在本例中)。然后,该键将包含一个REG_SZ
被调用RuntimeLib
,它是您的jvm.dll
. 不用担心Program files
vs Program files (x86)
。HKLM\SOFTWARE\Wow6432Node
如果您是 64 位 Windows 上的 32 位进程并且该键包含到 32 位的路径,WOW64 将自动将您的注册表查询重定向到jvm.dll
。代码:
#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);
}
加载 jvm.dll 并获取 CreateJavaVM 函数
这部分非常简单,您只需使用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");
创建 JVM
现在您可以调用该函数:
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
恭喜!现在有一个 JVM 在您的进程中运行!您可能会在应用程序启动时启动 JVM。除非您 100%确定您只会从刚刚创建 JVM 的线程中调用 Java 代码,否则您可以丢弃该env
指针,但您必须保留该jvm
指针。
获取 JNI 环境(可选)
因此,现在您创建了 JVM,并且您的应用程序已启动并运行,然后有人单击该按钮。现在您要调用 Java 代码。如果你 100%确定你现在在上一步中创建 JVM 的线程上并且你仍然有env
指针,那么你可以跳过这个。否则,执行快速检查当前线程是否附加到 JVM,如果不是,则附加它:
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();
调用 Java 代码
现在你就在那里,你想调用那个 Java 代码,你甚至有env
指针。您想要最简单的解决方案,所以这就是您调用静态方法的方式:
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>);
您可以使用javap -s
(命令行工具)来确定方法的签名。<type>
可以是任何原始类型(它必须与 Java 方法的返回类型匹配)。参数可以是任何原始类型,只要它们与 Java 方法的参数匹配即可。
结束
你有它:在 Windows 上从 C++ 调用 Java 代码的最简单方法(实际上只有前两部分是特定于 Windows 的......)。哦,也是最有效的一种。螺丝数据库和文件。使用127.0.0.1
套接字将是一种选择,但它的效率要低得多,而且可能不会比这少。哇,这个答案比我预期的要长一点。希望它有所帮助。