Blocco di un’applicazione Java nella barra delle applicazioni di Windows 7

Io uso Launch4j come wrapper per la mia applicazione Java sotto Windows 7, che, a mio avviso, in sostanza forchetta un’istanza di javaw.exe che a sua volta interpreta il codice Java. Di conseguenza, quando si tenta di bloccare la mia applicazione sulla barra delle applicazioni, Windows invece pin javaw.exe . Senza la riga di comando richiesta, la mia applicazione non verrà quindi eseguita.

Risultato del blocco di un'applicazione Launch4j sulla barra delle applicazioni

Come puoi vedere, anche Windows non si rende conto che Java è l’applicazione host: l’applicazione stessa è descritta come “piattaforma binaria Java (TM) SE”.

Ho provato a modificare la chiave di registro HKEY_CLASSES_ROOT\Applications\javaw.exe per aggiungere il valore IsHostApp . Ciò altera il comportamento disabilitando del tutto la mia applicazione; chiaramente non quello che voglio.

Risultato della specifica di javaw.exe come applicazione host

    Dopo aver letto su come Windows interpreta le istanze di una singola applicazione (e un fenomeno discusso in questa domanda ), mi sono interessato a incorporare un ID modello utente dell’applicazione (AppUserModelID) nella mia applicazione Java.

    Credo di poter risolvere questo problema passando un unico AppUserModelID a Windows. Esiste un metodo shell32 per questo, SetCurrentProcessExplicitAppUserModelID . Seguendo il suggerimento di Gregory Pakosz, l’ho implementato nel tentativo di far riconoscere la mia applicazione come istanza separata di 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 } 

    Questo sembra non avere alcun effetto, ma la funzione ritorna senza errori. La diagnosi del perché è qualcosa di un mistero per me. Eventuali suggerimenti?

    Attuazione operativa

    L’implementazione finale che ha funzionato è la risposta alla mia domanda di follow-up su come passare l’ AppID usando JNA.

    Ho assegnato il premio alla brillante risposta di Gregory Pakosz per JNI che mi ha messo sulla strada giusta.

    Per riferimento, credo che l’uso di questa tecnica apra la possibilità di utilizzare una qualsiasi delle API discusse in questo articolo in un’applicazione Java.

    Non ho Windows 7 ma qui è qualcosa che potrebbe farti iniziare:

    Sul lato Java:

     package com.stackoverflow.homework; public class MyApplication { static native boolean setAppUserModelID(); static { System.loadLibrary("MyApplicationJNI"); setAppUserModelID(); } } 

    E sul lato nativo, nel codice sorgente della libreria `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; } 

    La tua domanda ha chiesto esplicitamente una soluzione JNI. Tuttavia, dal momento che la tua applicazione non ha bisogno di nessun altro metodo nativo, jna è un’altra soluzione che ti salverà dallo scrivere codice nativo solo per il piacere di inoltrare l’API di Windows. Se decidi di utilizzare jna, presta attenzione al fatto che SetCurrentProcessExplicitAppUserModelID() prevede una stringa UTF-16.

    Quando funziona nella sandbox, il passaggio successivo consiste nell’aggiungere il rilevamento del sistema operativo nell’applicazione in quanto SetCurrentProcessExplicitAppUserModelID() è ovviamente disponibile solo in Windows 7:

    • puoi farlo dal lato Java verificando che System.getProperty("os.name"); restituisce "Windows 7" .
    • se si crea dal snippet JNI piccolo che ho fornito, è ansible migliorarlo caricando in modo dinamico la libreria shell32.dll utilizzando LoadLibrary quindi richiamando il puntatore della funzione SetCurrentProcessExplicitAppUserModelID utilizzando GetProcAddress . Se GetProcAddress restituisce NULL , significa che il simbolo non è presente in shell32 quindi non è Windows 7.

    EDIT: soluzione JNA .

    Riferimenti:

    • Il libro JNI per altri esempi JNI
    • Java Native Access (JNA)

    Esiste una libreria Java che fornisce le nuove funzionalità di Windows 7 per Java. Si chiama J7Goodies by Strix Code . Le applicazioni che lo utilizzano possono essere correttamente aggiunti alla barra delle applicazioni di Windows 7. Puoi anche creare le tue liste di salto, ecc.

    Prova ad usare JSmooth . Io uso sempre questo. In JSmooth è disponibile un’opzione in Skeleton di Windowed Wrapper

    Lauch java app in elaborazione exe

    Vedi su questa immagine.

    JSmooth

    Anche gli argomenti della riga di comando possono essere passati.
    Penso che questa possa essere una soluzione per te.

    Martijn

    Ho implementato l’accesso al metodo SetCurrentProcessExplicitAppUserModelID usando JNA e funziona abbastanza bene se usato come suggerisce la documentazione MSDN. Non ho mai usato l’API JNA nel modo in cui hai nel tuo snippet di codice. La mia implementazione segue invece l’ utilizzo tipico di JNA .

    Prima la definizione dell’interfaccia Shell32:

     interface Shell32 extends StdCallLibrary { int SetCurrentProcessExplicitAppUserModelID( WString appID ); } 

    Quindi utilizzare JNA per caricare Shell32 e chiamare la funzione:

     final Map WIN32API_OPTIONS = new HashMap() { { 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 ); 

    Molte delle API dell’ultimo articolo che hai citato fanno uso di Windows COM, che è abbastanza difficile da usare direttamente con JNA. Ho avuto qualche successo nella creazione di una DLL personalizzata per chiamare queste API (ad esempio usando SHGetPropertyStoreForWindow per impostare un ID app diverso per una finestra del submodule) che poi uso JNA per accedere al runtime.

    SetCurrentProcessExplicitAppUserModelID (o SetAppID ()) farebbe in effetti ciò che stai cercando di fare. Tuttavia, potrebbe essere più semplice modificare il tuo programma di installazione per impostare la proprietà AppUserModel.ID sul tuo collegamento – citando dal documento ID modello utente dell’applicazione sopra menzionato:

    Nella proprietà System.AppUserModel.ID del file di scelta rapida dell’applicazione. Un collegamento (come IShellLink, CLSID_ShellLink o un file .lnk) supporta le proprietà tramite IPropertyStore e altri meccanismi di impostazione delle proprietà utilizzati in Shell. Ciò consente alla barra delle applicazioni di identificare il collegamento appropriato da bloccare e assicura che le windows appartenenti al processo siano associate in modo appropriato a quel pulsante della barra delle applicazioni. Nota: la proprietà System.AppUserModel.ID deve essere applicata a un collegamento quando viene creato tale collegamento. Quando si utilizza Microsoft Windows Installer (MSI) per installare l’applicazione, la tabella MsiShortcutProperty consente a AppUserModelID di essere applicato al collegamento quando viene creato durante l’installazione.

    L’ultima libreria jna-platform ora include i collegamenti JNA per SetCurrentProcessExplicitAppUserModelID :

    https://github.com/java-native-access/jna/pull/680

    Ho risolto il mio senza impostazioni ID. C’è un’opzione in Launch4J se la stai usando e dici di farlo allora …

    Puoi cambiare l’intestazione in JNI Gui e poi avvolgerla intorno al jar con JRE. La cosa buona è che ora esegue .exe nel processo invece di eseguire javaw.exe con il tuo jar. Probabilmente lo fa sotto il cofano (non è sicuro). Inoltre ho notato anche che ci vuole circa il 40-50% in meno di risorse CPU che è ancora meglio!

    E il pinning funziona bene e tutte le funzionalità della finestra sono abilitate.

    Spero che sia di aiuto a qualcuno perché ho passato quasi 2 giorni a cercare di risolvere il problema con la mia app javafx non decorata.