【发布时间】:2018-12-05 05:55:43
【问题描述】:
我已经编写了一个从 Windows Vista 到 Windows 10 的 Java 程序。
该程序在嵌入式 Java 运行时环境中运行,因此用户无需安装 Java 即可使其工作。 此项目的目标是下载用户体验 -> 运行单个安装程序 -> 运行程序并让它感觉原生。用户无需其他步骤。
为了实现这个目标,我需要用 C++ 编写一个直接调用 JVM(而不是通过调用 java.exe)的本机可执行文件,这样 windows takbar 功能固定工作正常——如果我使用像这样的可执行包装器launch4j 在任何时候将执行传递给 java.exe,然后固定不会按预期的方式工作。我自己的可执行文件需要在一段时间内保持正在运行的程序,以便固定工作正常。
我编写了一个本地启动器 C++ 程序,它使用 JNI 通过 jvm.dll 而不是 java.exe 调用 Java 虚拟机,它可以工作。它是一个 100 行的垫片(本文底部的代码)。它只导入windows.h 并使用以下功能:HINSTANCE、GetProcAddress、WinMain。它在运行时显式链接到嵌入式jvm.dll。
但是,如果我使用 Visual Studio 2017 编译此 shim,则生成的可执行文件取决于安装了 Visual C++ 2015 Redistributable 的用户。如果没有,程序会给出错误““无法启动,因为您的计算机缺少 VCRUNTIME140.dll”。
我尝试使用 /MT 标志编译此程序,并在 Visual Studio 的常规项目属性页面中选择“在静态库中使用 MFC”。它没有任何区别。无论如何,我都会遇到同样的错误。
尽管许多使用 Windows Vista / Windows 7 的用户已经安装了运行时,但有些用户可能没有,我对这个项目的优先考虑是从下载到安装到运行的流畅、无错误的用户体验。
在我的项目中捆绑额外的 dll 没有问题。如果这更有意义的话,我用 MinGW 或 Cygwin 编译没有问题。对于从 Vista 到 Windows 10 的任何 Windows 版本,我唯一想要的是有保证的 download -> install -> run 路径。
如何创建独立的可执行文件或可以与少量 dll 一起分发的可执行文件,以便在 Vista 和更新版本的 Windows 上自包含?
最低完整可验证示例: You can download a zip 或通过以下步骤手动完成所有操作:
第 1 步 - 初始化: 创建一个文件夹 josh-problem。
第 2 步 - 下载 JRE: 下载 Windows 64 bit zip of Java 11 并将其放入 josh-problem/jre 以便 josh-problem/jre 的内容是名为 lib、legal、@987654342 的 java 文件夹@、include、conf、bin等
第 3 步 - 设置 Java 程序: Createjosh-problem/src/net/joshuad/test/Main.java。制作它的内容:
package net.joshuad.test;
public class Main {
public static void main(String[] args) {
System.out.println ( "Hi from Java, launched via embedded jre, via native executable." );
}
}
第 4 步 - 创建清单 创建文件 josh-problem\MANIFEST.MF,其中包含单行内容:
Main-Class: net.joshuad.test.Main
第 5 步 - 编译并创建 main.jar:导航到 cmd.exe 中的文件夹 josh-problem。运行以下命令:
jre\bin\javac.exe -d bin src\net\joshuad\test\Main.javajre\bin\jar.exe cfm main.jar MANIFEST.MF -C bin .
第 6 步 - 确认 jar 工作:运行命令:
jre\bin\java.exe -jar main.jar
您应该会看到输出:“来自 Java,通过嵌入式 jre 启动,通过本机可执行文件。”
第 7 步 - 编译 Native Launcher 在 Visual Studio 2017 中创建一个项目,并为其提供一个包含本文最底部源代码的 cpp 文件。添加到您的包括 josh-problem\jre\include 和 josh-problem\jre\include\win32。
第 8 步 - 尝试运行 - 将输出可执行文件移动到 josh-problem。尝试在干净的 Windows 7 Service Pack 1 机器上运行可执行文件。它将给出错误:“程序无法启动,因为您的计算机中缺少 VCRUNTIME140D.dll。”。
第 9 步 - 通过 java 运行 - 在同一台干净的 Windows 7 计算机上通过调用 java.exe 运行程序,它可以工作。
jre\bin\java.exe -jar main.jar
第 10 步 - 安装 Visual C++ Redistributable 并再次尝试我们的 exe - 在干净的 Windows 7 机器上下载并安装 2015 Visual C++ Redistributable。再次尝试我们的可执行文件。它有效。
**如何编译此代码,使其不需要 Visual C++ Redistributable?我知道这是可能的,因为我们刚刚演示了 java.exe 正在做的事情。如何让我的程序做到这一点?*
下载我知道这个问题很拗口。为了您的方便,我已经完成了上面的工作,并把它放在in a zip to download,其中包括jar、java 源代码、C++ 源代码和生成的可执行文件。您必须使用上面的 jre 文件夹链接下载 jre;我不想创建一个巨大的拉链。
我还上传了我的visual studio project in a zip。
win-launcher.cpp
#include <jni.h>
#include <windows.h>
typedef UINT(CALLBACK* JVMDLLFunction)(JavaVM**, void**, JavaVMInitArgs*);
int main(int argc, char** argv) {
HINSTANCE jvmDLL = LoadLibrary(".\\jre\\bin\\server\\jvm.dll");
if (!jvmDLL) {
printf("failed to find jvm.dll at specified location, exiting.\n");
return 1;
}
JVMDLLFunction createJavaVMFunction = (JVMDLLFunction)GetProcAddress(jvmDLL, "JNI_CreateJavaVM");
if (!createJavaVMFunction) {
printf("Failed to get pointer to JNI_CreateJavaVM function from jvm.dll, exiting\n");
return 1;
}
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption* options = new JavaVMOption[1];
int index = 0;
options[index].optionString = (char *)"-Djava.class.path=./main.jar";
vm_args.version = JNI_VERSION_10;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
createJavaVMFunction(&jvm, (void**)&env, &vm_args);
delete options;
jmethodID main = NULL;
jclass cls = NULL;
cls = env->FindClass("net/joshuad/test/Main");
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
printf("Unable to find net.joshuad.hypnos.Main, exiting.\n");
return 0;
}
if (cls != NULL) {
main = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");
}
else {
printf("Unable to find main() in java\n");
return 0;
}
if (main != NULL) {
jclass classString = env->FindClass("java/lang/String");
jobjectArray argsToJava = env->NewObjectArray(argc - 1, classString, NULL);
for (int i = 1; i < argc; i++) {
printf("Converting: %s", argv[i]);
jstring arg = env->NewStringUTF(argv[i]);
env->SetObjectArrayElement(argsToJava, i - 1, arg);
}
env->CallStaticVoidMethod(cls, main, argsToJava);
}
else {
printf("main method not found");
}
jvm->DestroyJavaVM();
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
return main(__argc, __argv);
}
【问题讨论】:
-
你不能指望像 VLC 那样在如此巨大的依赖中啜饮而不流血。非常怀疑是否有可能构建许多 VLC 库。
-
依赖问题不能由 VLC 或 Java 引起。它肯定是由我们编译的 exe 引起的。当没有安装可再发行组件时,您可以使用 jre 中的 java.exe 手动运行该程序。我将包含上面的调用,以便您确认。
-
您可以使用dependency walker,或现代等效的"dependencies" 来验证是您的exe 本身还是其他库的问题
-
我编写了一个测试程序,它删除了除 jre 之外的所有其他库并演示了问题。立即将其编辑到主帖中。
-
X/Y 您真正的问题似乎是您希望进程与正确的任务栏按钮/图标相关联。单独的进程可以共享一个按钮:docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/…
标签: c++ visual-studio dll