Da Java a EXE: Come, perchè, quando conviene

“E’ possibile creare un file exe a partire da un’applicazione java?”,
“Ho bisogno di convertire un jar in exe”,
“E’ possibile creare degli eseguibili in Java?”

Queste domande e tutte le possibili loro variazioni sono probabilmente le più classiche FAQ nei più popolari forum e newsgroup Java.

Anche le risposte sono solitamente sempre le stesse:

  • “Non puoi”.
  • “Non dovresti farlo, perche uccideresti lo scopo stesso di java”.
  • “Si può fare, ma solo utilizzando software di terze parti”.

 

La verità è che esistono due diversi approcci alla creazione di eseguibili nativi in Java, indirizzati a differenti casistiche e problematiche. Comunque solitamente, sotto certe condizioni, alcuni problemi possono essere risolti senza creare un file EXE. Quindi la più corretta risposta ad una di queste domande dovrebbe essere una richiesta di maggiori informazioni, del tipo:
Qual è l’obiettivo che vuoi raggiungere con la conversione in EXE?

 

Semplificare la distribuzione di un’applicazione Java

Java compila le classi in bytecode proprietario (i file .class), che non è supportato direttamente dall’hardware dei PC.
Quindi un programma Java ha bisogno della Java Runtime Environment (JRE) per essere eseguito, che si occupa di interpretare le istruzioni in bytecode e compilarle in codice nativo “on the fly”.
Questo obbliga l’autore del programma a richiedere che la corretta versione della JRE sia installata nei computer degli utenti finali. Generalmente, però, non si può assumere che gli utenti finali sappiano cosa è una JRE, o come verificare la versione di quella posseduta , o come installarne una versione diversa, senza creare problemi alle altre applicazioni java o alle applet visualizzate spesso.
Se poi se anche si è sicuri che la versione corretta della JRE sia installata sulla macchina dell’utente finale, che su alcune classi di utenti è possibile, il comando da eseguire per richiamare l’applicazione è probabilmente questo:

Sì, si può scrivere un file batch con il comando, del tipo runme.bat, ma è molto più comodo distribuire la nostra applicazione, magari ad un collega o ad un insegnante o ad un amico, come un unico eseguibile da lanciare con un doppio click. O, magari, prevedere un processo di installazione (e di disinstallazione) nativo, che non crei conflitti con altre applicazioni.
Per questo non è una sorpresa sapere che il principale motivo del desiderio di convertire i programmi java in exe nasce dalla necessità di rendere più semplice e sicuro il deploy su utenti windows. La vera sorpresa, per i newbie, è che il JDK non offre una funzionalità del genere.

 

JAR eseguibili

E’ possibile rendere eseguibile una applicazione java attraverso un doppio click, racchiudendola in un jar eseguibile. Per farlo bisogna specificare nel file MANIFEST la classe principale della applicazione e ogni altro jar da includere :

Quindi utilizziamo il tool “jar” contenuto del SDK, specificando l’opzione m e il nome del file MANIFEST appena creato:

Ora per eseguire l’applicazione possiamo digitare

e verrà letto nel file manifest il nome della classe della quale invocare il metodo main. Allo stesso modo, se la JRE è ben installata, basterà cliccare due volte sul jar per avviare la applicazione.
Nota: Da J2SE 5.0, i file jar sono associati al launcher windows javaw, che non apre la finestra nera con la console. Quindi, se ne avete bisogno, dovrete far partire il jar da riga di comando o da un file batch.

 

Se la vostra applicazione è composta da più jar, potete utilizzare il tool One-JAR per riunirli in un unico jar.
Il maggior problema di questo metodo è la compatibilità con la JRE. La JRE di default potrebbe essere infatti troppo vecchia e non avere i necessari Java Optional Packages (conosciuti prima come Standard Extensions). Per esempio, se la vostra applicazione usa il package java.nio introdotto dalla versione 1.4.2, non funzionerà con la JRE versione 1.3.x.
Allo stesso modo, se usa JavaMail 1.3, e la JRE installata ha JavaMail 1.2 o non la possiede affatto, il jar non verrà eseguito.

Di seguito viene ricapitolato quanto detto per i JAR eseguibili.
PRO:

  • Non si usano tool di terze parti.
  • Multipiattaforma

 

CONTRO:

  • Le applicazioni non partono se non si ha una JRE correttamente installata.
  • Le applicazioni non funzionano se le APIs utilizzate non sono presenti nella JRE di default.
  • Bisogna informare gli utenti che i file JAR sono cliccakabili.

 

RISORSE:

 

TOOLS:

Java Web Start

Fortunatamente, per risolvere questi problemi di compatibilità, dal SDK 1.4 è presente un tool che facilita la vita allo sviluppatore. Java Web Start (JWS) e il suo protocollo Java Network Launch Protocol (JNLP) permette la distribuzione delle applicazioni java direttamente da un webserver standard.

 
L’utente finale può iniziare l’installazione semplicemente cliccando su un URL. Se Java Web Start non è presente nel sistema, verrà proposto il download e l’installazione del tool. Se necessario, JWS provvederà poi ad avviare download ed installazione della JRE corretta e di eventuali package aggiuntivi.

 
L’applicazione viene quindi lanciata e rimane pronta e nascosta nel sistema dell’utente finchè l’utente non la richieda nuovamente dall’URL associato, senza reinstallarla a meno che la versione della applicazione non sia obsoleta. In tal caso verrà aggiornata. Un’altra caratteristica interessante di Java Web Start è la capacità di far girare le vostre applicazioni in una SandBox, un container “sicuro”. Ma a differenza della SandBox delle Applet, l’applicazione può accedere alle risorse native, come filesystem, stampanti e alla memoria degli appunti attraverso le JNLP Api, dopo aver chiesto il permesso all’utente, ovviamente.
Java WebStart è disponibile per piattaforme Windows, Linux, Solaris e Mac OsX a partire dalla versione 10.1. Ci sono anche implementazioni di terze parti del protocollo JNLP ed alcune includono tool per la preparazione di un package adatto a JWS.
Ma passiamo ai difetti.
Innanzitutto il WebServer che ospita le applicazioni deve supportare il tipo mime associato a Java Web Start:

Alcuni provider potrebbero non permetterlo. Inoltre per utilizzare le funzionalità di versioning e il supporto agli updates, c’è bisogno di un webserver in grado di supportare servlets, cgi-bin scripts, etc.
Lato client, non ci dovrebbero essere problemi nel riconoscere il MIME type, per lo meno nei browser più popolari. In alcuni browser più di nicchia invece potrebbe essere necessario configurarli manualmente. Abilitare JNLP in un’applicazione dovrebbe richiedere modifiche minime e la creazione di un set di files jar.
Prima della J2SE 5.0, JWS aveva veramente poco da offrire, in termini di integrazione desktop, limitandosi a creare una icona sul desktop e/o un collegamento nel menu Start. In Windows, l’applicazione non apparirà nelle entry in “Installazione Applicazioni”, quindi l’utente dovrà lanciare Java Web Start per disinstallare l’applicazione.
Infine, si vede la necessità di una interfaccia utente più pulita per JWS. Allo stato attuale (J2SE 5.0) gli utenti si trovano a interagire con orribili finestre con messaggi incomprensibili. Riassumento, JWS può essere valida come opzione in ambienti controllati, come le intranet, ma non è pronto per il mercato consumer, per il quale sono preferibili altre soluzioni.

Java Launcher e Wrappers custom

Quando un programma Java è invocato utilizzando uno dei metodi discussi sopra (file batch, jar eseguibile, JWS/JNLP), il sistema operativo esegue un lancher java fornito dalla JRE. La versione windows della JRE ha launcher separati per le applicazioni command line e quelle grafiche, rispettivamente java.exe e javaw.exe. Come risultato, tutte le applicazioni java in esecuzione hanno lo stesso nome nel Task manager e la stessa icona nella taskbar o nel pannello “alt-tab”.

Se si hanno più applicazioni java in esecuzione, non si ha modo di distinguere fra le diverse istanze di java nel task manager. Praticamente, questi launcher sono solamente piccoli programmi nativi che caricano la JVM da una dll (shared library) ed eseguono le tue applicazioni usando le Invocations API. Queste api fanno parte delle Java Native Interdace (JNI), quindi standard, e molto semplici.

Questo rende relativamente semplice scrivere un nostro launcher con un nome e un’icona unici. Quello che bisogna fare è trovare una JRE disponibile sul sistema dell’utente (o includerne una nell’applicazione), caricare e inizializzare la JVM e far girare l’applicazione su di essa.

Tool per creare dei Setup

Se si ha solo la necessità di installare una copia privata della JRE insieme alla propria applicazione e creare un collegamento che la esegua su tale JRE, si può utilizzare qualsiasi generatore di setup. Comunque, utilizzare un tool basato su Java potrebbe avere dei benefici:

  • Rilevare al momento dell’installazione eventuali JRE presenti ed eventuale download
  • Generazione di un launcher nativo
  • Parametri JVM editabili dall’utente
  • Redirezione degli stderr e stdout per salvare log ed eccezioni
  • Registrazione delle applicazioni Java come servizi Windows o demoni Unix

 

Questa categoria è la più diversificata in termini di prezzo e funzionalità. I tool Windows centrici, come Advanced Installer for Java permettono di creare installativi MSI. I tool multipiattaforma possono generare installativi per più piattaforme: Windows, Linux, Mac OS. Install4j è uno di questi.
Ci sono anche Tool che permettono di creare installativi unici che girino su più piattaforme. Tali installativi sono essenzialmente jar eseguibili con logiche specifiche per gli ambienti nativi, riconosciuti a runtime. InstallAnywhere è forse il più conosciuto della categoria, ma se il suo prezzo è oltre il vostro budget, considerate il più economico JExpress o IZPack (Open Source).
Infine, c’è un tool che li racchiude tutti, InstallShield che può creare sia MSI per Windows che installativi closs-platform, anche per server o dispositivi mobili, per qualsiasi tipo di applicazione e per una moltitudine di sistemi operativi. Supporta il riconoscimento della JRE, l’installazione in bundling, i launcher nativi e così via..
Comunque, per le installazioni semplici e dirette, InstallShield è un peso inutile. Ricordate anche che InstallShield e InstallAnywhere mirano agli sviluppatori enterprise ed i loro prezzi sono conseguentemente elevati. Tutte queste soluzioni non risolvono il problema fondamentale menzionato nella prima sezione di questo articolo. Sia creando un jar eseguibile o un sofisticato installativo, l’applicazione continua ad essere distribuita come bytecode platform-indipendent.
Nei primi giorni di Java, l’unico modo di eseguire un programma java su di un comune PC, era di interpretare tale bytecode. Oggi, qualsiasi implementazione J2SE decente contiene un compilatore Just-in-Time (JIT) che compila i metodi eseguiti frequentemente in codice nativo. Così è abbastanza naturale fare un passo avanti e compilare tutta l’applicazione prima di distribuirla. Vediamo quali tool ci permettono di farlo.

Compilatori Ahead-Of-Time

I compilatori AOT sono conosciuti anche come “compilatori statici” o “compilatori di codice nativo”. Quest’ultima denominazione è la più usata e, come spesso succede, la meno corretta dal punto di vista tecnico, dato che anche i compilatori JIT producono codice nativo.
Un compilatore Ahead-Of-Time (AOT) prende in input i vostri file jar o class e produce un eseguibile convenzionale in linguaggio nativo, come un EXE Windows o un binario ELF per Linux. Così come qualsiasi altra soluzione tecnica, anche questa ha i suoi vantaggi e svantaggi.

Vantaggi:

  • Performance. Un compilatore JIT funziona come una applicazione in background e condivide la cpu e le risorse di memoria con l’applicazione che compila e con qualsiasi altra applicazione in esecuzione. Un compilatore AOT gira sul sistema dello sviluppatore senza vincoli di tempo o di risorse. Per questo può, potenzialmente, utilizzare ottimizzazioni di risorse più potenti, rendendo il codice migliore.
  • Protezione della proprietà intellettuale. Il bytecode Java è molto facile da decompilare, basta cercare “download java decompiler” per ottenere il codice sorgente di qualsiasi applicazione java in 5 minuti. Sì, è possibile offuscare i nomi delle classi e dei metodi pubblici ai quali non si accede via reflection, ma l’offuscamento del controllo di flusso può rendere il bytecode non verificabile dalle future JVM e rendere impossibili le ottimizzazione implementate dai compilatori JIT. Infine, criptare il bytecode non lo protegge del tutto, qualsiasi algoritmo di criptaggio si usa. D’altra parte, il codice nativo prodotto da un compilatore AOT ottimizzante è troppo complesso da essere sottoposto a reverse engineering, quanto quello di applicazioni sviluppate in C++. Inutile da dire, non ci sono prestazioni perse. Se si è interessati a proteggere la propria proprietà intellettuale, si dia uno sguardo alla compilazione nativa.
  • Percezione dell’utente. Le applicazioni Java Client soffrono spesso della cosidetta syndrome del ciclo di riscaldamento. Avviare un programma java richiede l’interpretazione del bytecode, la sua profilazione e la compilazione JIT. Per questo i programmi Java tendono a partire più lentamente dei loro rivali nativi e l’iniziale tempo di risposta di una applicazione grafica Java è molto peggiore di quello effettivo dopo che è stata usata parecchie volte, che sono le due maggiori ragioni per cui Java è ancora percepita come lenta da molti utenti. Un eseguibile nativo gira direttamente sull’hardware, senza l’overhead interpretazione-profilazione-compilazione, così può avviarsi più velocemente ed ottenere migliori tempi di risposta.

 

Svantaggi:

  • Peso sul disco. Il bytecode del Java è stato progettato per la compattezza, a un livello molto più elevato del set di istruzioni macchina. Si pensi che un eseguibile prodotto da un compilatore AOT sia 2-4 volte più grande dell’originale file jar.
  • Applicazioni dinamiche.Le classi che l’applicazione carica dinamicamente a tempo di esecuzione possono essere non disponibili al sviluppatore. Queste librerie possono essere plugin di terzi, proxy dinamici ed altre classi generate a tempo di esecuzione e così via. Così il sistema deve includere un interprete di bytecode Java e/o un compilatore JIT. Inoltre, generalmente solo le classi che sono caricate dal sistema o dal classloader dell’applicazione possono essere precompilate in codice nativo. Così le applicazioni che usano i classloaders custom possono soltanto essere parzialmente precompilati.
  • Ottimizzazioni per Hardware specifico. Un compilatore JIT presenta un vantaggio potenziale rispetto ai compilatori AOT in quanto può selezionare pattern di generazione di codice secondo la reale configurazione hardware su cui sta girando l’applicazione. Per esempio, può utilizzare le estensioni Intel MMX/SSE/SSE2 per velocizzare i calcoli in virgola mobile. Un compilatore AOT deve o produrre codice per il minor comun denominatore oppure diverse versioni per i più metodi più CPU-intensivi, aumentando il volume di codice.

 

C’è anche un comune pregiudizio che la compilazione AOT uccida la portabilità Java. Questo non è il caso, dato che il codice sorgente non necessita di essere modificato e si può sempre distribuire il bytecode per le piattaforme per cui non esiste un compilatore AOT. (Si perderebbe ovviamente il vantaggio della protezione del codice).
Per quanto riguarda i tools, nel 2000 ne erano presenti una mezza dozzina, ma solo un paio sono sopravvissuti, si tratta di Excelsior JET e di GCJ (Gnu Compiler for Java). Se siete interessati al campo embedded, date un’occhiata a NewMonics PERC, che si indirizza a J2ME CDC ed ha anche un supporto limitato a J2SE 1.3.

Confronto dei prodotti:

Lavoro per la compagnia che ha scritto Excelsior JET, per questo saltate il resto dell’articolo se credete che stia diventando vergognosamente di parte.

Da Giugno 2005, la pagina Status del sito ufficiale GCJ lista 15 target supportati, dal “puro metallo” ARM a XScale su IBM s390x mainframes. Alcune altre macchine di riferimento, soprattutto Windows, comunque, non sono supportate pienamente. Excelsior Jet supporta, al momento, solamente Windows e Linux su Intel x86. La versione Windows è sul mercato dal 2000, per questo è più matura di quella linux, introdotta nel 2004.

Le librerie runtime di GCJ, libgcj, sono un’implementazione opensource delle classi core Java e seguono distanziate lo sviluppo Sun. Al momento, Giugno 2005, il livello è JDK 1.2, con qualche feature della 1.4, ma non supporta AWT. Per questo GCJ può compilare le applicazioni grafiche solo usando toolkit di terze parti, indipendenti da AWT, com SWT. Excelsior JET interopera con la JRE Sun e supporta tutte le api J2SE 5.0. Si sta lavorando per certificare la compatibilità al 100% con J2SE 5.0.

Entrambi i prodotti supportano il caricamento dinamico delle classi. GCJ esegue le classi caricate dinamicamente in un interprete, mentre JET utilizza un compilatore JIT. Questo permette un caching del compilato che permette di riusarlo quando l’applicazione viene rilanciata. GCJ e libgcj sono sotto licenza open source (GPL) e può essere quindi scaricato, modificato e distribuito gratuitamente. Notate che la “libgcc exception” si applica a libgcj, quindi utilizzare questo prodotto non obbliga a distribuire la propria applicazione sotto licenza GPL.

Excelsior JET parte da 150 dollari per sviluppatore e ci sono dei costi di runtime se si necessita di ottimizzare l’applicazione per i server multiprocessore o per le workstation dual-CPU.

Questo conclude l’articolo. Vorrei trasformarlo in una risorsa Web live, così se avete commenti o conoscete risorse tool che dovrei aggiungere, comunicatemeli, per favore a dleskov at excelsior dash usa dot com.

Comunicate con l’autore:
Discuss this article with Dmitry

Autore originale Dmitry Leskov.

Tradotto in collaborazione con JavaLobby

Una risposta

  1. ken shiro ha detto:

    come compilare un’applet tramite gcj.exe a linea di comando?

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *