MokaByte Numero 20 - Giugno 1998


 

Java1.2 vs Java$

di

Ennio Grasso

CSELT Torino, 08/03/98 
http://andromeda.cselt.it/users/g/grasso/index.htm

 

 


 

 

Executive Summary

Da un po' di tempo Sun ha messo a disposizione la versione beta della nuova release di Java, il JDK 1.2. Si tratta di un cambiamento significativo rispetto alla versione 1.1.x così come questa lo era stata rispetto alla 1.0.x e vista ormai l'importanza di Java credo sia importante soffermarsi brevemente sulle novità più interessanti.

Se da un lato Java, quello "ufficiale" definito da Sun e supportato da IBM, Oracle, Netscape, et al. procede per la propria strada, dall'altro si affianca un altro Java, che chiamerò Java$, di Microsoft. La strategia di M$ è evidente: ribaltare il concetto proposto da Sun secondo il quale Java non è solo un linguaggio ma anche una piattaforma e un'architettura. Una piattaforma perché grazie alla presenza della Java Virtual Machine ogni differenza hardware e di sistema operativo viene annullata. Un'architettura in quanto Java definisce un modello a componenti, Java Beans, e supporta la comunicazione distribuita con Java RMI e ora, con il JDK 1.2, anche con CORBA. Sono proprio questi elementi a disturbare il predominio di M$ con la piattaforma Wintel e l'architettura DCOM/ActiveX. Il contrattacco di M$ consiste nello sgretolare gli aspetti di piattaforma e di architettura di Java relegandolo al ruolo di semplice linguaggio di programmazione al pari di C++ e Visual Basic. Ma come attirare gli sviluppatori alla propria versione di Java invece di quella ufficiale? Come convincerli ad abbandonare lo slogan "write once run everywhere" che caratterizza lo sviluppo di applicazioni Java? Semplicemente offrendo qualcosa difficile da rifiutare: l'accesso da Java a tutte le API di Windows, la possibilità di definire oggetti COM e ActiveX in Java con la stessa facilità del Visual Basic ma con la potenza del C++.

Non mi ero mai occupato di Java$. Ultimamente ho però dato uno sguardo a questo "nuovo mondo" e devo ammettere che chi sviluppa applicazioni in ambiente Windows non può ignorare la potenza messa a disposizione da Java$. Su questo punto tornerò a parlare in seguito.

Le novità di Java 1.2

Iniziamo col dire che Java sta "ingrassando" non poco, anzi, forse troppo. Basti dire che il numero di packages (insiemi di classi) è passato da 8 nella versione 1.0 a 22 nella 1.1 e 45 nella 1.2. Di questo passo Java rischia di promuovere anch'esso i fat-clients, altro che thin-clients! Per fortuna Java 1.2 introduce una novità importante, quella delle extensions, che dovrebbe porre fine, o ridurre notevolmente, la necessità di continui upgrade di versione. Procediamo con ordine iniziando proprio dalle extensions.

Java extensions

L'idea delle extensions, o estensioni, è di risolvere il problema menzionato sopra: offrire un modello standard per estendere le funzionalità della piattaforma Java senza aumentare l'insieme delle classi di sistema (quelle incluse nel JDK). Il formato di un'estensione è quello di un JAR (Java ARchive), ossia un insieme di packages di classi Java compresse in un unico file. Finora i JAR erano usati come strumento conveniente per raggruppare le classi che formano un'applicazione anziché avere files separati per ogni classe con conseguente esplosione del numero dei files. Nel JDK 1.2 un JAR può raggruppare un insieme di classi che non rappresentano un'applicazione di per se ma servono a supporto di altre classi, e in tal caso formano un'estensione.

Il modello identifica due tipi di estensioni: downloaded e installed. Le prime sono recuperate dalla rete come le applets e sono soggette agli stessi vincoli di sicurezza di quest'ultime, le seconde sono invece installate localmente alla macchina e hanno gli stessi privilegi delle classi di sistema. Il modello è ricorsivo, nel senso che il JAR di un'estensione può a sua volta indicare una dipendenza nei confronti di una o più estensioni.

Il loading dinamico delle classi è stato modificato per supportare il modello delle estensioni. Nelle versioni precedenti il class loader della Virtual Machine cercava le classi usando la variabile di ambiente CLASSPATH. Adesso questa variabile non viene più utilizzata e il class loader adotta la seguente politica:

Facciamo un esempio. Creiamo un'applet e la "impacchettiamo" insieme a altre classi che abbiamo definito all'interno di un JAR. Supponiamo che l'applet usi una libreria di classi per la visualizzazione di ambienti VRML. Questa libreria è definita come una Java extension nel file display_vrlm.jar. Quindi indichiamo nel JAR dell'applet che questi dipende da display_vrlm.jar. Quando il JAR viene scaricato e l'applet eseguita all'interno di un browser, non appena si fa riferimento a una classe contenuta in display_vrlm.jar il class loader cerca la classe tra le installed extensions. È possibile infatti che l'estensione display_vrlm.jar sia già disponibile localmente. Se non viene trovata il class loader cerca l'estensione dal nodo di provenienza dell'applet e in questo caso diventa una downloaded extension.

Tra le installed extensions esiste una sotto categoria detta standard extension che rappresenta quelle estensioni definite e controllate direttamente da Sun. Tra queste possiamo citare Java Servlet (tra l'altro fornita assieme al JDK 1.2), Java Telephony (prima JTAPI), Java Management (prima JMAPI), Java 3D, Java Media Conference, Java Naming & Directory, Java Enterprise Beans, ecc.

Java Foundation Classes

Le Java Foundation Classes (JFC) rappresentano il miglioramento più significativo di Java 1.2 rispetto alle versioni precedenti. Le JFC sono il risultato dello sforzo congiunto di Sun, IBM e Netscape per aumentare il potere espressivo di Java nello sviluppo di interfacce grafiche.

Il modello precedente AWT (Abstract Windowing Toolkit), sebbene sufficiente in alcune circostanze, ha evidenziato diverse lacune quando si costruiscono interfacce grafiche sofisticate. Il pregio dell'AWT è di offrire una libreria indipendente dal sistema operativo per garantire la portabilità di applets e applicazioni Java che fanno uso di componenti grafiche. AWT ottiene questo risultato con un modello detto peer in cui ogni componente grafica (button, window, panel, scrollbar, ecc.) è una classe Java che avvolge (wrap) una corrispondente componente del sistema nativo sottostante e ne delega l'esecuzione dei metodi. Il problema del modello peer è la sua "pesantezza" per la mole di lavoro di traduzione tra le componenti AWT e le corrispondenti native: delegazione di invocazione di metodi, inoltro di eventi, mantenimento dello stato consistente tra le due componenti.

Le JFC consistono di 5 moduli:

Swing 

Swing definisce le componenti grafiche alternative all'AWT. Le componenti di Swing sono peerless, nel senso che sono completamente scritte in Java e non hanno una corrispondente componente nativa. Rispetto all'AWT, Swing rappresenta un salto qualitativo sia come potere espressivo che in termini di efficienza. Sembra una contraddizione: come fanno componenti Java pure a essere più efficienti di componenti native? Il trucco consiste nell'usare le componenti native solo per il rendering dell'immagine complessiva che deriva dalla composizione di più parti. Ad esempio, supponiamo di costruire un'interfaccia grafica composta da un panel con al suo interno due buttons, una text area e una scrollbar. Con l'AWT ognuna di queste componenti viene mappata su una componente nativa, per un totale di 5. Con Swing invece esiste una sola componente nativa: il graphic context che deriva dal rendering globale delle varie componenti Java con un miglioramento complessivo di prestazioni. Inoltre le componenti di Swing adottano il modello Java Beans e il corrispondente meccanismo di delegazione degli eventi (listeners) che migliora ulteriormente l'efficienza perché gli eventi sono inoltrati solo agli oggetti che manifestano interesse a riceverli. Con AWT invece gli eventi seguono la catena di ereditarietà delle classi e sono propagati anche a oggetti non interessati.

Una aspetto importante di Swing è di adottare il pattern Model-View-Controller (MVC) introdotto a suo tempo in frameworks grafici sofisticati come OpenStep e Taligent. Il pattern MVC divide ogni componente grafica in tre oggetti distinti:

Il pattern MVC permette grande flessibilità nello sviluppo di interfacce grafiche. In particolare, grazie all'MVC, Swing supporta il concetto di Pluggable Look & Feel (PL&F), ossia la possibilità di modificare a piacere l'apparenza di un'interfaccia grafica, persino mentre l'applicazione sta girando. A cosa serve il PL&F? Fino a poco fa si pensava fosse fondamentale che ogni interfaccia utente mantenesse il L&F del sistema ospitante. Un'interfaccia in Windows ha un L&F diverso dalla stessa progettata per Mac o Motif. Ora, nell'era delle intranets, l'interfaccia utente diventa quella del browser e la rigorosa aderenza al L&F del sistema ospitante diventa sempre meno importante. Fino all'estremo opposto di fornire lo stesso L&F indipendentemente dal sistema ospitante, o addirittura personalizzato in base alle preferenze dell'utente. Con Swing è possibile progettare interfacce grafiche che permettono all'utente di selezionare il L&F desiderato. Swing già dispone di alcuni L&F tra cui scegliere: Metal, Organic (specifici di Swing), Windows, Mac e Motif, rappresentati qui a titolo di esempio. Ma chiunque può estendere il framework inventandosi un proprio L&F e inserirlo (plugging) nella libreria.

 

 
Metal L&F Organic L&F

 

  
Windows L&F Mac L&F Motif L&F

Java 2D

Java 2D estende le potenzialità dell'AWT per il trattamento di immagini bidimensionali. Ora è possibile disegnare poligoni complessi, applicare rendering sofisticati, usare meccanismi di animazione e numerosi altri elementi.

Drag & Drop

Gli utenti sono abituati alla comoda e intuitiva metafora del Drag & Drop (D&D) di oggetti grafici. Dal punto di vista software il D&D è un protocollo complesso da implementare perché richiede una forte collaborazione tra le parti coinvolte (source e target). Le JFC supportano la funzionalità di D&D prima non disponibile nell'AWT. È possibile non solo il D&D tra applicazioni Java, ma anche tra un'applicazione Java e una non Java. Ad esempio, possiamo trascinare un documento RTF all'interno di una componente Java in grado di visualizzarlo.

Accessibility

È un insieme di classi a supporto di persone con problemi di accesso alle normali interfacce grafiche. Ad esempio uno screen magnifier in grado di ingrandire fino a 16 volte un'area dello schermo. Oppure uno screen reader in grado di tradurre una componente grafica in formato testuale da passare in input a uno speech generator.

Java Collections API

Vorrei spendere due parole sul concetto di collezione perché è talmente importante nello sviluppo di un programma, che non sia un semplice "Hello World", da meritare giusta considerazione. Brevemente, una collezione è un oggetto che rappresenta un insieme di oggetti. Ad esempio, un array è una collezione con certe caratteristiche: un insieme ordinato di elementi ad accesso diretto tramite indice. Le collezioni più importanti sono: Una classe che rappresenta una collezione fornisce metodi per inserire, rimuovere, trovare, ordinare, ecc., i suoi elementi. In genere esistono due possibilità: o lo sviluppatore definisce le proprie classi che implementano le collezioni di cui ha bisogno per la propria applicazione, oppure utilizza alcune classi di libreria che realizzano vari tipi di collezione. Ovviamente la seconda soluzione è da preferire perché evita di inventare la ruota ogni volta. La Standard Template Library (STL) è diventata lo standard a supporto delle collezioni in C++. Esiste qualche cosa di analogo in Java?

Le uniche due classi a supporto delle collezioni in Java 1.1 sono le classi Hashtable e Vector. Del tutto assente è però una differenziazione sull'implementazione delle collezioni. Per una collezione è importante non solo il tipo di funzionalità offerte, ma anche il modo in cui le funzionalità sono implementate. Questo aspetto non dove essere trasparente allo sviluppatore che deve avere la possibilità di scegliere l'implementazione più adatta al proprio caso come compromesso tra benefici/controindicazioni. Ad esempio, due liste, pur offrendo esattamente le stesse funzionalità, potrebbero esse implementate rispettivamente come un array e come insieme di oggetti linkati. La prima implementazione usa la memoria in modo più efficiente e permette accessi rapidi ma è penalizzante se occorre inserire e rimuovere oggetti di frequente perché richiede riallocazioni dell'array. La seconda implementazione ha vantaggi e svantaggi opposti. Se venisse fornita un'unica classe lista che maschera completamente ogni aspetto implementativo, lo sviluppatore non avrebbe la possibilità di migliorare le prestazioni dell'applicazione. Per questi motivi Java 1.2 fornisce un framework completo a supporto delle collezioni con diversi tipi di collezioni e diverse implementazioni per ogni tipo. Il nome di ogni collezione segue la convenzione <implementazione><tipo> dando origine alle seguenti classi:

implementazioni
 
tipo collezione
hash table
array
B-tree
(albero bilanciato)
oggetti linkati
insieme
HashSet
ArraySet
   
lista
 
ArrayList
 
LinkedList
dizionario
HashMap
ArrayMap
TreeMap
 
 

Ad esempio, un dizionario (detto Map) ha tre implementazioni: come hash table, adatto nei casi generali; come array, utile se il dizionario è di ridotte dimensioni e garantisce prestazioni migliori su operazioni di crezione e iterazioni; come albero bilanciato, che impone un ordinamento agli elementi e offre prestazioni migliori per le operazioni di ricerca ma è più costoso per le inserzioni di elementi. Le classi Hashtable e Vector del JDK 1.1 sono ancora disponibili anche se è preferibile ora usare le equivalenti HashMap e ArrayList rispettivamente. Una caratteristica delle nuove collezioni è di non offrire metodi sincronizzati (synchronized) come invece avviene per Hashtable e Vector. Questa scelta è dettata da ragioni di efficienza visto che il codice sincronizzato è decisamente penalizzante in Java. Se comunque le collezioni possono avere accessi concorrenti il framework fornisce delle classi di avvolgenti che si occupano di accedere in modo sincronizzato i metodi di una collezione.

Vi sarebbero diverse altre cose da dire al supporto delle collezioni, ma mi limito a segnalare che il framework è estensibile e chiunque può fornire un'implementazione personalizzata per un certo tipo di collezione, ad esempio una lista implementata da una tabella SQL. Finalmente le collezioni in Java hanno lo stesso potere espressivo dell'STL in C++.

Oggetti Reference

Java Reflection è a mio parere uno dei frameworks più importanti di Java. Con Java Reflection è possibile trattare le classi come dei metaoggetti per eseguire operazioni sofisticate, come ad esempio scoprire e invocare dinamicamente i metodi di una classe per relizzare modelli a componenti quali Java Beans. In Java 1.2 il concetto di metaoggetto viene esteso non solo per le classi ma anche i riferimenti di oggetto. È chiaro che solo applicazioni sofisticate hanno la necessità di trattare i metaoggetti Reference così come solo applicazioni avanzate hanno la necessità di trattare i metaoggetti Class, Method, Field, ecc. di Java Refelection. Ma a cosa serve avere metaoggetti che rappresentano riferimenti di oggetto? Essenzialmente per realizzare meccanismi di cashing sofisticati e per essere notificati nel momento in cui il garbage collector si appresta a rilasciare un oggetto. Normalmente un oggetto Java si trova in uno di due stati:
  1. 1 raggiungibile, se esiste anche un solo riferimento valido (un altro oggetto che vi riferisce);
  2. 2 non raggiungibile, se non esistono riferimenti validi e l'oggetto può essere rilasciato dal garbage collector.
Con i metaoggetti Reference si introducono diversi livelli di raggiungibilità con corrispondenti sottoclassi di Reference: strong (caso 1 precedente), guarded, weak e phantom. Solo quando tutti i metaoggetti Reference vengono "puliti" il garbage collector può rilasciare l'oggetto. L'applicazione può chiedere di essere notificata quando un riferimento di oggetto cambia stato. L'approccio consiste nel creare e registrare un Reference in una ReferenceQueue:

// obj is an object I want to control

MyClass obj = new MyClass();

WeakReference ref = new WeakReference(obj);

ReferenceQueue q = new ReferenceQueue();

ref.register(q);

...

Quando obj non ha più riferimenti validi il garbage collector inserisce il WeakReference ref nella coda q. L'applicazione può dedicare un thread separato che chiama l'operazione remove della coda. L'operazione blocca il thread fino a quando un oggetto cambia livello di raggiungibilità e diventa weak:

// this thread blocks till the weak reference is inserted in the queue

WeakReference ref = q.remove();

// do whatever you need to do with ref

...

ref.clear();

// now the garbage collector can release the object

Evoluzioni di Java Reflection, Serialization e RMI

Java Serialization e Java RMI sono due importanti frameworks inclusi nel JDK. Il primo permette di trasformare lo stato di un oggetto in uno stream di bytes. Questo stream può essere usato per salvare lo stato dell'oggetto su supporto persistente o per trasmetterlo nel corso di un'invocazione remota. E in effetti Java RMI permette invocazioni di metodi di oggetti remoti e usa Java Serialization come protocollo per trasmettere i parametri di invocazione. Pur conservando una compatibilità a con le versioni precedenti, la versione 1.2 introduce diverse novità in Java Serialization. Alcune funzionalità aggiuntive per rendere ancora più flessibile e sofisticato il meccanismo di serializzazione di una classe, ma che non aumentano il potere espressivo di Serialization:
  1. 1 Una classe può indicare esplicitamente quali attributi serializzare dichiarando l'attributo:
  2. public final static ObjectStreamField[] serialPersistentFields = {...};
  3. che serializza gli attributi elencati nell'array serialPersisteField anziché ispezionare la classe e serializzare tutti gli attributi non static e non transiet come avviene di default.
  4. 2 Le classi ObjectOutputStream.PutField e ObjectInputStrem.GetField sono due inner classes che permettono un ulteriore controllo fine sugli attributi da serializzare;
  5. 3 Due nuove interfacce: Replaceable e Resolvable che permettono a un oggetto di indicare un altro oggetto come sostituto di serializzazione. Questo meccanismo viene ad esempio usato da Java RMI. Quando un oggetto che supporta l'interfaccia Remote viene passato come parametro di invocazione remota, invece di passare l'oggetto viene passata la sua stub realizzando così una semantica pass-by-reference anziché pass-by-value.
Ma la vera novità di Java Serialization non è a livello di API ma a livello implementativo. La versione 1.1 di Java Serialization usava metodi nativi per implementare gli accessi agli attributi di una classe. Ora invece il framework è stato completamente riscritto e sfrutta Java Reflection 1.2 che permette l'accesso agli attributi privati e protetti di una classe. L'accesso agli attributi privati e protetti è necessario per poter serializzare una classe ma ciò non è possibile con Java Reflection 1.1.

Per quanto riguarda Java RMI la novità più importante riguarda la possibilità di attivare automaticamente oggetti remoti. Nella versione 1.1 di Java RMI un client può invocare i metodi di un oggetto remoto solo se il server che esporta l'oggetto è attivo in attesa di invocazioni. Java RMI 1.2 supporta un meccanismo di attivazione simile a CORBA: l'invocazione viene dapprima dirottata su un processo demone che si fa carico di lanciare il server se questi non è attivo. Altre novità sono la possibilità di indicare il tipo di sockets da usare in una chiamata remota (es. SSL) e alcune estensioni minori all'API.

Java IDL

Il JDK 1.2 contiene il package org.omg.Corba che contiene l'implementazione Java di un ORB CORBA compliant. Non si tratta di una novità perché Java IDL (il nome dell'ORB) esiste già da qualche tempo come libreria separata ma il suo inserimento nel JDK può avere impatti strategici. Allo stato attuale non è chiaro se Sun punti più su Java IDL o Java RMI come infrastruttura di comunicazione distribuita. L'impressione è che Sun voglia mantenere aperte entrambe le opzioni anche se RMI sembra privilegiato.

Evoluzioni di Java Beans

Anche il supporto a Java Beans è stato migliorato. La nuova versione introduce un modello gerarchico di componenti bean. La specifica 1.1 di Java Beans definisce un protocollo peer-to-peer tra oggetti Java affinché questi si comportino da componenti plug & play. La specifica 1.2 introduce anche una relazione gerarchica di contenimento tra vari beans. Un bean può allo stesso tempo essere sia componente che contenitore di altri beans. Un contenitore agisce da ambiente di funzionamento delle proprie componenti le quali possono interrogare il contenitore per scoprire e invocare i servizi disponibili.

Evoluzioni di Java Security

Il modello di sicurezza di Java divide il codice in de categorie: Molte applicazioni Java hanno sofferto di questa netta dicotomia. Talvolta si vorrebbe concedere maggiore libertà alle applets e d'altro canto non si può essere sempre certi dell'integrità del codice locale. Una soluzione parziale al primo problema si ha con l'uso del codice firmato, anche se la definizione dei privilegi comporta una certa complessità di programmazione. Comunque non si risolve il problema di concedere pieni poteri al codice locale. Java Security 1.2 adotta un modello di sicurezza unificato sia per codice locale che codice proveniente dalla rete e utilizza un approccio dichiarativo. Vediamo di che si tratta.

L'accesso a una risorsa viene regolato da una richiesta di "permesso" che indica sia la risorse che il tipo di azione che vuole eseguire. A seconda dei privilegi del richiedente il permesso verrà concesso o meno e l'azione potrà essere eseguita. I permessi sono specificati come sottoclassi di java.security.Permission. Un'applicazione può definire una sottoclasse di Permission per regolare accessi alle proprie risorse. Il JDK 1.2 definisce già alcune sottoclassi: FilePermission, SocketPermission, PropertyPermission, RuntimePermission. Ad esempio un'applicazione (o applet) che vuole accedere a myfile in lettura e scrittura deve creare un'istanza di FilePermission:

FilePermission perm = new FilePermission("myfile", "read, write");

se vuole aprire una connessione sulla macchina loom.cselt.it:

SocketPermission perm = new SocketPermission("loom.cselt.it", "connet");

Dopo aver creato il permesso, l'applicazione (o applet) lo inoltra all'AccessController che verifica se questa ha il diritto all'azione richiesta. In caso contrario l'AccessController solleva un'eccezione e nega la possibilità di eseguire l'operazione:

AccessController.checkPermission(perm);

Come fa l'AccessController a sapere se concedere o meno il permesso all'applicazione (o applet)? Consultando il file java.policy che contiene un elenco di questo tipo:

grant [SignedBy "signer"] [, CodeBase "URL"] {

permission perm_class ["target"] [, "action"] [, SignedBy "signer"];

permission ...

}

grant ...

Ogni entry grant concede al codice firmato da "signer" e proveniente da "URL" tutti i permessi elencati. Facciamo alcuni esempi:

grant {

permission java.io.FilePermission "myfile", "read";

}

concede a tutto il codice, indipendentemente se firmato o meno e dalla sua provenienza, l'accesso in lettura al file myfile.

grant SignedBy "Ennio", CodeBase "http://loom.cselt.it" {

permission java.io.FilePermission "/usr/ennio/-", "read,write,execute";

permission java.io.SocketPermission "*", "connect";

}

concede al codice firmato da Ennio e proveniente da http://loom.cselt.it (quindi applets) di leggere, scrivere, eseguire tutti i files nella directory /usr/ennio e sue sotto directories, e di aprire connessioni su qualunque macchina in rete.

grant SignedBy "Ennio" {

permission java.io.FilePermission "/usr/ennio/-", "read,write,execute";

permission java.io.SocketPermission "*", "connect";

}

è identico al precedente ma questa volta le permissions sono concesse al codice firmato da Ennio indipendentemente dalla sua provenienza: locale o qualunque nodo della rete.

Un'applet firmata da Ennio che voglia scrivere sul file /usr/ennio/myfile dovrà contenere il seguente frammento di codice:

FilePermission perm = new FilePermission("/usr/ennio/myfile", "write");

try {

AccessController.checkPermission(perm);

// write myfile

} catch (AccessControlException e) {

// I don't have the rights :(

}

Microsoft SDKJ 2.0

È giunto il momento di dare uno sguardo a quello che ho chiamato Java$, la personalizzazione M$ di Java. In questo caso l'equivalente del JDK viene detto SDKJ (Software Development Kit for Java), giunto alla versione 2.01, con svariate differenze rispetto al JDK da aver condotto Sun a citare M$ in tribunale per aver infranto le regole del contratto di concessione. In effetti M$ non si è limitata ad aggiungere nuove classi e funzionatà, cosa certamente possibile, ma ha alterato il comportamento di alcune classi "ufficiali" dei packages di Sun (quelli che iniziano con java.). Uno sviluppatore che usa il SDKJ può essere indotto a pensare che usando le classi ufficiali il proprio codice continui a essere portabile, mentre in realtà non lo è. M$ ribatte accusando Sun di comportamento sleale, sostenendo che altri (rif. Netscape) in passato hanno alterato le classi ufficiali senza proteste da parte di Sun, e che Internet Explorer 4 è attualmente il browser più conforme alle specifiche Java 1.1 sul mercato. Sia Sun che M$ hanno validi argomenti a loro sostegno e spetta alle aule di tribunale decidere chi ha ragione. È probabile che la sentenza cerchi di mediare e accontentare un po' tutti.

Nel frattempo osserviamo più da vicino le proprietà del SDKJ che lo distinguono dal JDK. Molte di queste proprietà sono rivolte agli sviluppatori Windows per usare Java come linguaggio alternativo a Visual Basic e C++.

Java chiama Windows

L'aspetto più interessante di Java$ è l'accesso a tutta la piattaforma Windows. Inutile negarlo, esiste una tale mole di software in ambito Windows che pensare di riscrivere tutto in Java è pura follia. Un'applicazione Java deve essere in grado di riusare il codice esistente e minimizzare i costi di sviluppo. M$ ha (ovviamente) indirizzato questo problema fornendo tre modi per interfacciare Java con la propria piattaforma, cui corrispondono altrettanti livelli di astrazione:

Java ® DDL

DLL sta per Dynamic Link Library, e rappresenta il formato binario dei moduli di riuso software in ambito Windows i cui servizi sono offerti tramite API di funzioni C. Le DLLs più importanti sono quelle che formano la WIN32 API a cui deve accedere ogni applicazione che voglia sfruttare i servizi di Windows. Ma Java è una cosa mentre le DLLs offrono delle API in C, come fare? La specifica di Java prevede un meccanismo di interfacciamento Java/C detto JNI (Java Native Interface). JNI richiede la scrittura di codice C avvolgente (wrapping) interposto tra la parte Java e l'API C, ma richiede uno sforzo non trascurabile dovuto a: Il risultato è che uno sviluppatore Java che voglia invocare funzioni C deve anche sviluppare del codice C. Anzi, deve essere un buon programmatore C visto che il tipo di operazioni richieste necessitano di una buona conoscenza degli aspetti di allocazione di memoria, conversione di tipo, gestione della concorrenza, ecc. Niente paura, M$ ha la risposta ai vostri problemi!

L'alternativa M$ a JNI si chiama J/Direct e ha l'obiettivo è di semplificare l'interfacciamento Java/C in ambito Windows. E bisogna ammettere che J/Direct è più immediato da usare di JNI perché non richiede la scrittura di codice C, anche se comporta una programmazione "sporca" e sicuramente non portabile. L'approccio di J/Direct è quello di introdurre nel codice Java delle direttive di compilazione all'interno di commenti. Le direttive vengono tradotte dal compilatore Java di M$ in opportune istruzioni della Virtual Machine la quale fornisce il supporto runtime per l'integrazione Java/C. Ovviamente solo il compilatore Java di M$ è in grado di riconoscere queste direttive e generare il codice di interfacciamento; le direttive sono racchiuse all'interno di commenti in modo che ogni compilatore non M$ le ignori senza sollevare errori di sintassi. Un esempio di J/Direct:

class MyClass {

/** @dll.import("USER32") */

public static native int MessageBox(int hwnd, String text, String title, int fuStyle);

...

}

La prima linea, racchiusa tra commenti, è la direttiva J/Direct. Nell'esempio la direttiva indica al compilatore che il metodo MessageBox della riga successiva corrisponde in realtà a una funzione C (con lo stesso nome) esportata dalla DLL USER32. Il metodo deve sempre avere i modificatori static e native. La Virtual Machine di M$ è in grado di effettuare automaticamente la conversione Java/C della maggior parte dei parametri di invocazione. Ad esempio un int Java corrisponde a un intero 32 bit C, un oggetto String Java corrisponde a un array di caratteri terminato da NULL, ecc. Le complicazioni sorgono quando la funzione C si aspetta:

Parametri di ritorno

Moltissime funzioni C, quasi tutte quelle di Windows, si aspettano come parametri di invocazione dei puntatori a variabili da modificare durante l'esecuzione. Ad esempio:

void SomeFunction(int x, *int y, int *z);

che viene invocata in C come:

int a, b;

SomeFunction(10, &a, &b);

In Java non esiste il concetto di puntatore. Per simulare l'effetto di un puntatore e avere delle variabili modificate nel chiamante è necessario passare i parametri per riferimento anziché per valore. In Java la semantica del passaggio dei parametri è definita in base al loro tipo: i tipi primitivi (int, char, ecc.) sono sempre passati per valore mentre gli oggetti sono sempre passati per riferimento. Come passare un tipo primitivo per riferimento? In Java gli arrays sono oggetti e quindi sono passati per riferimento; quindi il trucco di J/Direct consiste nell'allocare un array di dimensione [1] avente come unico elemento il tipo primitivo da passare. Ad esempio, per invocare SomeFunction da Java:

public class MyClass {

/** dll.import("MYDLL") */

public static native SomeFunction(int x, int y[], int z[]);

}

chiamata come:

int a[] = new int[1];

int b[] = new int[1];

MyClass.SomeFunction(10, a, b);

Strutture C

Java non ha il concetto di struct del C. È possibile simulare una struttura con una classe priva di metodi e avente come attributi i campi della struttura. Non è però possibile creare istanze della classe e passarle alle funzioni C come se fossero strutture perché il layout di una classe Java (il modo in cui gli oggetti sono allocati in memoria) viene deciso dall'interprete, non a tempo di compilazione come in C. Inoltre, il garbage collector di Java può spostare a runtime gli oggetti in memoria per ottimizzarne l'uso. Se un oggetto usato per rappresentare una struttura venisse spostato durante l'esecuzione della funzione C gli effetti sarebbero disastrosi perché la funzione si aspetta che la struttura mantenga posizione fissa quando accede ai suoi campi.

La soluzione di J/Direct è di introdurre una seconda direttiva per indicare che una classe Java viene usata per rappresentare una struttura C e che quindi il garbage collector non ha la facoltà di spostare in memoria le istanze della classe. La struttura C:

typedef struct {

int x;

char y;

} A_STRUCT;

corrisponde in Java:

/** @dll.struct() */

class A_STRUCT {

int x;

char y;

}

Una funzione AnotherFunction(*A_STRUCT p) della DLL MYDLL può essere invocata da Java:

public class MyClass {

/** dll.import("MYDLL") */

public static native AnotherFunction(A_STRUCT);

 

public void callit() {

A_STRUCT alfa = new A_STRUCT();

AnotherFunction(alfa);

}

}

Ma c'è ancora un problema. Molte strutture C contengono degli arrays tra i loro campi. Il compilatore C ottimizza la memoria allocando lo spazio dell'array all'interno della struttura stessa. In Java invece ogni array viene sempre allocato come oggetto separato. Anche in questo caso l'unico modo per risolvere il problema è di usare una terza direttiva per forzare l'allocazione dell'array all'interno della classe che rappresenta la struttura:

typedef struct {

int x;

float y[32];

} EMBEDDED_ARRAY;

corrisponde in Java:

/** @dll.struct() */

class EMBEDDED_ARRAY {

int x;

 

/** @dll.structmap([type=FIXEDARRAY, size=32]) */

float y[];

}

La direttiva dll.structmap specifica che l'array y[] di dimensione 32 deve essere allocato all'interno della classe. Un caso particolare sono gli array di caratteri intesi come stringhe. In questo caso si usa una variante:

typedef struct {

int x;

TCHAR y[32]; // TCHAR è un tipo di Windows

} EMBEDDED_ARRAY;

corrisponde in Java:

/** @dll.struct() */

class EMBEDDED_ARRAY {

int x;

 

/** @dll.structmap([type=TCHAR[32]]) */

String y;

}

Puntatori di funzione

Alcune funzioni C richiedono come parametro un puntatore a una funzione di callback invocata nel corso della loro esecuzione. In questo caso l'interfacciamento Java/C dovrebbe permettere di specificare un metodo di una classe Java da passare alla funzione C che verrà poi invocato da quest'ultima. In Java non è possibile passare puntatori a metodi. Però è possibile passare riferimenti a oggetti. J/Direct definisce la classe com.ms.dll.Callback con un solo metodo: callback. Per invocare una funzione C che si aspetta una callback come parametro occorre definire una sottoclasse di Callback, specificare il comportamento desiderato del metodo callback, e passare un'istanza della classe nell'invocazione della funzione C:

import com.ms.dll.*;

public class MyCallback extends Callback {

public boolean callback(int x, int y) {

// do whatever you need to do

...

}

}

 

...

public class MyClass {

/** @dll.import("MYDLL") */

public static native YetAnotherFunction(Callback c, int p);

 

public void callit() {

YetAnotherFunction(new MyCallback(), 10);

}

}

Per finire vorrei sottolineare che il SDKJ contiene il package com.ms.win32, un insieme di classi Java che usano J/Direct per accedere alle funzionalità della WIN32 API. Il package contiene tre categorie di classi Java:

E con questo concludo il capitolo Java ® DLL. Mi sono limitato a trattare i casi più semplici che comunque coprono la maggior parte delle necessità di interfacciamento. Casi più complessi si hanno quando si devono trattare puntatori in Java e quando è occorre definire delle routine di marshalling/unmarshalling per tipi non standard.

Java ® COM

COM (Component Object Model) è il modello architetturale di M$ che specifica come deve essere scritto il codice in ambito Windows per facilitare aspetti di gestione e riuso del software. COM non nasce a tavolino come sforzo accademico di definizione di un modello "avanzato" o "interessante". Invece, COM è il risultato della razionalizzazione di pratiche di programmazione C++ su Windows. In linea teorica il modello di COM è indipendente dal linguaggio di programmazione anche se fino a ieri il C++ era il linguaggio di riferimento e richiedeva programmatori con un alto grado di esperienza. Ma con il SDKJ le porte di COM si aprono anche ai programmatori Java e, manco a dirlo, in termini notevolmente semplificati rispetto al C++. Esistono due possibilità per lo sviluppatore Java:

Usare oggetti COM

Usare oggetti COM in Java è certamente la possibilità più interessante. Ma cosa significa "oggetto COM"? Il formato di rilascio binario dei moduli software in ambito Windows sono le DLLs. Una DLL può o meno essere conforme al modello COM. Se una DLL non è conforme a COM, l'unica cosa certa è che offre un insieme di funzioni C e possiamo usare J/Direct per accedervi da Java. Ma se la DLL è conforme a COM allora possiamo fare alcune assunzioni sul modo in cui le sue funzionalità sono esposte e semplificare le operazioni per accedere a tali funzionalità.

Le funzionalità degli oggetti COM sono descritte con un linguaggio dichiarativo IDL (Interface Definition Language) molto simile all'omonimo di CORBA. La compilazione della specifica IDL produce la type library dell'oggetto COM, l'equivalente runtime dell'IDL, che può essere consultata dinamicamente da un'applicazione per scoprire le funzionalità offerte dalle interfacce. L'informazione della type library può essere inserita direttamente nella DLL, o in un file separato con estensione TLB.

Il SDKJ contiene il tool JactiveX in grado di esaminare il contenuto di una type library e generare una classe Java avvolgente corrispondente alla classe COM e tante interfacce Java quante sono le interfacce supportate dalla classe COM. Notate che a differenza del C++ che non ha il concetto di interfaccia, in Java è possibile un mapping 1-1 dei concetti di classe e interfaccia di COM facilitando il passaggio Java « COM. La classe COM viene vista come una qualsiasi classe Java e il gioco è fatto! Ad esempio, supponiamo che la DLL server.dll abbia una type library che descrive la classe CComServer con due interfacce IComServer e IComManage:

jactivex /javatlb server.dll

produce il package server che contiene la classe CComServer e le interfacce IComServer e IComManage che possono essere importate e usate in qualunque programma Java:

import server.*;

IComServer s = (IComServer)new CComServer();

s.op1();

IComManage m = (IComManage)s;

m.op2();

Il flag javatlb serve a indicare che si vuole accedere a un oggetto COM e non un ActiveX, come vedremo in seguito. Notate come l'uso di COM si riduca a normale programmazione Java e confrontate la complessità dell'equivalente C++:

IComServer *s = NULL;

CoCreateInstance(CLSID_CComServer, NULL, CLSCTX_SERVER,

IID_IComServer, (void**)&s);
s->op1();

IComManage *m = NULL;

s->QueryInterface(IID_IComManage, (void**)&m);

m->op2();

s->Release();

m->Release();

La semplicità d'uso di COM in Java è possibile perché la Virtual Machine di M$ conosce il protocollo di COM e gestisce automaticamente le chiamate AddRef, Release e QueryInterface dell'interfaccia IUnknown che tutti gli oggetti COM supportano.

Scrivere oggetti COM in Java

Per realizzare un oggetto COM in Java è necessario seguire i seguenti passi: A questo punto l'oggetto COM può essere invocato da qualunque applicazione conforme a COM, C++ o Java che sia. Il modo migliore per illustrare il procedimento è tramite un esempio.

1. Definiamo un oggetto COM con un'interfaccia IWidget e classe CWidget:

[

object,

uuid(F3588E20-F601-11cf-BC7E-00A0C913D202),

]

library WidgetLib

{

[

uuid(F3588E21-F601-11cf-BC7E-00A0C913D202),

helpstring("IWidget"),

 

]

interface IWidget : IUnknown {

HRESULT add([in] long x, [in] long y, [out,retval] long *pz);
};

 

[

uuid(A364ACF2-F5EF-11cf-BC7E-00A0C913D202),

helpstring("CWidget")

]

coclass CWidget {

[default] interface IWidget;
};

};

Non è mia intenzione entrare nei dettagli della sintassi IDL perché esula da questa nota. In particolare, l'uso di quelle strane cifre (uuid) serve per assegnare degli identificatori univoci a tutte le entità di COM, interfacce, classi, librerie, ecc.

2. Compiliamo il file IDL con il compilatore MIDL che genera la type library dell'oggetto COM nel file WidgetLib.tlb. Questi passi (1 e 2) sono comuni a qualunque sviluppo di oggetti COM indipendentemente dal linguaggio usato per implementarli. Ora entra in gioco Java.

3. Usiamo JactiveX su WidgetLib.tlb con due flags: javatlb e cj. javatlb l'abbiamo già incontrato e serve a generare la classe Java CWidget e interfaccia IWidget. Il flag cj ordina a JactiveX di generare un'ulteriore classe CWidgetImpl con questo aspetto:

public class CWidgetImpl implements IWidgetDefault,com.ms.com.NoAutoScripting {

public int add(int x, int y) {

throw new com.ms.com.ComFailException(0x80004001); //E_NOTIMPL
}

}

4. Ora implementiamo l'oggetto COM in Java. Questo può essere fatto scrivendo direttamente il comportamento dei metodi della classe CWidgetImpl, oppure definendo una sua sottoclasse:

public class MyWidget implements CWidgetImpl {

public int add(int x, int y) {

return x + y;
}

}

5. Per finire registriamo l'oggetto COM nel registry di Windows con il tool Javareg in modo che ogni applicazione possa invocarne le funzionalità:

javareg /register /class:MyWidget

/clsid:{A364ACF2-F5EF-11cf-BC7E-00A0C913D202}
clsid deve essere lo uuid indicato per la classe CWidget nella specifica IDL, mentre class indica il nome della classe Java che fornisce l'implementazione all'oggetto COM. Come vedete implementare oggetti COM in Java è un po' più complesso che semplicemente usare oggetti COM da Java. La complessità aggiuntiva è dovuta alle regole del modello COM che richiedono una specifica IDL, la generazione della type library e infine la registrazione nel registry di Windows.

Java ® ActiveX

Eccoci all'ultimo capitolo sull'integrazione di Java con il mondo M$. Si tratta del livello più astratto e facile da usare: l'integrazione Java « ActiveX. Cos'è ActiveX e che relazione ha con COM? Non voglio annoiarvi con la storia di ActiveX e la sua derivazione da OLE. In parole semplici, ActiveX estende COM con un insieme di regole affinché gli oggetti si comportino come componenti plug & play, in grado cioè di scoprire e invocare dinamicamente i loro servizi. Facendo un paragone con Java, vale la proporzione: JavaBeans/Java = ActiveX/COM; così come JavaBeans specifica come scrivere classi Java in modo che si comportino da componenti plug & play, ActiveX fa la stessa cosa con gli oggetti COM. Molti concetti nel modello ActiveX hanno un equivalente Java Beans e viceversa facilitando il mapping tra i due. Entrambi usano un modello di comunicazione a eventi, entrambi definiscono properties per leggere e modificare gli attributi, entrambi supportano il concetto di introspezione delle funzionalità offerte, ecc. Come per l'integrazione Java « COM anche in questo caso abbiamo due possibilità:

- usare degli ActiveX in Java come se fossero dei Java Beans,

- usare dei Java Beans in Visual Basic, Internet Explorer, ecc., come se fossero degli ActiveX.

ActiveX come Java Beans

Esiste sul mercato un vasto numero di ActiveX dalle più svariate funzionalità. Le applicazioni in ambiente Windows possono includere e usare ActiveX scritti da terze parti aggingendo il codice "colla" che serve a chiamare le funzionalità degli ActiveX importati. Questo è l'approccio tipico dei programmi Visual Basic, Delphi e Power Builder. E perché non Java? Certo che sì.

Ricordate il tool JactiveX descritto per l'integrazione Java « COM? Come il nome suggerisce, questo tool è stato pensato prima di tutto per l'integrazione Java « ActiveX, o meglio Java Beans « ActiveX. JactiveX estrae le informazioni contenute nella type library di un ActiveX e genera una classe Java conforme a Java Beans che avvolge l'ActiveX e tante interfacce Java quante sono le interfacce dell'ActiveX.

A questo punto basta includere il bean nel programma Java e invocare le sue funzionalità. L'esempio che segue usa l'ActiveX Calendar incluso il M$ Office:

jactivex calendar.ocx

produce un bean Calendar.java che può essere usato da un programma Java:

import com.ms.activex.*;

import calendar.*; // Classes from JActiveX when run on Calendar control or typelib

 

public class UseCalendar implements ActiveXControlListener {

Calendar c;

 

public UseCalendar() {

c = new Calendar();

c.addActiveXControlListener(this);

}

 

// Implementation of ActiveXControlListener interface

public void controlCreated(Object o) {

if (o instanceof Calendar) c.today();

}

}

Java Beans come ActiveX

I passi per definire un oggetto COM in Java sono alquanto laboriosi. Si parte dalla specifica IDL, generazione della type library, quindi si usa JactiveX e poi Javareg. Se invece di definire un oggetto COM volgiamo costruire un ActiveX in Java le cose si semplificano molto. Niente IDL, né type library, invece partiamo dalla cosa più semplice possibile: un Java bean. Definiamo il nostro bean senza seguire alcuna regola particolare se non quelle dettate dal modello Java Beans. Oppure possiamo già disporre di un bean realizzato da qualche terza parte. L'unica cosa che dobbiamo fare è rendere il bean visibile come un ActiveX. Per questo usiamo direttamente il tool Javareg sulla classe del bean:

javareg /register /control /class:MyBean /typelib:MyBean.tlb

Javareg svolge automaticamente diversi compiti:

A questo punto il bean può essere usato come un qualunque ActiveX in applicazioni in grado di trattare gli ActiveX. Notate che solo la Virtual Machine di M$ presente in Internet Explorer 4 e nel SDKJ offre il supporto per il passaggio Java « ActiveX implementando le interfacce IUnknown e IDispatch.

Per finire due parole sull'uso del tag OBJECT di IE4. Questo tag serve per includere gli ActiveX nelle pagine HTML. A differenza delle applets che hanno la vita della pagina in cui sono incluse, gli ActiveX vengono installati su disco. Accessi successivi alla stessa pagina non comportano il trasferimento dell'ActiveX già disponibile localmente. Solo se l'ActiveX viene aggiornato sul server sarà nuovamente scaricato per sostituire la vecchia versione. Questo modello potrebbe essere interessante anche per le applets, soprattutto se hanno dimensioni tali da rendere pesante il loro trasferimento. Con IE4 è sufficiente includere l'applet nella pagina HTML usando il tag OBJECT anziché APPLET per far si che l'applet sia considerata dal browser come un ActiveX e quindi salvata su disco. Non occorre nemmeno usare Javareg: ci pensa IE4 a registrare la classe nel registry.

Chiuso il capitolo più importante di Java$, passiamo a un altro aspetto controverso nella guerra Java vs Java$, le Application Founation Classes.

Application Foundation Classes

Le AFC sono l'equivalente M$ delle JFC di Sun. Anche le AFC nascono come la necessità di migliorare il supporto grafico offerto alle applicazioni Java cercando di ovviare alle inefficienze dell'AWT. Come le JFC, le AFC sono peerless, nel senso che sono scritte completamente in Java e non hanno una corrispondente componente nativa e, come le JFC, offrono diverse componenti grafiche aggiuntive per aumentare il supporto alla generazione di interfacce grafiche. Ma le somiglianze finiscono qui. Le differenze più significative sono: In conclusione, meglio JFC o AFC? In termini di design, efficienza e flessibilità JFC è sicuramente migliore, senza contare che rappresenta la versione ufficiale di Java. Le AFC sono certamente migliori dell'AWT, ben integrate in IE4 e più facili da usare delle JFC perché basate su un modello semplice anche se meno flessibile. Ma nonostante la loro inferiorità le AFC sono supportate da M$… e per alcuni ciò è sufficiente.

  

 

MokaByte Web - 1998
www.mokabyte.it

MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@infomedia.it