将Java应用程序固定到Windows 7任务栏

35

我在Windows 7下使用Launch4j作为Java应用程序的包装器,它实质上是分叉了一个javaw.exe实例,以便解释Java代码。因此,当我尝试将我的应用程序固定到任务栏时,Windows会固定javaw.exe而不是我的应用程序。没有必要的命令行,我的应用程序就无法运行。

Result of pinning a Launch4j application to the taskbar

正如您所看到的,Windows也没有意识到Java是主机应用程序:该应用程序本身被描述为“Java(TM) Platform SE binary”。
我尝试更改注册表键HKEY_CLASSES_ROOT\Applications\javaw.exe来添加值IsHostApp。这通过禁用我的应用程序的固定行为来改变其行为;显然不是我想要的。

Result of specifying javaw.exe as a host application

阅读关于Windows如何解释单个应用程序实例(以及在这个问题中讨论的现象),我对将应用程序用户模型ID(AppUserModelID)嵌入我的Java应用程序产生了兴趣。

我相信我可以通过向Windows传递一个唯一的AppUserModelID来解决这个问题。有一个shell32方法可以做到这一点,SetCurrentProcessExplicitAppUserModelID。遵循Gregory Pakosz的建议,我尝试实现它,希望将我的应用程序识别为javaw.exe的单独实例:

NativeLibrary lib;
try {
    lib = NativeLibrary.getInstance("shell32");
} catch (Error e) {
    Logger.out.error("Could not load Shell32 library.");
    return;
}
Object[] args = { "Vendor.MyJavaApplication" };
String functionName = "SetCurrentProcessExplicitAppUserModelID";
try {
    Function function = lib.getFunction(functionName);
    int ret = function.invokeInt(args);
    if (ret != 0) {
        Logger.out.error(function.getName() + " returned error code "
                + ret + ".");
    }
} catch (UnsatisfiedLinkError e) {
    Logger.out.error(functionName + " was not found in "
            + lib.getFile().getName() + ".");
    // Function not supported
}

这似乎没有任何效果,但该函数返回而没有错误。诊断原因对我来说有点神秘。有什么建议吗?

工作实现

最终起作用的实现是关于如何使用 JNA 传递 AppID后续问题的答案

我已经将奖励授予 Gregory Pakosz 的出色 JNI 答案,这使我走上了正确的轨道。

供参考,我相信使用此技术打开了在 Java 应用程序中使用 本文中讨论的任何 API 的可能性。


对于JSmooth,如果需要,可以使用GIMP将.ico文件转换为.png文件。 - Martijn Courteaux
尝试使用String.getBytes("UTF-16");方法。 - Gregory Pakosz
有人为launch4j打开了一个原生支持的特性请求:https://sourceforge.net/tracker/?func=detail&aid=3483933&group_id=95944&atid=613103 帮助在下一个3.1发布版本中实现此特性! - ToFi
你问题中链接的图片似乎已经过期了。你有保存副本可以重新上传和编辑吗? - FThompson
不好意思,我没有。他们描述了两个问题:(1)右键单击应用程序并将其固定会创建一个非可执行的链接到javaw.exe,而不是我的应用程序;(2)在看到注册表键后,右键单击禁止固定。 - Paul Lammertsma
我通过Wayback Machine找回了那些丢失的图片! - Paul Lammertsma
7个回答

22

我不拥有Windows 7,但这里有一些可以帮助你入门的内容:

在Java方面:

package com.stackoverflow.homework;

public class MyApplication
{
  static native boolean setAppUserModelID();

  static
  {
    System.loadLibrary("MyApplicationJNI");
    setAppUserModelID();
  }
}

在本地端,`MyApplicationJNI.dll`库的源代码如下:

JNIEXPORT jboolean JNICALL Java_com_stackoverflow_homework_MyApplication_setAppUserModelID(JNIEnv* env)
{
  LPCWSTR id = L"com.stackoverflow.homework.MyApplication";
  HRESULT hr = SetCurrentProcessExplicitAppUserModelID(id);

  return hr == S_OK;
}

你的问题明确要求使用JNI解决方案。然而,由于你的应用程序不需要任何其他本地方法,jna是另一种解决方案,它将使你免于仅仅为了转发到Windows API而编写本地代码。如果你决定使用JNA,请注意 SetCurrentProcessExplicitAppUserModelID() 需要一个UTF-16字符串。

当它在你的沙盒中工作时,下一步是在你的应用程序中添加操作系统检测,因为显然只有在Windows 7中才有 SetCurrentProcessExplicitAppUserModelID()

  • 你可以通过检查 System.getProperty("os.name"); 返回值是否为 "Windows 7" 来从Java端完成这个操作。
  • 如果你使用我给出的小JNI片段构建,你可以通过使用 LoadLibrary 动态加载 shell32.dll 库,然后使用 GetProcAddress 获取 SetCurrentProcessExplicitAppUserModelID 函数指针。如果 GetProcAddress 返回 NULL,则表示该符号不存在于 shell32 中,因此它不是Windows 7。

编辑:JNA解决方案

参考:


这看起来非常不错!我选择使用JNI并没有特别的原因。我有一些使用JNI的Mac特定方法,但我肯定会考虑你的建议。 - Paul Lammertsma
好的。我不知道你是否熟悉JNI。 - Gregory Pakosz
回顾过去,我只能在Mac上编译专门为Mac编写的JNI方法。我一直在研究JNA,它看起来更加直观。虽然我还没有实际尝试过,但由于赏金即将到期,我打算提前颁发奖励点数。 - Paul Lammertsma
我已经使用我的JNA实现编辑了我的问题。这似乎没有任何效果;这与UTF-16有关吗?如果是这样,我该如何传递一个以这种编码方式表示的字符串? - Paul Lammertsma
我的之前关于字符串编码的问题在这个后续问题中得到了解答:https://dev59.com/aHI-5IYBdhLWcg3wVWzv。请参考。 - Paul Lammertsma
使用 System.getProperty("os.name") 可能不太具有未来性。在我的 Windows 7 上,使用 System.getProperty("os.version") 返回 "6.1",而在 Fedora 17 上返回 "3.8.4-102.fc17.x86_64",因此您可以检查 System.getProperty("os.name") 是否包含 "Windows",然后将 os.version 属性转换为 double 类型以与 6.1 进行比较。 - iboisver

5

有一个Java库可以为Java提供新的Windows 7功能,它叫做J7Goodies,由Strix Code开发。使用它的应用程序可以正确固定到Windows 7任务栏上。您还可以创建自己的跳转列表等。


1
我不是很喜欢使用专有库。尽管如此,它似乎包含了你可能想要在Windows 7中集成的所有Java内容。看起来不错! - Paul Lammertsma

4
我已经使用JNA实现了对SetCurrentProcessExplicitAppUserModelID方法的访问,并且在按照MSDN文档建议的方式使用时效果非常好。我从未像你代码片段中展示的那样使用过JNA API。我的实现遵循典型的JNA用法

首先,是Shell32接口定义:

interface Shell32 extends StdCallLibrary {

    int SetCurrentProcessExplicitAppUserModelID( WString appID );

}

然后使用JNA加载Shell32并调用函数:
final Map<String, Object> WIN32API_OPTIONS = new HashMap<String, Object>() {
    {
       put(Library.OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
       put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
    }
};
Shell32 shell32 = (Shell32) Native.loadLibrary("shell32", Shell32.class,
           WIN32API_OPTIONS);
WString wAppId = new WString( "Vendor.MyJavaApplication" );
shell32.SetCurrentProcessExplicitAppUserModelID( wAppId );

你提到的上篇文章中许多API都使用了Windows COM,在JNA中直接使用会比较困难。我曾经尝试创建自定义DLL来调用这些API(例如使用SHGetPropertyStoreForWindow为子模块窗口设置不同的应用程序ID),然后在运行时使用JNA进行访问,取得了一些成功。


这非常符合Gregory Pakosz对我的后续问题的回答(https://dev59.com/aHI-5IYBdhLWcg3wVWzv)。我非常好奇你所说的“自定义DLL”。这是用C#制作的东西,我猜测?你能给更多信息吗? - Paul Lammertsma
我选择使用C语言制作自定义DLL,并使用JNA轻松调用。目前,我只在C DLL中实现了一个SetWindowAppId函数,该函数调用SHGetPropertyStoreForWindow并能够操作结果的COM接口以设置窗口的应用程序ID(与各种Microsoft示例完全相同)。我还计划实现一些用于操作我的应用程序Jump Lists的函数,但我还没有做到。非常方便地,JNA在其Native类中提供了API,以检索Java窗口的句柄/指针,这使得这种方法成为可能。 - The_Fire
@The_Fire:你能提供SHGetPropertyStoreForWindow代码吗? - Rekin
@The_Fire:好的,我自己实现了 - 没有那么难。 :) - Rekin

4

尝试使用JSmooth。我总是使用这个。在JSmooth中,有一个选项在窗口包装器下的骨架中,称为

在exe进程中启动java应用程序

请参见此图像。

JSmooth
(来源:andrels.com)

还可以传递命令行参数。
我认为这可能是您的解决方案。

Martijn


我以前听说过这个包装器。我试了一下,但它经常挂起,无法执行任何操作。每当我选择“Skeleton”选项卡时,就无法选择其他选项卡。如果我最后访问该选项卡并按编译键,则仍然停留在“加载图标...”上。我正在使用Windows 7,但使用Vista兼容模式时情况相同。在以前的Windows版本下进行兼容性设置后根本无法启动。有什么想法吗? - Paul Lammertsma
经过一些摆弄,我编译出了一个版本。选择图标似乎会导致我之前评论中描述的问题。如果我在Skeleton选项卡下选择在exe进程中启动应用程序,则固定工作正常。这并没有真正回答我的嵌入AppID的问题,但它规避了问题。谢谢! - Paul Lammertsma
我只能加载非ICO图像作为图标。无论如何,在固定应用程序时,RelaunchIconResource(请参见http://msdn.microsoft.com/en-us/library/dd391573(VS.85).aspx)显示为Windows默认应用程序图标。使用资源编辑器,我可以看到该图标包含在EXE文件的A2图标组中。替换它会反映在固定的应用程序中,但这几乎不是一个解决方案。 - Paul Lammertsma
关于使用单个PNG而不是ICO的建议:对于我的项目来说,这不是一个选项,因为操作系统依赖的分发确实需要不同分辨率的应用程序图标。我感谢这个答案,但它并没有解决手头的问题,而切换到另一个包装器也不是可能的选择。 - Paul Lammertsma

3

SetCurrentProcessExplicitAppUserModelID(或SetAppID())实际上可以完成您想要做的事情。但是,修改安装程序以在快捷方式上设置AppUserModel.ID属性可能更容易 - 引用上面提到的Application User Model ID文档:

在应用程序的快捷方式文件中的System.AppUserModel.ID属性。一个快捷方式(作为IShellLink、CLSID_ShellLink或.lnk文件)通过IPropertyStore和其他在Shell中使用的属性设置机制支持属性。这使任务栏能够识别正确的快捷方式以固定,并确保属于该进程的窗口与该任务栏按钮适当关联。注意:System.AppUserModel.ID属性应该在创建快捷方式时应用于快捷方式。当使用Microsoft Windows Installer(MSI)安装应用程序时,MsiShortcutProperty表允许在安装期间创建快捷方式时应用AppUserModelID。

1
这是一个很好的建议,谢谢。我实际上已经研究过这个问题(我使用Inno Setup,在快捷方式中声称包含System.AppUserModel.ID)。经过一些实验,我无法重现结果。此外,即使这样做了,用户手动创建快捷方式时仍会感到困惑。我更愿意将AppUserModelID封装到我的Java应用程序中。通过JNI是否可能实现? - Paul Lammertsma

1

0

我没有使用任何ID设置即可解决我的问题。如果您正在使用Launch4J,那么有一个选项...

您可以将标题更改为JNI Gui,然后将其与JRE一起包装在jar中。 好处是它现在在进程中运行.exe而不是使用您的jar运行javaw.exe。 它可能在幕后做到了(不确定)。 此外,我还注意到它需要约40-50%的CPU资源,这更好!

并且固定工作正常,所有窗口功能都已启用。

我希望它对某人有所帮助,因为我花了近2天的时间来尝试解决我的无框javafx应用程序的问题。


如果您需要64位版本的javaw.exe,则不可能选择此选项。有一个名为Launch5J的项目,可能适用于64位版本,但对我来说没有起作用。 - Enwired

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接