Using JNA to get/set application identifier
Asked Answered
G

3

21

Following up on my previous question concerning the Windows 7 taskbar, I would like to diagnose why Windows isn't acknowledging that my application is independent of javaw.exe. I presently have the following JNA code to obtain the AppUserModelID:

public class AppIdTest {

    public static void main(String[] args) {
        NativeLibrary lib;
        try {
            lib = NativeLibrary.getInstance("shell32");
        } catch (Error e) {
            System.err.println("Could not load Shell32 library.");
            return;
        }
        Object[] functionArgs = new Object[1];
        String functionName = null;
        Function function;
        try {
            functionArgs[0] = new String("Vendor.MyJavaApplication")
                    .getBytes("UTF-16");
            functionName = "GetCurrentProcessExplicitAppUserModelID";
            function = lib.getFunction(functionName);
            // Output the current AppId
            System.out.println("1: " + function.getString(0));
            functionName = "SetCurrentProcessExplicitAppUserModelID";
            function = lib.getFunction(functionName);
            // Set the new AppId
            int ret = function.invokeInt(functionArgs);
            if (ret != 0) {
                Logger.out.error(function.getName() + " returned error code "
                        + ret + ".");
            }
            functionName = "GetCurrentProcessExplicitAppUserModelID";
            function = lib.getFunction(functionName);
            // Output the current AppId
            System.out.println("2: " + function.getString(0));
            // Output the current AppID, converted from UTF-16
            System.out.println("3: "
                    + new String(function.getByteArray(0, 255), "UTF-16"));
        } catch (UnsupportedEncodingException e) {
            System.err.println("System does not support UTF-16 encoding.");
        } catch (UnsatisfiedLinkError e) {
            System.err.println(functionName + " was not found in "
                    + lib.getFile().getName() + ".");
        }
    }
}

The output of the application is seemingly gibberish:

1: ‹ÿU‹ìƒìL¡¬Ÿv3ʼnEüSV‹uƒ&
2: ‹ÿU‹ìƒìL¡¬Ÿv3ʼnEüSV‹uƒ&
3: ????????????????P???????????

Being aware of the fact that the output may be UTF-16, in (3) I attempted to convert a byte array from UTF-16. In all honesty I don't know if my approach here is right as (a) I don't know the size of a PWSTR and (b) I don't know if GetCurrentProcessExplicitAppUserModelID is indeed returning a byte array or string.

I'm aware that JSmooth will run the GUI process in a wrapper which simulates this effect. Launch4j claims to do the same, but doesn't appear to work. I am looking to have the AppUserModelID set regardless of the Java wrapper.

What is going wrong here?

Gwenette answered 15/12, 2009 at 14:13 Comment(2)
What's the output if you make the format UTF-8?Garibaldi
??U???L???v3?E?SV?u?&... (it's actually longer, because the byte array is length 255). If I return the byte values, it reveals negative values: -117 -1 85 -117 -20 -125 -20 76 -95 -84 -97 12 118 51 -59 -119 69 -4 83 86 -117 117 8 -125 38 .... It gives me the impression that the function returning something else than a string.Gwenette
S
20

I didn't see your question before otherwise I would have given a try even without a bounty.

Here is what I came up with. Please note, as stated in the code itself, I didn't implement proper memory clean up with the CoTaskMemFree function (from Ole32.dll). So I suggest you take only the implementation for SetCurrentProcessExplicitAppUserModelID()

package com.stackoverflow.AppIdTest;

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.ptr.PointerByReference;

public class AppIdTest
{

  public static void main(String[] args) throws Exception
  {
    setCurrentProcessExplicitAppUserModelID(AppIdTest.class.getName());

    System.out.println(getCurrentProcessExplicitAppUserModelID());
  }

  // DO NOT DO THIS, IT'S JUST FOR TESTING PURPOSE AS I'M NOT FREEING THE MEMORY
  // AS REQUESTED BY THE DOCUMENTATION:
  //
  // http://msdn.microsoft.com/en-us/library/dd378419%28VS.85%29.aspx
  //
  // "The caller is responsible for freeing this string with CoTaskMemFree when
  // it is no longer needed"
  public static String getCurrentProcessExplicitAppUserModelID()
  {
    final PointerByReference r = new PointerByReference();

    if (GetCurrentProcessExplicitAppUserModelID(r).longValue() == 0)
    {
      final Pointer p = r.getValue();


      return p.getString(0, true); // here we leak native memory by lazyness
    }      
    return "N/A";
  }

  public static void setCurrentProcessExplicitAppUserModelID(final String appID)
  {
    if (SetCurrentProcessExplicitAppUserModelID(new WString(appID)).longValue() != 0)
      throw new RuntimeException("unable to set current process explicit AppUserModelID to: " + appID);
  }

  private static native NativeLong GetCurrentProcessExplicitAppUserModelID(PointerByReference appID);
  private static native NativeLong SetCurrentProcessExplicitAppUserModelID(WString appID);


  static
  {
    Native.register("shell32");
  }
}

Does it work for you?

At least here it correctly prints back:

com.stackoverflow.AppIdTest.AppIdTest

Southbound answered 18/12, 2009 at 15:21 Comment(12)
Brilliant! It works like a charm! I love it how without any further modifications my application name and icon appear for the pinned application. Thank you very much!Gwenette
I'm glad I helped. I never used JNA before that's why I was curious about your question. However, I didn't have access to a Windows 7 computer until todaySouthbound
and again, don't read the value back in your real application, otherwise you need to enhance the implementation to correctly clean up the memory after having converted the appID back to a Java StringSouthbound
Does the memory linger until the application closes? If so, then it's not something I really need to worry about as the function will only be called once per run. Nevertheless, the current implementation only calls setCurrentProcessExplicitAppUserModelID().Gwenette
The worry is with getCurrentProcessExplicitAppUserModelID() and yes I would say when the application closes it's reclaimed anyway. The point of implementing getCurrentProcessExplicitAppUserModelID() was getting feedback from Windows 7 before answering you and also being curious about jna and passing a WString by referenceSouthbound
It's elegant and simple; I'll bear in mind that the memory needs clearing when using functions that take pointers as arguments.Gwenette
not necessarily the case when using functions that take pointers as arguments -- but for this specific one it is, as stated in the function contractSouthbound
I'm using an application wrapped by Launch4j which has the same problem. Did you put the above code in your application during startup, or did you make a patch for Launch4j?Nijinsky
@GregoryPakosz I've successfully reproduced the results of this sample code, however I'm not sure what I should be seeing. I've created a simple application, which executes the sample code and shows a JFrame. An icon is then shown on the taskbar, but right-clicking on it doesn't give me the option to "pin" the icon to the taskbar (I only get a "close blabla" option). What am I missing here? Or this does not tries to answer the OP's related question at all?Gratulant
I'm also currently struggling with this problem. Can't get it working. Code compiles and doesn't throw any exceptions. The System.out correctly prints the app id which was set. But still I can't pin the application to the taskbar. I tried different launch4j settings (Wrapping the JAR and not wrapping the JAR, toggling the "Custom process name" checkbox) nothing helps. Maybe this solution simply no longer works with current Java versions? Or maybe something was changed in Windows 7?Conjugated
@KohányiRóbert @Conjugated The behavior you seem to be experiencing is a mode called "IsHostApp". Perhaps javaw is registered as IsHostApp on your system, or possibly Swing (since that is what you appear to be using) sets itself up that way to prevent pinning problems. You might find a bit more information in this related question.Gwenette
@PaulLammertsma - correct. Both java and javaw are registered as Host processes. Pinning them would create a reference to them - without the classpath and main-class arguments. Funny enough, javaws is not registered as Host, so one can observe the incorrect behaviour on a JNLP App. The bottom line: for Java the AppUserModelID should be defined on top level windows (instances of awt.Window for which a HWND pointer exist). The AppId alone won't suffice, the RelaunchCommand has to be specified as well.Rancid
R
3

If you just need to set the AppUserModelId then the above JNA code is enough. However if you want to take advantage of the new Windows 7 features in you Java application then check out J7Goodies a Java library providing Windows 7 taskbar extensions.


EDIT: more info from J7Goodies Programmer's Guide

4.2. Setting AppUserModelID

In order to use any of the Windows 7 features an application must explicitly set its process identifier – Application User Model ID (AppUserModelID). It can have no more than 128 characters and cannot contain spaces. Each section should be camel-cased, for example:

 CompanyName.ProductName.SubProduct.VersionInformation

This identifier must be set before any GUI (window) is shown. You set it by calling:

// Remember to set AppUserModelID before creating any UI
AppUserModelId.setCurrentProcessId("StrixCode.J7Goodies.Appname");

4.3. Setting Window Properties

A Java application cannot be pinned to the Windows 7 taskbar unless its window properties are defined. The properties consist of four fields:

  • AppUserModelID – the same as passed to AppUserModelId.setCurrentProcessId(String)
  • RelaunchDisplayName – application’s name
  • RelaunchCommand – the full command used to launch the application. In case of a Java program it will be: <path to javaw.exe> -jar <path to application jar>
  • RelaunchIcon – path to application’s icon

Important: RelaunchCommand and RelaunchDisplayName must always be set together. To set these properties use the straightforward WindowProperties class.

WindowProperties props = new WindowProperties(myFrame);
props.setRelaunchCommand("<full path to javaw.exe –arguments>");
props.setRelaunchDisplayName("My Java Application");
props.setRelaunchIcon("<full path to an .ico or .exe file>");
props.setAppUserModelID("StrixCode.J7Goodies.Appname");
props.save();
Rattray answered 2/12, 2010 at 14:57 Comment(0)
F
3

Here's a more simple example on how to call SetCurrentProcessExplicitAppUserModelID via JNA:

import com.sun.jna.*;
import com.sun.jna.win32.*;

interface Shell32 extends StdCallLibrary {

    Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32", Shell32.class, W32APIOptions.DEFAULT_OPTIONS);

    NativeLong SetCurrentProcessExplicitAppUserModelID(WString appID);

}
Fabria answered 4/8, 2016 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.