modello di eventi
Transcript
modello di eventi
EDITORIALE online.infomedia.it n. 66 - novembre/dicembre 2005 bimestrale - anno undicesimo Direttore Responsabile Marialetizia Mari ([email protected]) Direttore Esecutivo Il valore della conoscenza Francesco Balena ([email protected]) Managing Editor Renzo Boni ([email protected]) Collaboratori Filippo Bonanni Stefano Corti Raffaele Di Natale Maurizio Mammuccini Carlo Pagliei Fabio Perrone Alberto Rosotti Lorenzo Vandoni Direzione Natale Fino (nfi[email protected]) Marketing & Advertising I n tanti articoli su questo giornale abbiamo parlato delle tante bellissime novità di Visual Studio, di .NET Framework 2.0 e delle nuove versioni di VB e C. In questo editoriale proverò invece a fare qualche riflessione più “metodologica” che “tecnologica”. Partiamo con i linguaggi VB e C#, che sono stati potenziati tantissimo con generics e nuove keyword. Una delle mie preferite è l’istruzione Using, che è sempre stata presente in C# e ora è stata finalmente aggiunta anche a VB. Questa istruzione è importante perchè aiuta a scrivere correttamente applicazioni che usano risorse di sistema e le rilasciano correttamente, permettendo di evitare resource leak. Peccato che solo una minoranza di sviluppatori C# la usi correttamente, come ho potuto notare durante le code review e grazie a qualche domanda mirata nei miei seminari e conferenze. Segreteria: 0587/736460 [email protected] Amministrazione Sara Mattei ([email protected]) Grafica Manola Greco ([email protected]) Technical Book Lisa Vanni ([email protected]) Segreteria Enrica Nassi ([email protected]) Stampa TIPOLITOGRAFIA PETRUZZI Citta’ di Castello (PG) Ufficio Abbonamenti Tel. 0587/736460 - Fax 0587/732232 e-mail: [email protected] www.infomedia.it Gruppo Editoriale Infomedia srl Via Valdera P., 116 - 56038 Ponsacco (PI) Italia Tel. 0587/736460 - Fax 0587/732232 [email protected] Sito Web www.infomedia.it Passiamo a ClickOnce, che viene presentato come una rivoluzione nel mondo delle applicazioni Windows. Tutto vero, se non fosse che questa tecnologia è già disponibile dalla versione 1.0 di .NET, in forma meno sofisticata ma sufficientemente potente da poter essere usata in applicazioni reali. Mentre tutti si buttavano sul Web, alcune piccole aziende hanno investito su questa tecnologia e hanno sviluppato – in meno tempo e con meno spese – applicazioni più potenti e usabili della maggior parte delle applicazioni ASP. NET. Questo è un esempio di come il deficit di conoscenza possa portare a dilatare tempi e spese di sviluppo, e in definitiva a perdere occasioni commerciali. Il terzo esempio che mi viene in mente è il data binding nelle applicazioni Windows Forms, che è stato potenziato in modo considerevole grazie alla introduzione del BindingSource, un oggetto mediator tra la sorgente dati (es. un DataSet) e i controlli che mostrano i dati stessi. Oggetto presentato come una grande innovazione, se non fosse che un oggetto mediator molto simile è disponibile da sempre, sotto il nome di DataView. Per colpa del deficit di informazione, molti sviluppatori hanno rinunciato a una tecnologia molto utile (oppure hanno dovuto complicare il proprio codice all’inverosimile), ancora una volta allungando inutilmente i tempi di sviluppo. Dove portano queste mie considerazioni? Ovviamente al fatto che la potenza degli strumenti – i linguaggi, il framework, l’ambiente di sviluppo – vale ben poco se non è accompagnata dalla conoscenza delle possibilità (e dei limiti) delle tecnologie. A questi deficit si può supplire solo con la volontà di rimanere sempre aggiornati, leggendo non solo i manuali “ufficiali” (spesso molto carenti) ma libri, riviste come Visual Basic & .NET Journal, siti Internet, blog, e via elencando. Francesco Balena [email protected] Manoscritti e foto originali anche se non pubblicati, non si restituiscono. È vietata la riproduzione anche parziale di testi e immagini. Si prega di inviare i comunicati stampa e gli inviti stampa per la redazione all’indirizzo: [email protected] Visual Basic Journal è una rivista di Gruppo Editoriale Infomedia S.r.l. Via Valdera P, 116 Ponsacco - Pisa. Registrazione presso il Tribunale di Pisa n. 20/1999 N. 66 - Novembre/Dicembre 2005 VBJ 5 NOVEMBRE/DICEMBRE N.66 SOMMARIO SPECIALE .NET: Delegate e modello di eventi 8 I delegate, apparentemente strani e complicati, sono in realtà un facile meccanismo per una corretta strutturazione OOP della programmazione ad eventi nel .NET Framework. È importante comprenderne il funzionamento per sviluppare corrette applicazioni event-driven. di Maurizio Mammuccini TECNICHE 18 Application e User Settings in Visual Basic 2003 di Francesco Balena APPLICAZIONI 28 Controllo Remoto in Visual Basic .NET (terza puntata) di Stefano Corti .NET 36 Interprocess Communication con MSMQ e .NET Framework Quasi tutte le applicazioni oggigiorno devono comunicare con l’esterno; vediamo un approccio alla comunicazione fra processi utilizzando la libreria MSMQ, che permette di scrivere applicazioni sicure e aperte ai futuri sviluppi delle tecnologie di Carlo Pagliei 42 La GAC in salsa XML Convertiamo la GAC in un file XML per recuperare velocemente le informazioni su assembly, versioni e namespace di Filippo Bonanni SOFTWARE ENGINEERING 48 Model Driven Architecture Model Driven Architecture (MDA) è il termine con cui OMG identifica il suo tentativo di creare una architettura per lo sviluppo di applicazioni indipendenti dalla piattaforma di Lorenzo Vandoni REPORTAGE 52 Il nuovo umanesimo tecnologico di Alberto Rosotti Codice allegato All’indirizzo ftp.infomedia.it/pub/VBJ sono liberamente scaricabili tutti i listati relativi agli articoli pubblicati. La presenza di questa immagine indica l’ulteriore disponibilità, allo stesso indirizzo, di un progetto software relativo all’articolo in cui l’immagine è inserita. Il nome identifica la cartella sul sito ftp. RUBRICHE Editoriale 5 .NET Tools 61 N. 66 - Novembre/Dicembre 2005 VBJ 7 PROGRAMMAZIONE AD EVENTI .NET: Delegate e modello di eventi I delegate, apparentemente strani e complicati, sono in realtà un facile meccanismo per una corretta strutturazione OOP della programmazione ad eventi nel .NET Framework. È importante comprenderne il funzionamento per sviluppare corrette applicazioni event-driven. Eventi di Maurizio Mammuccini L a finalità del presente articolo è quella di trattare sinteticamente gli eventi, i delegate ed il modello di eventi del Microsoft .NET Framework, cercando di metterne in evidenza la programmabilità OOP e chiarendone i relativi costrutti formali previsti dalle specifiche. Il modello di eventi di .NET (e i delegate ad esso strettamente legati) infatti, è stato implementato dalla Microsoft aderendo totalmente ed in modo estremamente rigoroso al paradigma OOP e ciò principalmente per conseguire due grandi obiettivi di base: • • rendere più semplice l’utilizzo degli eventi nella fase di progettazione e sviluppo; renderne efficiente e sicura l’esecuzione del relativo codice (managed code e safe code) a runtime. Vorrei in questo articolo non soffermarmi sulla sola visualizzazione di uno o due esempi di come sia possibile far qualcosa con gli argomenti trattati nelle classi .NET prodotte, bensì fornire addizio- Maurizio Mammuccini ha esperienza decennale nella progettazione e sviluppo di software. Dedica particolare attenzione alle tecnologie di calcolo distribuito. Utilizza sin dalle primissime versioni il Visual Studio di Microsoft.Attualmente è formatore e consulente per aziende del settore informatico. È MCSD for VS6 - MCDBA for Sql Server 2000 - MCAD for .NET - MCSD for .NET ed MCT. Vive e risiede in Umbria. 8 VBJ N. 66 - Novembre/Dicembre 2005 nalmente una visione organica e generale degli argomenti in modo che poi ogni lettore possa proseguire da solo, utilizzandoli in modo corretto nelle proprie applicazioni. Perché gli eventi? Penso che in ogni esposizione che tratti di eventi nelle classi, sia d’obbligo chiarire cosa un’evento rappresenti per una classe e perché se ne renda necessaria l’introduzione nella programmazione. Una classe non dovrebbe essere solo un pezzo di codice da utilizzare quando occorre, bensì qualcosa di più organico; una classe dovrebbe poter comunicare con l’esterno emettendo notifiche recepibili e comprensibili da un qualche suo utilizzatore. Chiaramente resta da determinare cosa, di preciso, la classe dovrebbe notificare all’esterno. A mio avviso il contenuto della notifica, il suo messaggio, dovrebbe essere legato a quello che i pro- PROGRAMMAZIONE AD EVENTI gettisti object oriented definiscono come lo stato di un oggetto (scrivo usando il condizionale perché poi nella pratica non sempre è così). Tramite un evento la classe dovrebbe notificare all’esterno l’avvenuta modifica del suo stato. Possiamo riguardare lo stato di un oggetto come l’insieme dei valori in un certo istante – nel lifetime dell’oggetto – di tutti i campi membro della classe che definisce il tipo dell’oggetto. È chiaro che la sola modifica di uno di questi valori a runtime, porta inevitabilmente al cambiamento dello stato dell’oggetto che questi deve (non è comunque un obbligo) essere notificato all’esterno tramite un evento. Spesso questa percezione della realtà di un evento ci sfugge perché siamo abituati a pensare agli eventi come legati esclusivamente al mondo visuale di un’applicazione, ad esempio la notifica del clic su un bottone in un form od in una pagina web; gli eventi in una classe invece devono avere una giustificazione ben più ampia e concettualmente profonda. Stato di un oggetto Cerchiamo di approfondire il concetto di stato di un oggetto. Lo facciamo in un caso molto semplice, nel quale la classe che definisce il tipo dell’oggetto incapsuli un unico campo booleano. C# // campo booleano su cui valutare la variazione di stato bool valueState; VB.NET ‘campo booleano su cui valutare la variazione di stato Dim valueState As Boolean L’unico campo che va a determinare lo stato dell’oggetto è la variabile di tipo booleano valueState e l’evento dovrebbe venir sollevato ogniqualvolta il valore della variabile passi da Falso a Vero e viceversa (cambiamento di stato). Si potrebbe sviluppare un discorso di più ampio respiro prevedendo una classe che incapsuli un numero di campi maggiore di uno, ad esempio C# // campi di stato int valueStateInt; string valueStateString VB.NET ‘campi di stato Dim valueStateInt As Integer Dim valueStateString As String Dal punto di vista della variazione di stato dell’oggetto non è importante quale valore i campi assumano, ma piuttosto che i valori dei campi (od anche solo uno di questi) varino. Questo è motivo sufficiente per sollevare una notifica al client dell’oggetto, appunto sollevare un evento. Si potrebbe obiettare che molti eventi sollevati da oggetti non sempre sorgono corrispondentemente ad una variazione di stato; smontare comunque questa obiezione è semplice. Chiediamoci primariamente a cosa servano i metodi pubblici di un oggetto, ossia quei metodi che da client dell’oggetto noi vediamo ed invochiamo. Questi non sono che mezzi per manipolare correttamente i dati dall’esterno (valori dei campi) incapsulati nell’oggetto all’istante di tempo in cui li utilizziamo; in altre parole, mezzi per intervenire correttamente sui valori nei campi incapsulati, ossia sullo stato dell’oggetto facendo compiere all’oggetto qualcosa di utile sui dati. In questo ragionamento rientrano anche le proprietà esposte dall’oggetto. La classe che definisce il tipo di un oggetto non dovrebbe fornire funzionalità pubbliche a sé stanti, ossia logicamente scisse dai dati incapsulati al suo interno. Se ciò avvenisse dovrebbe essere in casi del tutto particolari e sporadici. L’intento è sempre quello di progettare tenendo a mente il principio di incapsulamento del codice, basilare nella programmazione orientata agli oggetti (OOP). N. 66 - Novembre/Dicembre 2005 VBJ 9 PROGRAMMAZIONE AD EVENTI La variazione dello stato di un oggetto deve sempre essere accompagnata da una notifica al client dell’oggetto dell’avvenuta modifica dei valori nei campi al suo interno. Delegate Vediamo dunque come .NET ci permetta di costruire queste notifiche di un oggetto e di spedirle al o ai client interessati, addizionandole di utili informazioni. Il fondamento di tutto il modello di eventi di Microsoft .NET, risiede nell’importante concetto di delegate. I delegate sono un’eccezionale feature del .NET Framework, e rassomigliano vagamente ai puntatori a funzione del C++. Ho scritto “vagamente” poiché i delegate sono migliori dei classici puntatori a funzione del C++, essendo type-safe e protetti. Scrivendo type-safe intendiamo che nel C++ (non gestito) l’indirizzo di una funzione è soltanto il suo indirizzo in memoria; questo, diversamente dai delegate, è privo di tutto un insieme di informazioni addizionali, quali il numero ed il tipo dei parametri che la funzione prevede, il tipo del valore restituito dalla funzione, la convenzione di chiamata della funzione. Dichiarare nel codice un tipo Delegate significa soprattutto predisporre un tipo derivato dalla fondamentale classe del .NET Framework System.Delegate e contenente il meccanismo di chiamata a quella che è detta essere un’ entità chiamabile. Cos’è un’entità chiamabile? Nel caso di metodi d’istanza possiamo definire un’entità chiamabile come la coppia data dall’istanza di una classe e da un metodo di questa; nel caso invece di un metodo statico (static) l’entità chiamabile è data dal nome completo (ossia comprensivo del nome della classe d’appartenenza) del metodo. Quando viene chiamata un’istanza di delegate contenente la chiamata ad una funzione che dichiara parametri, sarà poi possibile passare al delegate gli argomenti attesi in modo che la funzione venga richiamata con gli insiemi di argomenti definiti. 10 VBJ N. 66 - Novembre/Dicembre 2005 Dunque ognivolta che nel codice istanziamo un delegate, non facciamo altro che incapsulare in modo sicuro una invocazione ad una opportuna funzione, ossia ad una funzione di sintassi compatibile a quella dichiarata nell’istruzione delegate. C# //istruzione delegate public delegate int GetIntFromTwoInt(int i, int j); VB.NET ‘istruzione delegate Public Delegate Function GetIntFromTwoInt(ByVal i As Integer, ByVal j As Integer) As Integer Le dichiarazioni di delegate precedenti incapsulano chiamate a funzioni, invocazioni ad entità chiamabili, di sintassi compatibile, ossia che accettano due interi e restituiscono un intero. Ogni altra tipologia di invocazione non è permessa. Da ciò comunque emerge che uno stesso delegate può essere utilizzato per invocare differenti funzioni purchè di identica sintassi. Il delegate GetIntFromTwoInt sopra potrebbe essere indistintamente utilizzato per invocare sia una funzione Addizione che Moltiplicazione di interi, metodi pubblici di una classe OperazioniAritmetiche: C# //Addizione public int Addizione(int i, int j); //Moltiplicazione public int Moltiplicazione(int i, int j); VB.NET ‘Addizione Public Function Addizione(ByVal i As Integer, ByVal j As Integer) As Integer ‘Moltiplicazione Public Function Moltiplicazione(ByVal i As Integer, ByVal j As Integer) As Integer Il delegato GetIntFromTwoInt sopra non potrebbe però venir utilizzato per invocare una PROGRAMMAZIONE AD EVENTI funzione Opposto di un intero nella classe OperazioniAritmetiche: C# //Opposto public int Opposto(int i); VB.NET ‘Opposto vengono passati gli argomenti 3 e 4. Questa è un’impressione errata. Il compilatore, presa visione che dlg è un delegate, produce il codice necessario all’invocazione del metodo Invoke di dlg. Dunque il compilatore, quando compila l’istruzione di chiamata, la tratta come se contenesse esplicitamente l’invocazione al metodo Invoke, ossia come se fosse scritta così: Public Function Opposto(ByVal i As Integer) As Integer C# //Invocazione Per invocare il metodo tramite delegate dev’essere prodotta prima l’istanza del delegate con indicazione di quale funzione si intenda incapsulare l’invocazione. int result = dlg.Invoke(3,4); VB.NET ‘Invocazione Dim result As Integer = dlg.Invoke(3,4) C# Operazion//Istanziazione della classe del metodo iAritmetiche oa = new OperazioniAritmetiche(); //Incapsulamento nel delegate dell’invocazione al metodo GetIntFromTwoInt dlg = new GetIntFromTwoInt (oa.Addizione); In C# non è però possibile (a differenza di MC++ ) modificare il codice sorgente in modo da invocare esplicitamente la chiamata al metodo Invoke trasmettendo gli argomenti per il metodo incapsulato nel delegate; in tal caso otterremmo il seguente errore: VB.NET ‘Istanziazione della classe del metodo [Compiler Error CS1533] Dim oa As OperazioniAritmetiche = Il metodo Invoke non può essere chiamato New OperazioniAritmetiche() direttamente su un delegato. ‘Incapsulamento nel delegate dell’invocazione al metodo Dim dlg As GetIntFromTwoInt = New GetIntFromTwoInt(oa.Addizione) L’esecuzione della chiamata alla funzione incapsulata avviene semplicemente chiamando il delegate e passandogli gli argomenti C# //Invocazione int result = dlg(3,4); VB.NET ‘Invocazione Dim result As Integer = dlg(3,4) Se osserviamo la sintassi di invocazione al metodo, sembra che dlg sia una funzione a cui Utilizzando l’utility Ildasm.exe fornita dalla Microsoft col .NET Framework ed analizzando il codice compilato è possibile entrare un po’ negli internals del meccanismo di chiamata al metodo e quindi poter comprendere meglio cosa succeda dietro le quinte. Se disassembliamo con Ildasm il compilato dell’eseguibile dove i delegate sono stati compilati, troviamo che GetIntFromTwoInt e GetIntFromOneInt sono stati definiti dal compilatore come classi denominate rispettivamente GetIntFromTwoInt e GetIntFromOneInt e derivate dalla fondamentale classe System.MulticastDelegate del .NET Framework. Ci può aiutare in questo discorso dare uno sguardo alla Figura 1 che mostra la struttura della classe scritta nell’assembly. N. 66 - Novembre/Dicembre 2005 VBJ 11 PROGRAMMAZIONE AD EVENTI Figura 1 Delegati nel compilato IL Per il compilatore tutti i delegate (allo stato attuale del .NET Framework) derivano dalla classe System.MulticastDelegate ed in definitiva dalla classe System.Delegate. Soltanto i compilatori sono istruiti su come derivare dalla Delegate o dalla MulticastDelegate, mentre per i programmatori questo non è consentito. Dunque da codice non ci è possible derivare queste due classi. Come si vede dalla Figura 1 tutti i delegate presentano un costruttore con due parametri, un riferimento ad un oggetto ed un tipo intero che fa riferimento al metodo incapsulato. Questo costruttore riflette un po’ tutta la gerarchia che a partire dalla classe Delegate si sviluppa sino alla classe di delegato passando per la MulticastDelegate. Risalendo a ritroso la gerarchia, i valori passati ad argomento al costruttore del nostro delegate servono ad impostare due campi privati della classe Delegate; questi campi sono _target di tipo System.Object (un riferimento all’oggetto su cui si deve eseguire l’invocazione al metodo incapsulato) e _methodPtr di tipo System.Int32 (un intero con cui il CLR identifica univocamente il metodo da chiamare). Tramite le due proprietà Target e Method (ereditate dalla System.Delegate) la classe MulticastDelegate mette a disposizione dei programmatori la possibilità di leggere i valori dei campi _target e _methodPtr. Sempre dalla Figura 1 si nota come il compi- 12 VBJ N. 66 - Novembre/Dicembre 2005 latore abbia inoltre prodotto i tre metodi BeginInvoke, Invoke, EndInvoke nelle classi di delegate. La chiamata al metodo Invoke utilizza i campi privati _target e _methodPtr per richiamare il metodo incapsulato nel delegate sul particolare oggetto indicato. Pertanto la sintassi del metodo Invoke deve coincidere esattamente con quella indicata per il delegate. Gli altri due metodi BeginInvoke ed EndInvoke hanno invece un’utilizzo nella gestione callback del metodo incapsulato. Spesso ed erroneamente molti testi od articoli connotano subito il metodo incapsulato nel delegato come metodo callback (metodo di richiamata); ciò è vero se il metodo è utilizzato in tal senso tramite l’utilizzo di BeginInvoke ed EndInvoke. Ogni altro utilizzo del delegate produce soltanto un’invocazione (anonima) delegata a metodo di un altro oggetto. L’utilizzo callback del metodo incapsulato nel delegate vede l’invocazione a BeginInvoke che avvia la chiamata asincrona e ritorna immediatamente al chiamante senza attendere l’ultimazione della chiamata. L’esecuzione di EndInvoke viene eseguita per recuperare i risultati della chiamata asincrona e può avvenire in ogni momento dopo l’invocazione a BeginInvoke. Nel presente articolo non mi soffermo su questi aspetti di callback, a tale argomento sarebbe necessario dedicare un articolo ulteriore. Qui voglio solo evidenziare che tramite delega si può utilizzare callback, un metodo di classe, il cui tempo d’esecuzione potrebbe essere anche lungo e bloccare il thread che lo invoca. Chiamate con associazione tardiva Può inoltre essere utile, con alcune importanti osservazioni, far vedere come chiamare con associazione tardiva (late-bound) la fun- PROGRAMMAZIONE AD EVENTI zione incapsulata nel delegate, passandogli gli argomenti come un array di tipo object ulteriori approfondimenti il lettore può riferirsi all’ottimo manuale di Jeffrey Richter [1] indicato in bibliografia. C# .NET Events model //Invocazione con associazione tardiva object[] args = new object[2]{3,4}; int result = dlg.DynamicInvoke(args); VB.NET ‘Invocazione con associazione tardiva Dim args As Object() = New Object(1) {3, 4} Dim result As Integer = dlg.DynamicInvoke(args) Val la pena di osservare che durante il binding al metodo, nelle invocazioni con late binding, si può subire un certo overhead dovuto ai controlli sul metodo (es. numero dei parametri, tipo dei parametri, corretta assegnazione degli argomenti, …). Questo overhead si riduce se non si utilizza il late binding al metodo, ossia se invece si sfrutta l’early binding o binding anticipato al metodo incapsulato nel delegate. Approfondiamo questo aspetto. Il metodo Invoke prodotto dal compilatore, come già osservato in precedenza, dentro la classe compilata del delegate ha sintassi esattamente coincidente con quella del metodo target incapsulato nel delegate. A questo punto è noto anticipatamente (early binding) a tempo di compilazione se, passando direttamente tra parentesi gli argomenti attesi – subito dopo il nome del reference all’oggetto delegate – sia rispettata o meno la sintassi del metodo target. Utilizzando invece, nell’invocare il metodo target, il metodo DynamicInvoke a tempo di compilazione, non si ha nessun controllo sulla sintassi d’assegnazione degli argomenti. Infatti come si vede dal codice sopra questi vengono passati come semplice array di tipo object e l’assegnazione viene risolta a tempo d’esecuzione, ossia con late binding. L’argomento dei delegate, straordinariamente bello ed interessante, è lungi dall’essere ultimato. Qui non vado oltre e mi limito ora ad esporne sinteticamente l’applicazione nel modello ad eventi del .NET Framework. Per Il modello di eventi del .NET Framework si basa su un’idea molto semplice che chiama in causa i delegate. Ossia, il mittente (o sorgente) dell’evento non è a conoscenza di quale oggetto (ricevente o destinatario) riceverà e possibilmente gestirà la notifica prodotta. L’events model .NET prevede in tal senso di dover fornire un intermediario fra l’oggetto mittente e l’oggetto ricevente. Il ruolo d’intermediario viene recitato da un apposito delegate detto delegato d’evento .NET che per convenzione presenta una sua sintassi standard codificata nel Framework con due parametri, l’origine che ha generato l’evento e i dati trasmessi con la notifica dell’evento. Torneremo fra breve sull’importante punto della sintassi del delegato d’evento. Quindi il delegato d’evento si frappone come oggetto fra i due terminali di comunicazione e non gestisce direttamente la notifica, quindi non deve essere riguardato come ricevente. Il delegato si ricorda per la precisa notifica d’evento che riceve, quale oggetto e quale metodo di quest’ultimo gestirà la comunicazione ricevuta. Da codice è semplice stabilire il legame tra il mittente ed il delegato d’evento. Vediamo come. Il delegato d’evento viene dichiarato nella classe del mittente: C# //Dichiarazione del delegato d’evento public delegate EventNameEventHandler (object s,EventArgs e) //Dichiarazione dell’evento public event EventNameEventHandler EventName; VB.NET ‘Dichiarazione del delegato d’evento Public Delegate Sub EventNameEventHandler(ByVal s As Object, ByVal e As EventArgs) N. 66 - Novembre/Dicembre 2005 VBJ 13 PROGRAMMAZIONE AD EVENTI Figura 2 IL della classe sorgente d’evento ‘Dichiarazione dell’evento Public Event EventName As EventNameEventHandler Se, ad esempio, l’evento nella classe NotifyingClass ha nome StateChange con delegate StateChange-EventHandler, si avrà il seguente codice di dichiarazione dell’evento: C# //Dichiarazione del delegato d’evento public delegate StateChangeEventHandler (object s,EventArgs e) //Dichiarazione dell’evento public event StateChangeEventHandler StateChange; VB.NET ‘Dichiarazione del delegato d’evento Public Delegate Sub StateChangeEventHandler (ByVal s As Object, ByVal e As EventArgs) ‘Dichiarazione dell’evento re il codice necessario ad inoltrare la chiamata del mittente all’opportuno delegate quando avviene la notifica verso l’esterno. Sin qui però il delegate non conosce il particolare metodo che poi dovrà chiamare (handler) quando riceve la notifica. Questa informazione viene passata, ed era naturale aspettarselo, nel destinatario finale della notifica d’evento, il quale si registra per ricevere la notifica. È interessante approfondire come viene impostata tale informazione. Ricorriamo ancora una volta al “fedele” Ildasm.exe. Dalla Figura 2 osserviamo che disassemblando l’assembly della classe sorgente d’evento notiamo che l’evento StateChange, dal compilatore, è stato scisso in due funzioni pubbliche nascoste add_StateChange e remove_ StateChange. Lo scopo preciso di queste funzioni, che non possiamo invocare esplicitamente e direttamente, è quello di favorire al ricevente un modo di gestire il carico o lo scarico dell’eventuale handler d’evento. Ho scritto che noi non possiamo invocare esplicitamente e direttamente queste due funzioni. In realtà lo facciamo in una forma particolare che ora evidenzio. Nella classe del ricevente scriviamo: Public Event StateChange As StateChangeEventHandler C# Questo evento solleva una notifica di cambiamento di stato ogniqualvolta l’unico campo booleano valueState subisce la trasformazione da true a false (True a False) e viceversa. Dal codice scritto si legge esplicitamente il collegamento tra la sorgente d’evento ed il delegato che riceve notifica, infatti l’evento è dichiarato proprio come il delegato d’evento, a significare che quel delegate sarà informato che è successo qualcosa nel mittente. Questo è sufficiente al compilatore per scrive- 14 VBJ N. 66 - Novembre/Dicembre 2005 //Dichiarazione del reference al mittente MittenteTypeClass reference; //Valorizzazione del reference al mittente reference=new MittenteTypeClass([<args>]); //Dichiarazione del gestore l’evento reference.EventName+=new MittenteTypeClass. EventNameEventHandler( reference_EventName ); VB.NET ‘ Dichiarazione del reference al mittente PROGRAMMAZIONE AD EVENTI Dim reference As MittenteTypeClass ‘ Valorizzazione del reference al mittente Reference=New MittenteTypeClass([<args>]) ‘ Dichiarazione del gestore l’evento AddHandler reference.EventName, AddressOf mente chiamato argomenti d’evento è incapsulato dentro una classe derivata dalla classe System.EventArgs. La dichiarazione dunque di una classe d’argomenti d’evento da passare con la notifica è: reference_EventName C# Dunque, l’utilizzo della funzione add_EventName passa in C# per l’operatore +=, mentre in Visual Basic.NET passa per l’utilizzo della funzione AddHandler. In modo similare, l’utilizzo della funzione remove_EventName passa in C# per l’operatore -=, mentre in Visual Basic.NET passa per l’utilizzo della funzione RemoveHandler. L’implementazione dell’evento, nell’assembly, in due funzioni add_XXX e remove_XXX offre la possibilità al ricevente di gestire una propria lista interna, di tipo EventHandlerList, di handler per l’evento andando ad aggiungere od a rimuovere item dalla lista stessa a seconda dell’esigenza o meno di gestire particolari eventi, o di cambiare a runtime un particolare gestore d’evento a favore di un altro. Argomenti d’evento Concludo l’articolo indicando un’altra importante feature OOP degli eventi, spesso purtroppo sottovalutata dai programmatori: la possibilità di fornire e gestire classi di informazioni con l’evento. Se ben ricordiamo, precedentemente ho scritto che la sintassi del delegato d’evento è pressochè standard. La funzione nel ricevente che viene richiamata in gestione (handler) dell’evento notificato ha una sintassi canonica dove, oltre alla sorgente dell’evento, è presente un parametro all’interno del quale si riceve con la notifica anche un potenziale insieme di valori d’informazione, che possono risultare utili per la gestione dell’evento stesso. Va detto comunque che non è obbligatorio associare alla notifica un insieme di valori, ma in alcuni casi utilizzare informazioni addizionali potrebbe essere di estremo aiuto alla routine che gestisce l’evento. Questo insieme di valori che viene comune- //Classe d’argomenti d’evento public class EventNameEventArgs : System. EventArgs{…} VB.NET ‘ Classe d’argomenti d’evento Public Class EventNameEventArgs Inherits System.EventArgs ‘… End Class Nell’esempio utilizzato nell’articolo possiamo definire una classe d’argomenti d’evento StateChangeEventArgs che fornisce addizionalmente nella gestione d’evento il vecchio valore di stato ed il nuovo (Listato 1). Quando nel mittente viene sollevato l’evento, la classe StateChangeEventArgs viene istanziata e caricata delle informazioni da trasmettere al ricevente con la notifica: C# //Valorizzazione degli argomenti StateChangeEventArgs evArgs = new StateChangeEvent Args(oldValoreBooleano, newValoreBooleano); //Passaggio degli argomenti con la notifica d’evento this.StateChange(this,evArgs); VB.NET ‘ Valorizzazione degli argomenti Dim evArgs As StateChangeEventArgs = New StateChange EventArgs(oldValoreBooleano, newValoreBooleano) ‘Passaggio degli argomenti con la notifica d’evento Me.StateChange(Me,evArgs) Un’osservazione tratta dal .NET Framework La trasmissione di informazioni con la notifica d’evento ha applicazioni già dentro N. 66 - Novembre/Dicembre 2005 VBJ 15 PROGRAMMAZIONE AD EVENTI Listato 1 La classe StateChangeEventArgs che fornisce il vecchio e il nuovo valore di stato C# //Classe d’argomenti d’evento associata all’evento StateChange public class StateChangeEventArgs : EventArgs { /* * Vecchio e nuovo valore di stato */ bool oldStateValue; bool newStateValue; public StateChangeEventArgs(bool oldState,bool newState) { // // TODO: aggiungere qui la logica del costruttore // this.oldStateValue=oldState; this.newStateValue=newState; } /// <summary> /// Valore originale di stato della classe notificante /// </summary> public bool Original { get { return this.oldStateValue; } } /// <summary> /// Valore corrente di stato della classe notificante /// </summary> public bool Current { get { return this.newStateValue; } } } VB.NET ‘ Classe d’argomenti d’evento associata all’evento StateChange Public Class StateChangedEventArgs Inherits EventArgs ‘ Vecchio e nuovo valore di stato Dim oldStateValue As Boolean Dim newStateValue As Boolean Public Sub New(ByVal oldState As Boolean, ByVal newState As Boolean) Me.oldStateValue = oldState Me.newStateValue = newState End Sub ‘ <summary> ‘ Valore originale di stato della classe notificante ‘ </summary> Public ReadOnly Property Original() As Boolean Get Return Me.oldStateValue End Get End Property ‘ <summary> ‘ Valore corrente di stato della classe notificante ‘ </summary> Public ReadOnly Property Current() As Boolean Get Return Me.newStateValue End Get End Property End Class 16 VBJ N. 66 - Novembre/Dicembre 2005 il .NET Framework stesso. Si può, fra le altre, evidenziare la classe di connessione ad un database XxxConnection dell’infrastruttura ADO.NET. Questa espone pubblicamente sempre l’evento StateChange che viene notificato ai client ogniqualvolta è modificato lo stato della connessione fra i diversi valori forniti nell’enumerazione ConnectionState. Con la notifica dell’evento di cambiamento di stato della connessione viene passata un’istanza di classe d’argomenti d’evento di nome StateChangeEventArgs. Il vecchio ed il nuovo valore di connessione possono essere recuperati (ed utilizzati) dal gestore della notifica rispettivamente tramite le due proprietà pubbliche OriginalState e CurrentState. Tenendo presente quanto è stato scritto all’inizio dell’articolo, voglio qui ribadire l’estrema importanza di pensare gli eventi come notifiche di cambiamento di stato nella sorgente d’evento, e quindi di informare sul valore di vecchio e nuovo stato. A tale scopo le classi d’argomento d’evento giocano un ruolo da attori principali e non da semplici comparse, proprio perché naturalmente parte integrante della notifica prodotta. Ciò indipendentemente dal fatto che il ricevente gestisca o meno queste informazioni quando riceve la notifica. Conclusioni In questo articolo ho cercato di dare una sistematica sep- PROGRAMMAZIONE AD EVENTI pur breve e sintetica esposizione dei delegate e del modello di eventi di Microsoft.NET, fornendo osservazioni d’implementazione che ritengo utili per tutti i programmatori .NET Il fondamento di tutto il modello di eventi di Microsoft .NET, risiede nell’importante concetto di delegate Non ho inteso evidenziare automatismi reconditi della piattaforma.NET, bensì stimolare i progettisti e programmatori ad utilizzare gli eventi che spesso mancano nelle classi che progettiamo e realizziamo. Una classe deve poter comunicare all’esterno il proprio cambiamento di stato e questa avvenuta notifica deve poter essere gestita da chi la riceve. Classi “mute” seppur utili ed interessanti diventano di difficile gestione perché non si ha modo di sincronizzarsi con loro, in quanto non richiamano e non notificano nulla all’utilizzatore. In un prossimo articolo approfondiremo il confronto tra il modello ad eventi di.NET e di COM, con la speranza che ciò riesca utile a tutti quei progettisti e programmatori che in tempi recenti sono migrati dalla piattaforma COM a .NET. Bibliografia [1] Jeffrey Richter - “Microsoft .NET Programmazione avanzata”, Mondadori Informatica, 2002 Riferimenti [2] http://msdn.microsoft.com/library/default. asp?url=/library/en-us/csref/html/vcrefthedelegatetype.asp [3] http://msdn.microsoft.com/library/default. asp?url=/library/en-us/csref/html/vcwlkeventstutorial.asp TECNICHE Application e User Setting in Visual Basic 2003 di Francesco Balena U na delle novità principali di Visual Basic 2005 e C# 2.0 è il supporto per la persistenza delle impostazioni del programma, a livello di applicazione e a livello utente. Potete trovare maggiori informazioni su questa importante feature in [1], ma cercherò di riassumere il concetto in poche righe. Visual Studio 2005 fornisce un designer (vedi Figura 1) che permette di definire le impostazioni usate dal programma. Man mano che si definiscono nuove impostazioni, Visual Studio genera il codice della classe Settings (nel namespace My), che espone tali impostazioni sotto forma di proprietà, in modo che si possa poi accedervi da codice con una syntassi strong-typed, ad esempio: Dim filename As String = My.Settings.LastLoadedFile In un certo senso, la classe My.Settings assolve al compito che in passato era riservato alle variabili globali (tipicamente conservate in un modulo), con in più il vantaggio di poterle caricare e salvare su file. Le impostazioni possono essere definite a livello di applicazione (ad es. la stringa di connessione al database) oppure a livello di singolo utente (ad es. i colori preferiti o la lista degli ultimi file caricati). La differenza principale tra i due tipi di impostazioni è che le impostazioni a livello di applicazioni sono a sola lettura, mentre quelle a livello utente sono assegnabili. La classe My.Settings espone anche un metodo Save che salva il valore corrente del- 18 VBJ N. 66 - Novembre/Dicembre 2005 le impostazioni utente. Le impostazioni sono caricate al lancio del programma e possono essere anche salvate automaticamente al suo termine, semplicemente abilitando una opzione di progetto. Le impostazioni a livello utente sono conservate in un file nel folder C:\Documents and Settings\Username\Local Settings\Application Data (sono creati tanti file distinti, uno per ciascun utente) mentre le impostazioni a livello di applicazione sono conservate nel file di configurazione della applicazione. I limiti di My.Settings Il meccanismo di Visual Studio 2005 è decisamente potente ed elegante, ma non risolve affatto tutti i problemi che si possono verificare lavorando con le impostazioni di un programma, ad esempio: a) non è possibile memorizzare i valori su altri medium, ad esempio in un campo di database. USER TECNICHE Figura 1 Il designer di Visual Studio 2005 permette di definire le impostazioni usate dal programma b) non è possibile specificare se l’utente è locale oppure si tratta di un roaming user (in modo che le impostazioni utente siano ritrovate anche se l’utente esegue il login da un’altra macchina sulla LAN). c) a volte diventa importante modificare via codice anche le impostazioni a livello di applicazione, ma questo è assolutamente impossibile con la classe My.Settings, a meno di non scrivere un parser del testo XML del file .config. d) non esiste una proprietà che restituisce il nome del file contenente le impostazioni a livello utente, quindi ad es. non è semplice eseguire il backup delle impostazioni utente, oppure implementare un meccanismo di recovery dei valori com’era nelle precedenti sessioni o ancora migrare le impostazioni su altre macchine. Più in generale, dopo un po’ di frequentazione con la classe My.Settings ci si rende conto che spesso risulta insufficiente per i propri scopi. Ad esempio, spesso mi capita di lavorare con applicazioni data-centriche e vorrei creare dei file di “solution” (concettualmente simili ai file .sln di Visual Studio) che conservano informazioni sul gruppo di documenti aperti in un certo momento, e altre informazioni al contorno. Visto che però non è possibile caricare il contenuto delle impostazioni utente da un file specifico, per questo tipo di informazioni occorre un meccanismo differente da quello offerto da My.Settings. Insomma, alla fine in molti programmi occorre comunque implementare un meccanismo di persistenza delle variabili globali che è più raffinato di quello offerto da My.Settings, sia perchè serve avere maggiore controllo su dove le informazioni sono memorizzate (file, database, registry, ecc.) sia perchè è necessario poter lavorare con più istanze della classe che espone tali variabili (ad esempio per poter fare il merge di opzioni di provenienza differente). Per questi motivi, ed altri ancora, alla fine ho deciso di scrivere una serie di classi che N. 66 - Novembre/Dicembre 2005 VBJ 19 TECNICHE Listato 2 Le altre due classi UserSettingsBase e ApplicationSettingsBase ‘ ---------------------------------------‘ Base class for user settings ‘ ---------------------------------------Public MustInherit Class UserSettingsBase Inherits SettingsBase Sub New() Dim dirName As String = Environment.GetFolderPath( _ Environment.SpecialFolder.LocalApplicationData) Dim appName As String = [Assembly].GetExecutingAssembly().GetName().Name SetFilename(Path.Combine(dirName, Path.ChangeExtension(appName, DefaultExtension))) End Sub End Class ‘ ---------------------------------------‘ Base class for Application settings ‘ ---------------------------------------Public MustInherit Class ApplicationSettingsBase Inherits SettingsBase Sub New() Dim dirName As String = _ AppDomain.CurrentDomain.SetupInformation.ApplicationBase Dim appName As String = [Assembly].GetExecutingAssembly().GetName().Name SetFilename(Path.Combine(dirName, Path.ChangeExtension(appName, DefaultExtension))) End Sub End Class potessero risolvere una volta per tutte il problema della definizione e della persistenza delle impostazioni globali del programma, sia a livello utente che di applicazione. Il codice è scritto in VB 2003, quindi può essere usato con la precedente versione di Visual Basic, ma il risultato è così flessibile e potente che sto cominciando ad usarlo anche nei miei progetti VB 2005. In definitiva, quindi, lo scopo di questo articolo è mostrare un meccanismo generalizzato per gestire, salvare, e ricaricare un gruppo di valori di uno dei seguenti tipi: • • • 20 Impostazioni a livello di applicazione, condivise tra tutti gli utenti della applicazione Impostazioni a livello di utente, specifiche per un particolare utente Impostazioni a livello di progetto (o di soluzione), che possono essere salvate e ricaricate esplicitamente dall'operatore e serVBJ N. 66 - Novembre/Dicembre 2005 vono per individuare le proprietà dei documenti con cui l’utente sta lavorando in quel momento. Grazie alle classi base che introduco in questo articolo, il compito dello sviluppatore che vuole implementare un gruppo di valori si riduce a definire una classe che eredita da una classe base e aggiungere alla nuova classe uno o più campi pubblici (uno per ciascuna impostazione). Le classi implementate in questo modo esporranno quindi i vari metodi per salvare e ricaricare da file o da stream il gruppo di impostazioni e verificare se i valori sono stati modificati. Opzionalmente sarà anche possibile caricare automaticamente le impostazioni a livello di applicazione o utente quando parte l’applicazione e salvarli quando l’applicazione viene chiusa. Notare che il meccanismo implementato in questo articolo funziona solo con le applicazioni Windows Form, e non ASP.NET. TECNICHE se, quindi occorre copiare i valori dei campi e delle proPublic Class MySettings prietà dalla nuova Inherits UserSettingsBase istanza all’oggetto ‘ --------------------------------------------------“corrente” (ossia ‘ Instance fields and properties quello rappresen‘ --------------------------------------------------tato dalla keyword Public Name As String = “Francesco” Me). Ma la classe Public ID As Integer base non conosce Public RecentDocs() As String Public FormLocation As Point = New Point(0, 0) quali sono i campi Public FormSize As Size = New Size(100, 200) che verranno defi‘ These fields aren’t persisted niti nelle classi de<XmlIgnore()> Public FormTitle As String rivate. Per risolve<XmlIgnore()> Public TimeStarted As Date re questo problema ‘ --------------------------------------------------il codice usa reflec‘ Static members tion per iterare su ‘ --------------------------------------------------tutti i campi e le ‘ The Global instance (optional, but makes usage easier) proprietà pubbliPublic Shared Global As New MySettings che di istanza, evi‘ This static constructor is needed only to automatically load the Global tando però di co‘ instance the first time it is referenced by the application. If the piare gli elemen‘ application doesn’t use the Global instance or it loads ‘ it explicitly, you can omit this static constructor. ti marcati con l’atShared Sub New() tributi XmlIgnore, If File.Exists(Global.FileName) Then Global.Load() End Sub perchè questi eleEnd Class menti non devono essere salvati o ricaricati da file. La classe SettingsBase Reflection viene usata anche nel metodo HasChanges per confrontare l’istanza corrente La classe astratta SettingsBase funge da (Me) con l’oggetto salvato nella variabile priclasse base per la classe UserSettingsBavata OldSettings, in modo da poter determise (base per le impostazioni utente), la clasnare se anche solo una delle proprietà è stase ApplicationSettingsBase (base per le ta modificata ed è necessario quindi salvare impostazioni di applicazione), e le altre clasl’oggetto su file. si che volete usare per conservare un grupIl terzo aspetto interessante della classe Setpo di variabili che devono essere facilmente tingsBase è la proprietà AutoSave, che se imlette e scritte da file. postata a True permette di salvare automaIl codice sorgente della classe astratta Setticamente su file il contenuto della istanza tingsBase è lungo (Listato 1), ma il suo funquando l’applicazione termina (a condiziozionamento è abbastanza lineare. ne ovviamente che l’istanza sia stata caricaIl meccanismo di persistenza in XML delta in precedenza da file o almeno che la sua la classe è implementato mediante l’oggetto proprietà FileName sia stata assegnata corXmlSerializer, che viene usato nei vari overrettamente). load dei metodi Load e Save. Con il metodo Questa feature si basa sull’evento ProcesLoad, però, c’è un piccolo problema da risExit dell’oggetto AppDomain, che scatta apsolvere: il metodo Deserialize di XmlSerialipunto quando il processo corrente sta per terzer restituisce una nuova istanza della clasminare. Listato 3 Una classe che definisce alcune impostazioni a livello utente N. 66 - Novembre/Dicembre 2005 VBJ 21 TECNICHE Le classi UserSettingsBase e ApplicationSettingsBase Le altre due classi base sono decisamente più semplici. In pratica, esse derivano da SettingsBase e modificano soltanto il valore di partenza della proprietà FileName. Nel caso di UserSettingsBase la proprietà punta al file C:\Documents and Settings\Username\ Local Settings\Application Data\ Data\ApplicationName.xml, mentre nel caso di ApplicationSettingsBase la proprietà punta al file ApplicationName.xml nella stessa directory dell’applicazione. Dal sorgente della classe SettingsBase si può notare che le assegnazioni alla proprietà FileName provocano una eccezione se la classe è del tipo UserSettingsBase o ApplicationSettingsBase, quindi il costruttore di queste classi usa il metodo Protected Sub SetFileName per aggirare questo problema. Creare una classe di impostazioni utente A differenza della classe My.Settings di VB 2005, che contiene sia impostazioni utente che di applicazione, l’approccio che propongo in questo articolo richiede la definizione di due classi distinte, una per le impostazioni utente e una per le impostazioni di applicazione. Ovviamente, è possibile che uno specifico progetto richieda impostazioni di un solo tipo, quindi non è obbligatorio definire sempre due classi. Grazie alla potenza delle classi base preparate in precedenza, il sorgente di una classe per le impostazioni utente è davvero molto semplice. Ecco ad esempio una classe che definisce alcune impostazioni a livello utente (Listato 3). Per default tutti i campi e le proprietà pubbliche delle classi che derivano da UserSettingsBase sono scritte e lette da file. Accade spesso, d’altra parte, di voler definire della variabili globali Listato 4 Una classe che contiene tutte le impostazioni a livello di applicazione che non devono essere persistite tra una sessione e l’altra. Public Class MyAppSettings Per ottenere che Inherits ApplicationSettingsBase ciò non avvenga, è ‘ --------------------------------------------------sufficiente marca‘ Instance fields and properties re il campo o la pro‘ --------------------------------------------------prietà con l’attribuPublic DatabaseName As String = “SqlServer” to XmlIgnore (vedi Public MainServer As String = “MYSERVER” ad es. i campi FormTitle e TimeStar<XmlIgnore()>Public FormCount As Integer ted nell’esempio pre‘ --------------------------------------------------cedente.) ‘ Static members ‘ --------------------------------------------------La prima parte della classe MySettings ‘ The Global instance (optional, but makes usage easier) cambia da progetto a Public Shared Global As New MySettings progetto, mentre la ‘ This static constructor is needed only to automatically load the Global seconda parte (nel‘ instance the first time it is referenced by the application. If the la sezione “Static ‘ application doesn’t use the Global instance or it loads ‘ it explicitly, you can omit this static constructor. members”) è identiShared Sub New() ca per tutte le clasIf File.Exists(Global.FileName) Then Global.Load() End Sub si che derivano da End Class UserSettingsBase. Purtroppo, le rego- 22 VBJ N. 66 - Novembre/Dicembre 2005 TECNICHE le della ereditarietà non permettono di spostare questi membri statici nella classe base. Comunque, sia il campo Global che il costruttore statico sono opzionali, nel senso che la classe può funzionare anche se questi membri non sono definiti, come spiegano i commenti. La classe My.Settings espone anche un metodo Save che salva il valore corrente delle impostazioni utente re un campo readonly in una classe di questo tipo). MySettings.Global.FormSize = New Size(10, 200) Ovviamente, i campi e le proprietà readonly non sono salvate su file. Esistono due modi per effettuare il salvataggio delle impostazioni utente: esplicitamente mediante il metodo Save oppure implicitamente mediante la proprietà AutoSave. Nel secondo caso l’istanza di MySettings è salvata automaticamente (se vi sono state delle modifiche) al termine della applicazione: ‘ Salvataggio esplicito sul file di default MySettings.Global.Save() ‘ Attiva l’auto save MySettings.Global.AutoSave = True A questo punto potete definire nuove istanze di MySettings come fareste con qualsiasi altra classe, ma nella maggior parte dei casi sarà sufficiente usare l’istanza globale static rappresentata dal campo Global. Grazie al costruttore statico, non è neanche necessario caricare esplicitamente l’istanza Global alla partenza della applicazione, poiché tale caricamento avverrà automaticamente la prima volta che la classe MySettings è referenziata: Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load Me.Size = MySettings.Global.FormSize Me.Location = MySettings.Global.FormLocation lblMessage.Text = “Welcome, “ & La classe MySettings eredita da UserSettings due ulteriori metodi. Il metodo Refresh ripristina il contenuto delle variabili com’era al momento dal caricamento o salvataggio più recente, mentre il metodo HasChanges restituisce True se uno o più campi o proprietà sono state modificate dopo l’ultimo caricamento o salvataggio. In questo modo è possibile salvare i dati solo se strettamente necessario: If MySettings.Global.HasChanges Then MySettings.Global.Save() Creare una classe di impostazioni di applicazione MySettings.Global.Name ‘ Crea nuovi elementi per l’array RecentDocs ReDim MySettings.Global.RecentDocs(9) End Sub Tutti i campi e le proprietà della classe MySettings sono anche assegnabili, a meno che non siano marcati con la parola chiave ReadOnly (ma non ha molto senso inseri- Il meccanismo per creare una classe che contiene tutte le impostazioni a livello di applicazione è identico a quello visto per le impostazioni utente, con la sola differenza che la classe deve ereditare da ApplicationSettingsBase (anziché UserSettingsBase). Un esempio di classe di questo tipo è riportata nel Listato 4. Valgono per la classe MyAppSettings tut- N. 66 - Novembre/Dicembre 2005 VBJ 23 TECNICHE ti i commenti fatti per MySettings, inclusa la possibilità di marcare campi e proprietà con l’attributo XmlIgnore. La classe MyAppSettings si usa allo stesso modo: quanto visto in precedenza è che la classe deriva direttamente da SettingsBase e che non è necessario un costruttore statico: Public Class MyProjectSettings ‘ Leggi una proprietà Inherits SettingsBase lblServer.Text = “Connecting to “ & MyAppSettings.Global.DatabaseName Public Documents() As String Public StartupDocument As String A differenza delle impostazioni di applicazione di VB 2005, i campi e le proprietà di MyAppSettings possono essere assegnate e l’istanza intera può essere salvata sul file di default: ‘ altri campi e proprietà ‘ .... ‘ The Global instance (optional, but makes usage easier) Public Shared Global As New MyProjectSettings() ‘ Salvataggio esplicito sul file di default End Class MySettings.Global.Save() Ovviamente, tocca al programmatore il compito di accertarsi che un utente possa sovrascrivere le modifiche fatte da un altro utente. Nella pratica, questa feature dovrebbe essere abilitata soltanto per gli utenti con i privilegi di amministratore, che possono quindi influire sulle modalità con cui altri utenti usufruiscono della applicazione stessa. In confronto, l’approccio imposto da Visual Studio 2005 richiede che l’amministratore modifichi manualmente il file di configurazione, quindi direi che la mia tecnica è sicuramente più user friendly. Impostazioni di progetto o soluzione In questo caso non serve definire un costruttore statico, perchè la proprietà FileName non ha un valore di default iniziale, come accade per le impostazioni utente e di applicazione. Per questo motivo, una istanza di MyProjectSettings si carica con un overload del metodo Load che accetta un argomento (o in alternativa, con il metodo Load senza argomenti, ma solo dopo aver assegnato una valore non nullo alla proprietà Filename) ‘ Leggi una proprietà Dim startDoc As String = MyProject Settings.Global.StartupDocument ‘ Assegna una proprietà MyProjectSettings.Global.Documents(0) = In precedenza accennavo al fatto che spesso occorre salvare e ricaricare un gruppo di variabili che non sono legate nè alla applicazione nel suo complesso nè al singolo utente. Per immaginare come questo possa essere utile, basti pensare ai file .sln o .vbproj che crea Visual Studio per immagazzinare i dati di un progetto o soluzione VB.NET. Questi file di soluzione sono poi utilizzabili da più utenti, possono essere spostati facilmente su un’altra macchina, ecc. Definire una classe di questo tipo è davvero semplice. Le uniche due differenze rispetto a 24 VBJ N. 66 - Novembre/Dicembre 2005 “c:\mydoc.txt” ‘ Salva (1° metodo) MyProjectSettings.Global.Save (“c:\mysettings.xml”) ‘ Salva (2° metodo) MyProjectSettings.Global.FileName = “c:\mysettings.xml” MyProjectSettings.Global.Save() Anche in questo caso è possible impostare a True la proprietà AutoSave, in modo che il TECNICHE valore sia salvato automaticamente all’uscita della applicazione, anche se in genere il salvataggio delle impostazioni di progetto dovrebbe essere fatto soltanto dopo aver chiesto all’utente se le impostazini correnti devono essere effettivamente salvate. Alternative alla istanza Global Se si preferisce non usare l’istanza static Global occorre definire una variabile che sia visibile dall’intero progetto, ad esempio una variabile Public in un modulo oppure una variabile Shared in una classe o nel form principale. In questo caso è possibile eliminare il codice nella sezione “Static Members”, ma diventa necessario caricare esplicitamente la variabile globale in questione: Public Module MainModule ‘ L’istanza globale usata in tutta l’applicazione Public MySettings As New MySettings Sub Main() ‘ Caricamento esplicito MySettings.Load() tionSettingsBase è abbastanza evidente: esse forniscono tutta la flessibilità dell’oggetto My.Settings senza dover passare a .NET 2.0, e anzi otteniamo anche qualcosa in più, in particolare la possibilità di caricare e salvare le impostazioni da qualsiasi file o stream, e la possibilità di modificare anche le impostazioni di applicazione. Certo, Visual Studio 2005 permette di definire la classe Settings in modo visuale, usando l’apposito designer, però mi chiedo se davvero questo approccio visuale sia meglio che non scrivere una classe che contiene tutti i campi che ci interessano. Anzi, direi che preferisco di gran lunga l’uso di una classe scritta ad hoc, su cui ho certamente un controllo maggiore. Il meccanismo di persistenza in XML della classe è implementato mediante l’oggetto XmlSerializer MsgBox(“Welcome, “ & MySettings.Name) ‘ ...(continua) End Sub End Module Il vantaggio di questo modo alternativo di usare MySettings è che i campi e le proprietà si raggiungono con un solo “punto” anzichè due (ad es. MySettings.Name anzichè MySettings.Global.Name). Questo meccanismo vale ovviamente anche per le classi di impostazioni di applicazione (derivate da AppSettingsBase) e di soluzione (derivate direttamente da SettingsBase). Vantaggi e limiti Il vantaggio della tecnica basata sulle classi SettingsBase, UserSettingsBase e Applica- Il limite principale di questa versione della classe ApplicationSettingsBase è che le impostazioni sono salvate in un file XML distinto dal file di configurazione “ufficiale” della applicazione. Con un po’ di sforzo in più questo problema potrebbe essere risolto, anzi se lo risolvete mandatemi pure il vostro codice. Un altro limite, un po’ più serio, è che alcuni oggetti non possono essere serializzati in XML. In particolare, gli oggetti che non hanno un costruttore senza argomenti e che le cui proprietà sono a sola lettura hanno questo problema. Tra i tipi che soffrono di questo problema, i più comuni sono System.Drawing.Color e System.Drawing.Font: se provate a definire una N. 66 - Novembre/Dicembre 2005 VBJ 25 TECNICHE proprietà di questo tipo, nel file XML va a finire un elemento senza contenuto: ‘ Altri campi e proprietà ‘ ... End Class Public Class MySettings Inherits UserSettingsBase Public ForeColor As Color = Color.Blue Public Structure ColorXml Public A As Byte Public R As Byte End Class Public G As Byte Public B As Byte Un modo poco elegante ma efficace per risolvere questo problema è marcare l’elemento con l’attributo XmlIgnore e definire una proprietà pubblica che provvede alla serializzazione del valore in formato stringa. Ecco un esempio: <XmlIgnore()> _ Public Property Color() As Color Get Return Color.FromArgb(A, R, G, B) End Get Set(ByVal Value As Color) Public Class MySettings A = Value.A Inherits UserSettingsBase R = Value.R G = Value.G <XmlIgnore()> _ B = Value.B Public ForeColor As Color = Color.Blue End Set End Property Public Property ForeColorText() As String End Structure Get Return String.Format(“{0},{1},{2},{3}”, ForeColor.A, ForeColor.R, Il codice nella applicazione può usare la proprietà BackColor come segue: ForeColor.G, ForeColor.B) End Get Set(ByVal Value As String) MySettings.Global.BackColor.Color = New ColorXml(Color.White) Dim parts() As String = Value.Split(“,”c) ForeColor = Color.FromArgb(CInt(parts(0)), CInt(parts(1)), CInt(parts(2)), CInt(parts(3))) End Set End Property End Class Se la classe MySettings contiene numerose proprietà di un certo tipo non serializzabile, allora può convenire definire una classe accessoria il cui unico scopo è permettere la serializzazione del tipo in questione. Ecco un esempio: Public Class MySettings Il vantaggio di usare una struttura anzichè una classe per ColorXml è duplice: prima di tutto non occorre ricordare di usare l’operatore New quando si definisce un elemento di questo tipo nella classe MySettings. Secondo, e più importante, tutte le strutture ridefiniscono il metodo Equals per restituire True solo se tutti gli elementi di due strutture coincidono. In questo modo il metodo HasChanges definito in SettingsBase continua a funzionare bene anche se la classe MySettings contiene elementi di tipo struttura. Bibliografia Inherits UserSettingsBase Public BackColor As ColorXml 26 VBJ N. 66 - Novembre/Dicembre 2005 [1] http://msdn.microsoft.com/library/en-us/ dnvs05/html/vbmysettings.asp APPLICAZIONI Controllo Remoto in Visual Basic .NET Terza puntata di Stefano Corti I n questa terza parte del nostro progetto di Controllo Remoto in Visual Basic .NET ci occuperemo della progettazione ed implementazione del codice della classe RemoteProcesses. Mediante le proprietà e i metodi pubblici che definiremo, sarà possibile eseguire operazioni come creare nuove cartelle sulla macchina remota, avviare, controllare e monitorare i processi in esecuzione, stampare determinate tipologie di file utilizzando la stampante remota ed infine impartire comandi DOS mediante un’interfaccia del tutto simile al Prompt dei Comandi di Windows (cmd.exe). Si tratta quindi di una classe abbastanza complessa che, per ragioni che tra breve illustreremo, dovrà ereditare anche proprietà e metodi precedentemente definiti nella classe RemoteStructure, di cui abbiamo parlato nella precedente puntata. La nostra nuova classe prevede invece un semplice metodo costruttore, una proprietà pubblica, dieci metodi pubblici e due metodi privati. Incominciamo subito ad illustrare il primo metodo createNewDir(), che oltre a fornirci immediatamente l’occasione per un primo cenno sul meccanismo dell’ereditarietà, ci consente di creare una nuova Directory su Target. Osserviamo nel Listato 1 che il percorso completo della nuova Directory viene passato al metodo per valore mediante la stringa path. Sarà dunque necessario verificare se la Directory specificata non sia attualmente già presente sulla macchina Target. Creiamo una variabile di tipo Stefano Corti si occupa di programmazione PHP e JSP lato server, della piattaforma .NET e di integrazione di sistemi legacy con le nuove realtà del web, soprattutto in ambito gestionale, bancario e finanziario. È attualmente alle dipendenze di un primario gruppo bancario italiano. Può essere contattato via email: [email protected]. 28 VBJ N. 66 - Novembre/Dicembre 2005 boolean che chiameremo flagD e impostiamo tale variabile a True. Se il metodo statico Exists() restituisce True, significa che la Directory specificata è già presente nella macchina remota, altrimenti dovrà essere creata invocando il metodo statico createDirectory, passando come argomento il valore stringa della variabile path. Le righe di codice seguenti vengono eseguite solo a condizione che la variabile di tipo boolean flagD abbia assunto il valore True, ovvero soltanto nel caso che sia stata effettivamente creata la nuova nuova Directory. In tale condizione, provvediamo subito ad individuare la posizione dell’ultimo carattere \ (backslash) presente nella stringa path. Questo ci consente (mediante la funzione di Visual Basic Left) di ottenere una nuova stringa contenente un numero di caratteri specificato scorrendo la stringa a partire dall’estrema sinistra. In questo contesto, otteniamo quindi la Directory di livello superiore che conterrà la nostra nuova Directory appena creata, unitamente ad altre cartelle e file eventualmente presenti. Mediante la parola chiave MyBase, possiamo assegnare il valore stringa alla proprietà Pro- APPLICAZIONI file precedentemente definita all’interno della classe RemoteStructure che risulta essere, in definitiva, la classe “genitore” della classe RemoteProcesses, essendo le due classi legate attraverso l’istruzione Inherits, che consente alla classe o interfaccia corrente di ereditare gli attributi, i campi, le proprietà, i metodi e gli eventi da un’altra classe o interfaccia. Nello stesso modo invochiamo anche il metodo getFilesAndDirectories() per ottenere tutto il contenuto della Directory superiore. Ovviamente dobbiamo anche definire il codice preposto all’intercettazione del relativo comando proveniente dall’applicativo Controller, unitamente al parametro ad esso associato. Case Is = “MAKEDIR” writer.Write(objProc.createNewDir(job(1))) sentText.Clear() sentText.Text = “Creazione di Directory su Dischi Logici” Iniziamo ora a prendere confidenza con le classi del .NET Framework che ci consentono Imports System.IO di avviare, monitorare ed arrestare i processi e Imports System.Diagnostics le applicazioni in esecuzione. La classe Process espone un elevato numero di membri per conPublic Class RemoteProcesses trollare i processi in esecuzione sulle relative Inherits RemoteStructure macchine. Esamineremo poco per volta queste funzionalità, ricordando che tale classe appar... tiene allo spazio dei nomi System.Diagnostics Sub New() che dovrà quindi essere importato nel nostro Me.mProc = “IExplore” progetto, mediante l’istruzione Imports nelle End Sub prime linee di codice della classe. Passiamo subito ad un esempio Listato 1 Creazione di nuove cartelle su Target concreto. Poniamo il caso che nella scheda File Manager nella GUI Public Function createNewDir(ByVal path As String) As Controller venga selezionato nel String ListView remoto un determinato Dim dirReturn As String file e poi venga premuto il pulsanDim flagD As Boolean = True te ‘Avvia File Selezionato’. Questo If Directory.Exists(path) = True Then evento viene intercettato e gestidirReturn &= vbCrLf & “Attenzione: la Directory esiste già...” to dal codice presente nel ListaElse to 2. Al relativo comando STARTTry Directory.CreateDirectory(path) SELECTEDFILE viene associato dirReturn &= vbCrLf & “Directory creata con successo il parametro stringa contenente il su PC remoto.” Catch e As Exception percorso completo del file individirReturn &= vbCrLf & “Errore: “ & e.Message duato. Le procedure che consenflagD = False End Try tono di estrarre e trasformare in End If valore String un determinato elemento presente all’interno di un If flagD Then Dim found As Integer = 0 ListView, le analizzeremo in detfound = path.LastIndexOf(“\”) taglio quando ci occuperemo delMyBase.Profile = Left(path, found) & “\” dirReturn &= vbCrLf & “Contenuto attuale directory radice: “ & _ la progettazione delle interfacce MyBase.getFilesAndDirectories() utente dei programmi Controller e End If Target. Per ora ci basta notare che Return dirReturn al server viene inviato il comando definito, unitamente al suo paraEnd Function metro associato nelle consuete moImports System N. 66 - Novembre/Dicembre 2005 VBJ 29 APPLICAZIONI Listato 2 Codice associato alla pressione del pulsante Avvia Processo su Controller Private Sub startProcByFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles startProcByFile.Click Dim messGo As String = “STARTSELECTEDFILE>” Dim list_x As ListView.SelectedListViewItemCollection list_x = Me.ListView1.SelectedItems Dim i_list_x As ListViewItem For Each i_list_x In list_x messGo &= i_list_x.Text.ToString() Next If (messGo = “STARTSELECTEDFILE>”) Then MessageBox.Show(“Dovete prima selezionare un file da avviare.”, _ “Messaggio di Errore”, MessageBoxButtons.OK, MessageBoxIcon.Error) Else Try writer.Write(messGo) inMessages.Clear() inMessages.Text &= vbCrLf & “Comando Inviato: “ & vbCrLf & _ messGo Catch exception As SocketException inMessages.Text &= “Errore di comunicazione con il computer remoto: “ & _ exception.Message & vbCrLf Catch ex As Exception inMessages.Text &= “Errore: “ & ex.Message & vbCrLf End Try End If End Sub dalità. Vediamo adesso come il comando viene interpretato e gestito dal server Target. ... Private objProc As RemoteProcesses = New RemoteProcesses() ... Case Is = “STARTSELECTEDFILE” objProc.AppliFile = job(1) ... writer.Write(objProc.beginProc()) ... Abbiamo istanziato un oggetto (privato) dalla classe RemoteProcesses e lo abbiamo chiamato objProc. Nella struttura Select End Select, abbiamo previsto il caso che il comando ricevuto sia appunto STARTSELECTEDFILE. Alla proprietà AppliFile passiamo il valore dell’array Job(1) che contiene appunto il percorso completo del file da avviare. Non ci resta ora che invocare il metodo beginProc() per l’oggetto objProc che provocherà immediatamente l’avvio dell’applicazione associata a quella particolare estensio- 30 VBJ N. 66 - Novembre/Dicembre 2005 ne di file, determinando l’avvio del processo ad esso collegato. Il codice dei metodi pubblici beginProc() ed endProc() sono riportati nel Listato 3, mentre il codice di implementazione della proprietà Applifile è visibile nel Listato 4. Esaminiamo ora più in dettaglio queste porzioni di codice. Prima di tutto ci occorrono alcune variabili di classe private. Dichiariamo xPro e p come variabili di tipo Process unitamente alle variabili nProc, sProc ed mProc di tipo String. Dichiareremo poi altre variabili che ci serviranno per ulteriori funzionalità che vedremo in seguito. In particolare, per adesso, si presti attenzione alle variabili sParam di tipo String (impostata subito a Nothing) e alle variabili stringa mDelim, mDelemiter e all’array stringa pJob(). Private xPro, p As Process Private oRemoteAll As Process() Private oEnvi, oS As String Private oDrives As String Private oCurrentEnvironment As String Private nProcArray As String Private nProc As String APPLICAZIONI Private sProc As String Private mProc As String Private oArrow As String Private sParam As String = Nothing Private mDelim As String = “|” Private pJob As String() = Nothing Private mDelimiter As Char() = mDelim.ToCharArray() Private iterator As Integer stringa privata mProc. Successivamente viene verificata la presenza del carattere | all’interno della stringa. Il metodo IndexOf() restituisce il valore negativo -1 se nella stringa esaminata non viene individuato il carattere presente nell’argomento del metodo stesso. In questo caso è semplice dedurre che unitamente al nome dell’applicativo non è stato passato alcun parametro aggiuntivo oppure è stato specificato il percorso assoluto di un file. La variabile privata sProc, che ritroveremo nei metodi beginProc() ed endProc(), viene posta uguale a mProc. In caso contrario verrà determinato l’array pJob() che conterrà in indice 0 il nome del processo da avviare e in indice 1 il parametro specificato. Ovviamente il valore (stringa) di job(0) viene passato a sProc. Nel metodo beginProc(), all’interno di un blocco Try Catch End Try, viene verificato se il valore della variabile privata sParam è rimasto uguale a Nothing oppure ha subi- Nella GUI del Controller abbiamo infatti previsto diverse modalità di accesso ai processi remoti. Come abbiamo visto, il modo più semplice per avviare un processo consiste nel selezionare un determinato file nel file manager remoto e innescare quindi l’avvio dell’applicativo associato a quella particolare estensione di file. Abbiamo previsto inoltre un’altra scheda dove l’utente ha a sua disposizione una serie di campi di testo e controlli più completi. Tale Control Tab è visibile nella Figura 1. Come si può osservare, il Controller può avviare o terminare un’applicazione remota sempliceListato 3 Metodi pubblici beginProc ed endProc mente specificando il nome dell’applicazione ed inserendo un eventuale parametro. Un semplice esempio potrebbe esPublic Function beginProc() sere IExplore con parametro http://www. Try infomedia.it/. Questo provoca l’avvio imIf sParam Is Nothing Then xPro = Process.Start(sProc) mediato del browser Internet Explorer Return vbCrLf & “Processo avviato...” & _ su Target e il recupero della URL spevbCrLf & “Processo attualmente in corso.” cificata. Il parametro che il Controller Else invierà a Target sarà dunque, nel caso xPro = Process.Start(sProc, sParam) dell’esempio di cui sopra, strutturato in Return vbCrLf & “Applicazione avviata...” & vbCrLf & “Processo attualmente in corso.” questo modo: LAUNCH>IExplore|http:// www.infomedia.it/. In pratica, nel codiEnd If Catch e As Exception ce che gestisce l’evento associato al pulReturn vbCrLf & “Errore: “ & e.Message sante premuto, viene ricostruita la strinEnd Try ga per mezzo delle righe riportate nel LiEnd Function stato 5. Vediamo ora come tutto questo viene gestito a livello Target. Public Function endProc() Try Osserviamo ancora una volta il Listato xPro.CloseMainWindow() 4. Abbiamo visto che il parametro inviaxPro.Close() to deve essere ulteriormente splittato in Return vbCrLf & “Processo chiuso...” & _ vbCrLf & “Applicazione attualmente chiusa.” due parti per estrarre il nome del procesCatch e As Exception so da avviare e il relativo parametro. Le Return vbCrLf & “Errore Intercettato: “ & e.Message due porzioni sono divise da un carattere End Try separatore (| carattere pipe). Prima di tutEnd Function to vediamo che il valore passato alla proprietà viene memorizzato nella variabile _ N. 66 - Novembre/Dicembre 2005 VBJ 31 APPLICAZIONI Listato 4 Proprietà AppliFile della classe Remote Processes Public Property AppliFile() As String Get Return mProc End Get Set(ByVal Value As String) mProc = Value If (mProc.IndexOf(mDelim) = -1) Then sProc = mProc Else pJob = mProc.Split(mDelimiter, 2) sProc = pJob(0) sParam = pJob(1) End If End Set End Property alla corrispondente finestra principale e restituisce il valore Boolean True se il messaggio è stato inviato correttamente, False se il processo associato non dispone di una finestra principale o se la finestra principale è disattivata. Successivamente il metodo Close() libera tutte le risorse associate al componente. Vediamo ora come implementare un servizio completo che consente di impartire, in un’apposita scheda dell’interfaccia Controller del tutto simile al Prompt dei Comandi di Windows XP, qualsiasi comando DOS e fare in modo che questo comando venga eseguito su Target, ottenendo infine il relativo output. Con determinati metodi di alcune importanti classi del .NET framework, tutto questo può essere progettato e realizzato con estrema semplicità. Definiamo dunque il metodo pubblico promptInputOutput(), il cui codice è visibile nel Listato 6. Dobbiamo anzitutto creare un nuovo componente Process che, in questo caso, chiameremo myPdos. Successivamente occorre utilizzare la proprietà StartInfo che rappresenta l’insieme di parametri che servono per avviare un processo, impostando quindi le proprietà da passare al metodo Start() del componente Process. La seguente proprietà FileName è di fatto l’unico membro necessario di StartInfo, to cambiamenti. Nel primo caso, viene invocato il metodo Start() della classe Process, passando come argomento il valore di sProc, che conterrà il nome dell’applicazione da avviare oppure il percorso completo del file da eseguire. Si tratta di un metodo di overload che, se viene determinato un solo valore String, avvia una risorsa di processo, specificando il nome di un documento o un file di applicazione e associa la risorsa a un nuovo componente Process. Diversamente (vedi codice dopo la Else) il metodo accetta anche due parametri sempre di tipo String, innescando l’avvio di una risorsa di processo (in questo caso viene specificato il nome di un’applicazione e un insieme di argomenti della riga di comando) e associa la risorsa ad un nuovo componente Process. All’interno del metodo endProc() vengono invocati invece i metodi pubblici CloseMainWindow() e Close(). La chiamata di questi metodi è relativa a xPro, che abbiamo visto essere di tipo Process a cui è stata appena associata la risorsa corrispondente al processo precedentemente avviato. Più precisamente, CloseMainWindow() chiude un processo che dispone di un’interfaccia utente invian- Figura 1 Scheda contenente i controlli dei Processi nella GUI del Controller do un messaggio di chiusura 32 VBJ N. 66 - Novembre/Dicembre 2005 APPLICAZIONI Listato 5 Evento associato alla richiesta di avvio processo con parametro Private Sub startProcButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles startProcButton.Click If (txtStartProc.Text = “” Or txtStartProc.Text Is Nothing) Then MessageBox.Show(“Dovete specificare un processo oppure il percorso di un file da lanciare.”, _ “Messaggio di Errore”, MessageBoxButtons.OK, MessageBoxIcon.Error) Else Dim messGo As String If (txtStartParam.Text = “” Or txtStartParam.Text Is Nothing) Then messGo = “LAUNCH>” & txtStartProc.Text Else messGo = “LAUNCH>” & txtStartProc.Text & “|” & txtStartParam.Text End If Try writer.Write(messGo) inMessages.Clear() inMessages.Text &= vbCrLf & “Comando Inviato: “ & vbCrLf & _ messGo Catch exception As SocketException inMessages.Text &= “Errore di comunicazione con il computer remoto: “ & _ exception.Message & vbCrLf Catch ex As Exception inMessages.Text &= “Errore: “ & ex.Message & vbCrLf End Try End If End Sub laddove l’avvio di un processo mediante specifica della proprietà FileName è analogo alla digitazione delle informazioni nella finestra di dialogo Esegui del menu di avvio di Windows. In questo caso il nome del file da specificare non può che essere l’eseguibile .exe che attiva il prompt dei comandi, ovvero cmd.exe. Impostiamo quindi la proprietà CreateNoWindow su True, al fine di non determinare la comparsa della finestra GUI sulla macchina Target, quando invocheremo il metodo Start(). Le successive tre proprietà richiedono più attenzione. Quando un processo viene avviato, supponiamo un eseguibile come appunto cmd.exe, i canali di input predefiniti sono in genere la tastiera o eventualmente periferiche di puntamento quali il mouse, mentre il canale di output principale è quasi sempre lo schermo collegato alla macchina sulla quale il processo è in esecuzione. Per ottenere la remotizzazione di un processo è necessario variare o meglio reindirizzare i flussi di input/output verso il processo medesimo. Le proprietà RedirectStandardInput e RedirectStandardOutput si occupano proprio di questo specifico aspetto. In particolare, RedirectStandardInput imposta un valore che indica se l’input di comando del processo deve essere letto dal membro StandardInput dell’istanza di Process, consentendo in tal modo la lettura dei dati da un’origine diversa dal flusso di input standard. RedirectStandardOutput opera in modo del tutto analogo per i dati in uscita dal processo, provocando la scrittura del flusso su una destinazione differente dal flusso di output standard che abbiamo visto essere, nella quasi totalità dei casi, lo schermo del PC. Prima di potere utilizzare queste proprietà, è necessario impostare la proprietà UseShellExecute su True, poiché è indispensabile segnalare al CLR del .NET framework che non intendiamo utilizzare la shell del sistema operativo per avviare il processo, bensì intendiamo creare il processo direttamente dal file eseguibile. L’impostazione di questa proprietà su False consente di reindirizzare i flussi di input, output e di errore. Impostiamo quindi le proprietà RedirectStandardInput e RedirectStandardOutput su True. Ora che abbiamo ottenuto il reindirizzamento dei canali di I/O, dobbiamo innescare l’avvio del processo mediante la chiamata del metodo Start() e vei- N. 66 - Novembre/Dicembre 2005 VBJ 33 APPLICAZIONI scrivere e leggere dati dell’applicazione. Ricordiamo che queste proprietà ritornano valori di tipo StreamWriter e Public Function promptInputOutput(ByVal ooPP_dos As StreamReader, quindi dobbiamo anzitutString) As String Dim DOS_report_output As String = Nothing to creare una prima istanza della clasDim myPdos As New Process() se StreamWriter che chiameremo DOS_ myPdos.StartInfo.FileName = “cmd.exe” sss a cui verrà passato il valore (di tipo myPdos.StartInfo.CreateNoWindow = True StreamWriter) restituito dalla proprietà myPdos.StartInfo.UseShellExecute = False myPdos.StartInfo.RedirectStandardInput = True StandardInput dell’oggetto myPdos di myPdos.StartInfo.RedirectStandardOutput = True tipo Process. Le classi StreamWriter e myPdos.Start() StreamReader gestiscono l’input e l’ouDim DOS_sss As StreamWriter = myPdos.StandardInput tput di caratteri in una particolare codiDOS_sss.WriteLine(ooPP_dos) fica (in genere UTF8 Encoding, se non DOS_sss.Close() DOS_report_output = myPdos.StandardOutput.ReadToEnd() diversamente specificato) e differiscomyPdos.WaitForExit() no sostanzialmente dalle classi derivamyPdos.Close() te da Stream che gestiscono invece le Return “DOSPROMPTREPLY>” & DOS_report_output operazioni di I/O a livello di byte. Abbiamo così ottenuto l’oggetto DOS_sss End Function per il quale invochiamo il metodo WriteLine() (ereditato da TextWriter) che si colare i dati in transito. Il metodo che stiamo occupa della scrittura dei dati, come previsto realizzando accetta in ingresso valori di tipo dai parametri di overload, seguiti da un termiString passati per valore alla variabile strinnatore di riga. Il valore stringa passato al mega ooPP_dos. Chiaramente ooPP_dos assumetodo non è altro che il contenuto della variabirà il valore di job(1) il quanto il metodo verrà le ooPP_dos. Invochiamo quindi il metodo cloinvocato nella struttura Select Case EndSelect se() per determinare la chiusura del flusso. Ora all’interno del ciclo principale del server conche abbiamo inviato il comando al processo, tenuto nella classe Form1. dobbiamo immediatamente estrarne l’output opportunamente reindirizzato. Inizializziamo Case Is = “GETDOSPROMPT” una variabile stringa DOS_report_output e le Try assegniamo subito il valore restituito dal metosentText.Clear() do ReadToEnd() invocato per l’oggetto di tipo sentText.Text = “ESECUZIONE CMD.EXE” & StreamReader ritornato dalla proprietà StanvbCrLf & job(1) dardOutput di myPdos. Il metodo sovraccariwriter.Write(objProc.promptInputOutput(job(1))) co WaitForExit() che invochiamo subito dopo Catch exception As SocketException la chiamata di ReadToEnd() è molto importansentText.Clear() te in quanto indica al componente Process di sentText.Text = “Errore di Comunicazione: “ & attendere in modo indefinito la terminazione exception.Message & _ del processo associato, al fine di consentire al vbCrLf thread corrente di attendere che tale processo Catch ed As Exception venga terminato. Infine il metodo Close() libera sentText.Clear() la memoria allocata al processo terminato. Listato 6 Remotizzazione del Prompt dei Comandi DOS sentText.Text = “Errore: “ & ed.Message & vbCrLf End Try A questo punto dobbiamo utilizzare le proprietà StandardInput e StardardOutput che ottengono flussi utilizzati rispettivamente per 34 VBJ N. 66 - Novembre/Dicembre 2005 Conclusioni Nel prossimo appuntamento completeremo la trattazione di questa importante classe e realizzeremo un completo sistema di chat testuale tra Controller e Target. .NET Interprocess Communication con MSMQ e .NET Quasi tutte le applicazioni oggigiorno devono comunicare con l’esterno; vediamo un approccio alla comunicazione fra processi utilizzando la libreria MSMQ, che permette di scrivere applicazioni sicure e aperte ai futuri sviluppi delle tecnologie MSMQ di Carlo Pagliei E sistono molti modi per far comunicare fra loro due o più applicazioni: il modo classico è quello di definire un protocollo per lo scambio dei dati e un tipo di trasporto. Ad esempio potremmo utilizzare una socket TCP come trasporto e il protocollo HTTP o FTP, oppure una porta seriale con un protocollo proprietario creato ad-hoc. Esistono poi altre soluzioni che possono risultare valide in casi particolari: ad esempio l’uso di file condivisi o di messaggi di sistema come WM_COPYDATA. Tutti questi approcci sono ugualmente validi ma hanno un problema in comune: richiedono di concentrarsi sia sul modo in cui sono scambiati i dati, sia sul loro significato, costringendoci a spendere tempo e risorse su questioni “secondarie”: cosa succede se la connessione cade? come garantire la consegna dei messaggi? come fare per crittografare i messaggi o gestire permessi e utenti? A queste esigenze ha risposto Microsoft con una libreria che permette di ignorare tutti i problemi di “basso livello” e di concentrarci sul lavoro che più ci interessa: chattare e scambiarci informazioni. Carlo Pagliei è laureato in ingegneria elettronica, lavora con Etere srl dal 1996 e si occupa dello sviluppo di software per il mondo del broadcast (TV, Radio). I suoi interessi principali sono l’analisi e la progettazione di sistemi e l’applicazione pratica della programmazione object oriented. Nel tempo libero, bimbi permettendo, si dedica alla lettura e alle nuove tecnologie. 36 VBJ N. 66 - Novembre/Dicembre 2005 In questo articolo analizzeremo gli aspetti principali di Microsoft Message Queue (MSMQ), facendo in particolare riferimento alla libreria System.Messaging del .NET Framework, ne vedremo un’applicazione pratica e infine daremo un’occhiata ai piani che Microsoft ha in serbo per l’infrastruttura di comunicazione prevista su Windows Vista. Introduzione a Microsoft Message Queue MSMQ non è una tecnologia particolarmente nuova, esiste ormai da diversi anni ed è disponibile per quasi tutte le versioni di Windows: XP, 2000, NT4 (SP4), 95, 98, ME. Esistono comunque delle differenze nelle funzionalità supportate dai vari sistemi operativi; per semplificare, tutto quello che diremo in seguito si riferirà a Windows XP Professional. L’installazione è molto semplice: basta andare nel Pannel- .NET lo di Controllo, Installazione Applicazioni, Windows Components, selezionare Message Queuing e confermare. Appena terminato possiamo aprire Gestione Computer, e alla voce Servizi e Applicazioni troveremo anche Message Queuing: da questa console possiamo amministrare le varie code e dare un’occhiata alla loro organizzazione. La prima cosa che salta all’occhio è la presenza di tre tipi principali di code: di sistema, private e pubbliche. Le code di sistema sono utilizzate da MSMQ per scopi che in questo articolo non approfondiremo (ad esempio memorizzare messaggi non consegnati). Le code private sono accessibili solo sul PC dove risiedono a patto di conoscere l’intero percorso d’accesso. Le code pubbliche infine sono “replicate” su ogni PC e disponibili su tutta la rete attraverso la mediazione di Active Directory, ma non sono accessibili se non si è parte di un dominio. Proviamo a creare una coda ed apriamone la form delle proprietà: le varie impostazioni riguardano quelle relative alla sicurezza, agli utenti, ecc. Ecco il primo aspetto notevole della libreria: MSMQ utilizza le procedure di autenticazione di Windows per verificare il mittente (certificati digitali, Kerberos V5, NTLM), può criptare i messaggi in modo da renderli sicuri, può registrare tutte le operazioni effettuate su una certa coda. Tutto questo in maniera completamente trasparente allo sviluppatore: non dobbiamo preoccuparci direttamente di queste funzionalità, la libreria ce le fornisce “gratuitamente”, inoltre funzionano nello standard di Windows, perciò un qualsiasi amministratore sarà in grado di configurare il sistema egregiamente. Caratteristiche principali di MSMQ Se vogliamo spedire un messaggio di posta elettronica ci basta conoscere l’indirizzo del destinatario, senza preoccuparci se il ricevente sia effettivamente online: sarà il nostro motore di invio che si occuperà del lavoro di spedizione e consegna. MSMQ funziona esat- tamente con lo stesso principio: qualcuno (il ricevente) si connette ad una coda e attende l’arrivo di messaggi, qualcun altro (il mittente) può spedire messaggi a quella coda senza che sia fisicamente collegato ad essa. I due oggetti con cui abbiamo a che fare sono perciò le code ed i messaggi [1]: La coda è un semplice contenitore di messaggi, un messaggio a sua volta è un contenitore di dati con una serie di informazioni associate che indicano come trattarli: priorità, sicurezza, ricevute, temporizzazioni. Chiunque può leggere (o spedire) messaggi da una coda a cui ha accesso, anche in multiutenza. MSMQ è disponibile per quasi tutte le versioni di Windows: XP, 2000, NT4, 95, 98, ME MSMQ prevede due tipi di utenti: dipendenti e indipendenti. Un utente viene definito dipendente quando ha necessità di un collegamento stabile e sempre attivo con la coda, per cui se questa non è disponibile non è in grado di funzionare correttamente. Gli utenti indipendenti invece non necessitano di tale collegamento e comunicano in modalità disconnessa: inviano messaggi che saranno poi effettivamente consegnati quando la connessione alla rete sarà disponibile. Quest’ultima è la modalità predefinita ed è chiaramente quella per noi più interessante. Accenniamo infine a due caratteristiche di MSMQ che è difficile trovare in altre librerie: il supporto alle transazioni e i trigger. Grazie al supporto alle transazioni possiamo associare N messaggi fra loro collegati in una singola transazione: MSMQ garantisce che i messaggi verranno consegnati una sola volta nell’ordine stabilito e prelevati tutti dalla coda di destinazione, e che se anche uno solo N. 66 - Novembre/Dicembre 2005 VBJ 37 .NET dei passi causerà un errore, tutta la transazione verrà cancellata. Ultima caratteristica interessante sono i trigger: è possibile specificare una serie di azioni (trigger) che MSMQ esegue quando su una coda si verificano determinate condizioni (rules). Ad esempio potremmo impostare un trigger che, quando nella coda X arriva un messaggio che contiene una stringa predefinita, lancia un eseguibile, un file batch, uno script o un oggetto COM. Con questa tecnica è ad esempio possibile realizzare dei server che vengono lanciati semplicemente inviando un messaggio su una coda, senza che nessuna applicazione sia in esecuzione. La libreria System.Messaging Il .NET Framework, fra le sue tante librerie, ne ha una che corrisponde al namespace System.Messaging. Questo namespace contiene una serie di classi che servono a creare, amministrare e monitorare code, a inviare e ricevere messaggi: l’attuale implementazione della libreria si appoggia proprio su MSMQ [2]. MSMQ supporta funzionalità avanzate come la gestione degli utenti, le transazioni e i trigger La classe più importante è MessageQueue, che espone metodi per inviare e ricevere in modalità sia sincrona che asincrona, controllare la presenza di messaggi, ricercare code secondo vari criteri. L’altra classe fondamentale è Message, che contiene il messaggio vero e proprio insieme a svariati parametri di controllo: Timeout, Acknowledgements, Authentication. I dati da inviare vengono inseriti nella proprietà Body: al suo interno può essere inserita qualsiasi 38 VBJ N. 66 - Novembre/Dicembre 2005 cosa, da una semplice stringa ad un oggetto serializzato. Da queste poche righe si intuisce subito la potenza dell’accoppiata MSMQ + .NET Framework: con poche righe di codice, senza preoccuparci praticamente di nulla, possiamo prendere un oggetto complesso quanto si vuole, serializzarlo, impacchettarlo in un messaggio e spedirlo a qualcun altro senza neanche la necessità di controllare che il destinatario sia collegato. Ci basterà attendere la risposta o una ricevuta di MSMQ che ci informerà di eventuali problemi. Una semplice applicazione pratica: MSMQ-Chat Cominciamo finalmente a scrivere qualche riga di codice per vedere come funzionano le cose in pratica. Realizziamo una semplice applicazione dimostrativa, tipo Messenger, che serve a scambiare messaggi con un amico o collega: abbiamo una finestra che visualizza gli utenti registrati (code), ne scegliamo uno, ci colleghiamo con l’utente, e iniziamo a chattare. Problema 1: come fare per avere la lista degli utenti? Ipotizziamo che ad ogni utente corrisponda una diversa coda ed utilizziamo il metodo statico MessageQueue.GetPublicQueues() che ci fornisce la lista delle code pubbliche registrate. Se non siamo in un dominio possiamo usare GetPrivateQueuesByMachine() per ottenere le code private di un particolare PC. Ottenuta la lista riempiamo un TreeView per visualizzarla. Problema 2: come gestire lo scambio di messaggi? Utilizziamo due classi: IpcQueue e IpcMessage. IpcQueue farà per noi il lavoro del postino trasmettendo un oggetto IpcMessage che, oltre al messaggio vero e proprio, conterrà l’indirizzo del mittente e altre informazioni supplementari. Per quanto riguarda la classe IpcQueue abbiamo bisogno di poche cose: due metodi per avviare e chiudere la connessione con la coda, StartListening() e StopListening(), un metodo che ci consente di inviare un messaggio ad una coda qualsiasi, SendTo(), e un even- .NET to che ci avverte dell’arrivo di un messaggio. Internamente la classe utilizza due oggetti MessageQueue, uno per la ricezione ed uno per l’invio. Nel costruttore IpcQueue() su entrambe le code va inizializzato il formatter che codificherà il messaggio, specificando esplicitamente la classe IpcMessage da serializzare. Sulla classe “ricevente” va anche impostato l’evento che informerà dell’arrivo di un nuovo messaggio. Il frammento di codice che segue descrive le operazioni da effettuare: private IAsyncResult _listening; ... private void _receiveCompleted(object sender, System.Messaging.ReceiveCompletedEventArgs e) { try { Message m = _rxQueue.EndReceive(e.AsyncResult); IpcMessage msg = new IpcMessage(); msg = (IpcMessage)m.Body; /* codice che gestisce l’evento */ ... } _rxQueue = new MessageQueue(); finally { Type[] typArray = new Type[]{typeof(IpcMessage)}; _listening = _rxQueue.BeginReceive(); _rxQueue.Formatter = new XmlMessageFormatter } (typArray); _rxQueue.ReceiveCompleted += return; } new ReceiveCompletedEventHandler (this._receiveCompleted); Vediamo ora brevemente come fare per inviare un messaggio: basta creare l’oggetto IpcMessage, inizializzarlo e passarlo al metodo Send() che internamente ed in maniera trasparente provvederà a formattarlo come XML e inviarlo al destinatario: public void SendTo(string address, string aSubject, string aMessage){ IpcMessage msg = new IpcMessage(); msg.SenderPath = _rxQueue.PathName; msg.Subject = aSubject; msg.Body = aMessage; Non c’è molto da commentare: il codice è semplice e abbastanza intuitivo anche se non si ha una approfondita conoscenza di System. Messaging. Quello che si nota è l’estrema semplicità e pulizia del codice che nasconde quasi tutti i dettagli; l’unica scelta da fare è quella del serializzatore, che naturalmente deve essere lo stesso per il mittente ed il destinatario. L’applicazione completa è disponibile sul sito Ftp di Infomedia, inoltre su [3] potete trovare una trattazione di MSMQ e System.Messaging discretamente approfondita e con svariate applicazioni di esempio. Il futuro: Windows Communication Foundation _txQueue.Path = address; _txQueue.Send(msg); } Resta infine da vedere come implementare la ricezione; utilizziamo quella asincrona, altrimenti l’applicazione sarebbe bloccata fino alla ricezione di un messaggio. Il codice è molto semplice; appena arriva un messaggio viene attivato l’evento _receiveCompleted(), che internamente provvede a deserializzare il messaggio, ad eseguire su di esso le operazioni richieste ed infine a riavviare la ricezione: Chi decide di appoggiarsi al .NET Framework per scrivere applicazioni lo fa per diversi motivi, uno dei quali è sicuramente il fatto che Microsoft sta spingendo su di esso in maniera molto forte. Il prossimo anno dovrebbe essere molto importante dal punto di vista dello sviluppatore, a fine 2006 è previsto infatti il rilascio della prima versione ufficiale di Windows Vista: il sistema operativo, ora allo stadio di beta 1, che prenderà il posto di XP. Insieme a Windows Vista verrà rilasciato anche WinFX: una serie di librerie di alto livello (Foundations), basate sul .NET framework, N. 66 - Novembre/Dicembre 2005 VBJ 39 .NET che costituiranno il nuovo modello di programmazione Windows. L’SDK ed il runtime di WinFX saranno disponibili, sempre a fine 2006, anche per Windows XP e Windows Server 2003. In questo scenario di cambiamenti un posto in prima fila se l’è guadagnato Windows Communication Foundation (nome in codice Indigo): la nuova infrastruttura di comunicazione di Windows. Si tratta di un insieme di tecnologie, costruite attorno ai Web Service, che sono la naturale evoluzione delle tecnologie attualmente disponibili: COM, COM+, MSMQ, ASMX, ecc. Tutte le funzionalità di Indigo sono basate sul .NET Framework, esposte come managed API e sono estensioni delle attuali librerie di Remoting, Web Service, Messaging ed Enterprise Services. Su [6] si può leggere un buon articolo che introduce le idee alla base di Indigo, spiega i nuovi concetti di servizi e contratti e infine descrive la libreria System.ServiceModel. Attraverso l’uso di codice dimostrativo scende nel dettaglio abbastanza da capire come viene progettata e costruita una nuova applicazione e come interagisce col mondo che la circonda. Attualmente WinFX è in versione beta 1, quindi potrebbero ancora esserci dei cambiamenti di un certo rilievo, ma comunque chi scrive oggi dovrebbe essere in grado di utilizzare quelle nuove cambiando poco o nulla. Siamo pian piano arrivati alla parte interessante che dovrebbe far ingolosire qualsiasi sviluppatore: Microsoft afferma infatti che tutti i sistemi basati su Indigo saranno in grado di comunicare con sistemi che supportano MSMQ e viceversa [5], perciò chi oggi sceglie di appoggiarsi alla libreria System. Messaging si trova nella condizione ideale di poter scrivere codice multipiattaforma in grado di supportare sia sistemi legacy basati sulle API MSMQ (COM), sia sistemi futuri basati su Indigo. Chi fosse interessato ad approfondire l’argomento, sul Windows Vista Developer Center [4] può trovare informazioni preliminari, FAQ, approfondimenti, libri, white paper, blog e quant’altro. Si possono infine scaricare sia Windows Vista in versio- 40 VBJ N. 66 - Novembre/Dicembre 2005 ne beta 1 che i vari SDK di tutte le Foundation anche per Windows XP. Conclusioni Abbiamo mostrato come sia possibile scrivere applicazioni che comunicano in maniera semplice ed affidabile appoggiandosi a MSMQ utilizzando, in maniera trasparente e senza il diretto intervento dello sviluppatore, sia tutte le caratteristiche di sicurezza che offre Windows (gestione degli utenti, crittografia, autenticazioni, ecc.) sia caratteristiche avanzate quali il supporto alle transazioni, che sarebbero di difficile implementazione utilizzando ad esempio una socket TCP. Abbiamo anche scoperto che utilizzando MSMQ attraverso la libreria System.Messaging del .NET Framework siamo in grado di supportare sia sistemi legacy basati sulle API MSMQ, sia i futuri sistemi basati su Indigo. Bibliografia [1] MSDN - “Message Queuing (MSMQ)”, http://msdn.microsoft.com/library/default. asp?url=/library/en-us/msmq/msmq_overview_4ilh.asp [2] MSDN – “System.Messaging namespace”, http://msdn.microsoft.com/library/default. asp?url=/library/en-us/cpref/html/frlrfsystemmessaging.asp [3] Adrian Turtschi et al. – “C# Book, Cap. 7: Message Queuing using MSMQ”, Syngress, 2002 [4] Microsoft – “Windows Vista Developer Center”, http://msdn.microsoft.com/windowsvista/ [5] Microsoft - “Indigo Frequently Asked Questions”, http://msdn.microsoft.com/windowsvista/support/faq/communication/ [6] Clemens Vasters – “Introduction to Building Windows Communication Foundation Services”, http://msdn.microsoft.com/webservices/indigo/default.aspx?pull=/library/ en-us/dnlong/html/introtowcf.asp#introt_ topic1 .NET La GAC in salsa XML Convertiamo la GAC in un file XML per recuperare velocemente le informazioni su assembly, versioni e namespace di Filippo Bonanni N ell’articolo sul motore di scripting in .NET presente nel numero 64 abbiamo lasciato in sospeso la questione di referenziare assembly di terze parti registrati nella GAC. Più in generale possiamo porci questa domanda: esiste un modo per scoprire, partendo da un namespace, l’assembly in cui è contenuto? La risposta che mi era venuta in mente consisteva nell’utilizzare la Reflection (vedi [1]) per ispezionare gli assembly alla ricerca del namespace in questione, ma questa non è certo un’operazione comoda da eseguire frequentemente; se però “sincronizziamo” la GAC con un file, magari XML, nel quale esportare l’elenco degli assembly con le informazioni sulle versioni e sui namespace in essi contenuti, diventa tutto più accessibile. In questo articolo vedremo come realizzare un “GACInspector”, un componente che possa essere utilizzato per questo scopo: generare un file XML riproduzione fedele della GAC. La GAC in breve La Global Assembly Cache non è altro che una directory, chiamata GAC, di solito contenuta nel percorso %windir%\assembly. Per ogni assembly registrato, viene creata una directory sotto tale percorso chiamata con lo stes- GAC so nome dell’assembly e quindi un’ulteriore directory dal nome di tipo Version__PublicKeyToken per ogni versione registrata. Ad esempio il percorso dell’assembly System.Data.dll, potrebbe apparire così Directory di C:\WINDOWS\assembly\ GAC\System.Data 17/03/2005 11.35 <DIR> . 17/03/2005 11.35 <DIR> .. 17/03/2005 11.35 <DIR> 1.0.5000.0__b77a5c561934e089 0 File 0 byte 3 Directory 17.287.491.584 byte disponibili Il file è contenuto nella cartella 1.0.5000.0__b77a5c561934e089 e se ci fossero state più versioni avremmo trovato altre cartelle analoghe a questa. In ogni cartella è presente anche un file __AssemblyInfo__.ini riportante un riepilogo delle informazioni sull’assembly: [AssemblyInfo] Signature=a3ba424f0b663038e86ae83 Filippo Bonanni si occupa di informatica dal 1998 ed al momento i suoi interessi sono rivolti principalmente all’ambiente .NET. Attualmente lavora nel team di sviluppo della D&T Informatica di Sesto Fiorentino in progetti web-based di interfacciamento a sistemi AS/400 e di archiviazione ottica in ASP.NET. 42 VBJ N. 66 - Novembre/Dicembre 2005 f72ffe2a5c2722466 MVID=c5596d3e16daa840b86afe4a5bf c9e06 URL=file:///C:/WINDOWS/Microsoft.NET/ Framework/v1.1.4322/System.Data.dll .NET DisplayName=System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 name=“calcr.dll” > <ver Signature è la firma digitale ottenuta da una combinazione dell’hash del file e della chiave privata (facente parte della coppia Private/Public Key utilizzate per l’assegnazione dello Strong Name), MVID (Module Version ID) è un GUID generato durante la compilazione, URL indica la posizione dell’assembly ed infine DisplayName fornisce informazioni sullo Strong Name. Ecco la rappresentazione dello stesso assembly nel file XML: id=“5.0.0.0__a1690a5ea44bab32” DisplayName=“CalcR” Version=“5.0.0.0” Culture=“neutral” PublicKeyToken=“a1690a5ea44bab32” > <namespaces> <name>Samples.Math.Calculator</name> </namespaces> </ver> <ver <assembly id=“6.0.0.0__a1690a5ea44bab32” folder=“system.data” DisplayName=“CalcR” name=“System.Data.dll” Version=“6.0.0.0” > Culture=“neutral” PublicKeyToken=“a1690a5ea44bab32” <ver id=“1.0.5000.0__b77a5c561934e089” DisplayName=“System.Data” Version=“1.0.5000.0” Culture=“neutral” PublicKeyToken=“b77a5c561934e089” > <namespaces> <name>Samples.Math.Calculator</name> </namespaces> </ver> </assembly> > <namespaces> <name>System</name> <name>System.ComponentModel</name> <name>System.Data</name> <name>System.Data.Common</name> <name>System.Data.Odbc</name> <name>System.Data.OleDb</name> <name>System.Data.SqlClient</name> <name>System.Data.SqlTypes</name> Con una struttura del genere, per risalire dal namespace all’assembly che lo contiene è sufficiente una semplice query XPath (vedi [2]) che restituirà uno o più nodi, a seconda che esistano una o più versioni o che il namespace sia ripetuto in più assembly. L’algoritmo di sincronizzazione <name>System.Xml</name> </namespaces> </ver> </assembly> Come si può vedere, la struttura replica le informazioni contenute nella GAC ed in più offre un elenco di tutti i namespace presenti nel file. Se invece il file ispezionato avesse avuto più versioni, il risultato sarebbe stato come il seguente esempio: <assembly folder=“calcr” In Figura 1 è riportato il flusso decisionale del nostro componente: se l’assembly (nella figura “Asm”), non è più in GAC viene rimosso dal file XML, se invece c’è e non è ancora stato censito viene aggiunto; lo stesso procedimento è eseguito per le versioni, aggiungendo le nuove e rimuovendo quelle non più presenti. A questo punto, per ogni nuova versione trovata si procede con l’ispezione dell’assembly relativo, alla ricerca di tutti i namespace in esso contenuti che vengono successivamente aggiunti nel documento XML. N. 66 - Novembre/Dicembre 2005 VBJ 43 .NET Struttura del programma VerifyAssembly() end set Prima di iniziare l’ispezione dell’assembly occorre fornire al programma alcune informazioni basilari: il percorso della GAC (che non dimentichiamoci può essere modificato), il percorso completo del file XML di output ed infine il nome dell’assembly da analizzare end Property Public Sub New(GACPath as String, OutputFile as String) If GACPath Is Nothing Then GACPath=String. Empty If OutputFile Is Nothing Then OutputFile= String.Empty public Property AssemblyName as String get _GACPath=GACPath.Trim() If _GACPath=String.Empty Then Throw New Exception Return _AssemblyName (“Specifica il percorso della GAC”) end get If Not(_GACPath.EndsWith(“\”)) Then _GACPath+=“\” set _AssemblyName=Value.Trim() _filename=OutputFile.Trim() If _AssemblyName=String.Empty Then If _filename=String.Empty Then Throw New Exception __AssemblyName=Nothing (“Specifica un nome di file di output valido”) assemblyFolder=_AssemblyName.Substring(0, __AssemblyName.LastIndexOf(“.”)) assemblyFolder=assemblyFolder.toLower() Me.LoadXml() End Sub completeAssemblyPath=_GACPath & assemblyFolder If Not(completeAssemblyPath.EndsWith(“\”)) Then completeAssemblyPath+=“\” Figura 1 Il flusso del GACInspector Il percorso della GAC e il file di output sono dichiarati come parametri da passare al costruttore poiché rimarranno invariati durante il ciclo di vita del componente mentre il nome dell’assembly è una proprietà che potrà essere aggiornata per passare all’elemento successivo registrato in GAC. Nel costruttore richiamiamo il metodo LoadXml() che crea il file XML nel caso non esista ancora e lo carica in un XmlDocument (vedi [1]); nella proprietà AssemblyName recuperiamo il nome della cartella contenente l’assembly (che come abbiamo detto è il nome del file stesso senza il suffisso .dll o .exe) e, con il metodo VerifyAssembly(), verifichiamo la sua esistenza nella GAC. Da notare che partendo dall’analisi della GAC, quello che avremo immediatamente a disposizione sarà il nome della directory e non quello del file, quindi dotiamo il componente di un ulteriore metodo che permetta di fare il percorso inverso: Public Function GetAssemblyName(assemblyFolder as String) as String Dim Result as String 44 VBJ N. 66 - Novembre/Dicembre 2005 .NET assemblyFolder=_GACPath & assemblyFolder Result=Directory.GetFiles(Directory.GetDirectories (assemblyFolder)(0))(0) ziamo un oggetto XmlNode per la creazione del nodo e un oggetto XmlAttribute per l’inserimento degli attributi: Result=Result.SubString(Result.LastIndexOf (“\”)+1) Return Result If findNode Is Nothing Then newNode=doc.CreateNode(XmlNodeType.Element, End Function “assembly”, ““) Da una cartella di tipo %GACPATH%\assemblyFolder recuperiamo dalla prima sottodirectory (una delle tante possibili versioni dell’assembly) il primo file (poiché i file saranno solo due e __AssemblyInfo__ .ini sarà sempre al secondo posto per via del suo nome, il primo sarà sempre quello voluto) per ottenere il nome da utilizzare nella proprietà. attrib=doc.CreateNode(XmlNodeType.Attribute, Rimozione di assembly non più in GAC doc.DocumentElement.AppendChild(newNode) “folder”,”“) attrib.value=assemblyFolder newNode.Attributes.Append(attrib) attrib=doc.CreateNode(XmlNodeType.Attribute, “name”,”“) attrib.value=_AssemblyName newNode.Attributes.Append(attrib) doc.save(_filename) Se la verifica dell’esistenza dell’assembly in GAC ha dato esito negativo può essere dovuto al fatto che tutte le versioni sono state rimosse e quindi occorre rimuovere tutte le referenze anche dal file XML: Private Sub RemoveAssembly() Dim findNode as XmlNode=doc.SelectSingleNode(“/GAC/ assembly[@folder=’“ & assemblyFolder & “‘]”) If Not(findNode Is Nothing) Then doc.DocumentElement.RemoveChild(findNode) End If Ricerca di nuove versioni Scendiamo di un livello ed incominciamo ora l’analisi delle versioni dell’assembly. Per verificare la presenza di nuove versioni memorizziamo in un Array di stringhe tutte le possibili directory di versione (quelle di tipo Version_ _PublicKeyToken) presenti in GAC mediante il metodo Directory.GetDirectories(): doc.save(_filename) End If Private Function GetAssemblyVersions(AbsolutePath End Sub as boolean) as String() Dim Result() as String Con una query XPath proviamo a recuperare il nodo <GAC><assembly> con attributo folder uguale alla directory non più presente in GAC e se lo troviamo procediamo alla sua eliminazione. Aggiunta di nuovi assembly Dim i as integer Result=Directory.GetDirectories(complete AssemblyPath) If Not(AbsolutePath) Then For i=0 to Result.Length-1 Result(i)=Result(i).Replace(completeAssembly Path,”“) Next L’aggiunta prevede, come per la rimozione, una ricerca dell’assembly all’interno del file XML; stavolta però, se l’elemento non viene trovato, si procede alla creazione del nodo con l’assegnazione degli attributi; per farlo utiliz- End If Return Result End Function La variabile booleana AbsolutePath permet- N. 66 - Novembre/Dicembre 2005 VBJ 45 .NET te di decidere se memorizzare il percorso assoluto o relativo all’interno dell’Array. Fatto ciò, confrontiamo le versioni presenti in GAC con quelle del file XML sempre tramite una query XPath, stavolta però a partire dal nodo <assembly> (passato come parametro a valore nella variabile Node), memorizzando in un’ArrayList tutte quelle nuove: Next doc.save(_filename) End Sub Le versioni presenti nel file XML sono memorizzate in un XmlNodeList estraendo tutti i nodi-figlio <ver> dall’elemento <assembly> fornito come parametro alla funzione. Aggiunta di nuove versioni Private Sub FindNewVersion(ByRef alVer as Array List, ByVal Node as XmlNode) Le nuove versioni sono tutte quelle presenti nell’ArrayList generata dal metodo FindNewVersion(), quindi per ogni suo elemento dobbiamo creare un nodo <ver> con i relativi attributi id, DisplayName, Version, Culture e PublicKeyToken. Dim arrTemp() as String Dim i as Integer Dim findNode as XmlNode arrTemp=GetAssemblyVersions(True) For i=0 to arrTemp.Length-1 findNode=Node.SelectSingleNode(“ver[@id=’“ & arrTemp(i).SubString(arrTemp(i).LastIndexOf (“\”)+1) & “‘]”) If findNode Is Nothing Then alVer.Add (arrTemp(i)) Next End Sub Esiste un modo per scoprire, partendo da un namespace, l’assembly in cui è contenuto Eliminazione di Versioni Con un confronto inverso – tramite il metodo Array.IndexOf() che consente di ricercare un oggetto all’interno di una matrice di oggetti, cioè partendo dalle versioni presenti nel file XML e verificandone l’esistenza nella GAC, eliminiamo tutti i nodi non più necessari: Mentre il primo è esattamente il nome della cartella di versione, gli altri attributi devono essere recuperati dal file __AssemblyInfo__ .ini che leggiamo attraverso uno StreamReader (vedi [1]) recuperando il rigo che inizia per “DisplayName” e dividendolo successivamente in una matrice: Private Sub ClearRemovedVersion(ByVal Node as XmlNode) Private Sub AddNewVersion(alVer as ArrayList, Dim arrTemp() as String Node as XmlNode) Dim i as Integer [Dichiarazione delle variabili] Dim verNodeList as XmlNodeList For i=0 to alVer.Count-1 Esci=False arrTemp=GetAssemblyVersions(False) sr=New StreamReader(alVer(i).ToString & “\” verNodeList=Node.SelectNodes(“ver”) & iniFile) Do For i=0 to verNodeList.Count-1 If Array.IndexOf(arrTemp,verNodeList.Item(i). tempString=sr.ReadLine If tempString.StartsWith(“DisplayName”) Attributes.ItemOf(“id”).Value)=-1 Then Node.RemoveChild(verNodeList.Item(i)) 46 VBJ N. 66 - Novembre/Dicembre 2005 Then Esci=True Loop Until Esci or sr.Peek=-1 .NET Una volta recuperati tutti i namespace li aggiungiamo sotto l’elemento <ver> completando l’estrazione delle informazioni sull’assembly. sr.Close() arrTemp=tempString.Split(“,”) [Creazione del nodo <ver>] For j=0 to arrTemp.Length-1 [Creazione degli attributi] Conclusioni Next [Chiamata alla funzione che recupera i namespace] [Aggiunta del nodo <ver> sotto il nodo <assembly>] Next doc.save(_filename) End Sub Ispezione dell’assembly in cerca dei namespace A questo punto ci resta soltanto da ispezionare l’assembly alla ricerca dei namespace in esso contenuti e per farlo ricorriamo alla indispensabile Reflection. Dopo aver caricato il file mediante la classe Assembly (vedi [1]) basta semplicemente estrarre in una matrice tutti i tipi contenuti in esso e scorrerli alla ricerca dei namespace: Dim a as [Assembly] Dim arrT() as Type Dim alNamespace as New ArrayList() a=[Assembly].LoadFile(verPath & _AssemblyName) arrT=a.GetTypes For i=0 to arrT.Length – 1 If Not(arrT(i).NameSpace Is Nothing) And (alNamespace.IndexOf(arrT(i).NameSpace)=-1) Then alNamespace.Add(arrT(i).NameSpace) Next Il metodo Assembly.GetTypes() ci permette di recuperare tutti i tipi e utilizzando nel ciclo il confronto con la proprietà Type.Namespace, che restituisce il namespace del Tipo, individuiamo le informazioni volute. Da notare che in un assembly ci possono tranquillamente essere molti Tipi appartenenti allo stesso namespace, quindi per evitare di recuperare il medesimo nome più volte verifichiamo con il metodo ArrayList.IndexOf che l’elemento non sia già presente nell’ArrayList. Affinché il file XML possa essere di una qualche utilità, occorre che sia sempre allineato con la GAC, quindi è necessario utilizzare il componente descritto all’interno di un programma che la monitorizzi costantemente. Nel codice allegato a questo articolo è disponibile un file d’esempio “GACWatcher.vbns” (eseguibile con il motore di scripting per .NET descritto nel numero precedente) che tramite un oggetto FileSystemWatcher (vedi [1]) controlla costantemente la GAC passando al GACInspector il nome della cartella modificata da ispezionare mediante due metodi, OnChanged() e OnDeleted(), aggiunti proprio per questo scopo; la soluzione più idonea sarebbe quella di utilizzare questo codice per la realizzazione di un servizio che parta all’accensione del computer e resti quindi sempre attivo in background. Il file d’esempio “BuildGACFile.vbns” invece, permette di creare il file XML con la situazione attuale della GAC. Per utilizzare il file XML all’interno del “motore di scripting per .NET” è sufficiente aggiungere una funzione che, partendo dal nome dell’assembly, con una query XPath recuperi il nodo contenente l’assembly stesso e quindi ricostruisca il percorso completo da utilizzare per referenziare il file al momento della compilazione. Il nome dell’assembly potrebbe essere fornito come elemento del tag <references> con intestazione <gac> per distinguerlo da quelli <prv>. Riferimenti [1] .NET Framework SDK – http://msdn.microsoft.com/netframework/support/documentation [2] XPath Tutorial – http://www.w3schools. com/XPath N. 66 - Novembre/Dicembre 2005 VBJ 47 SOFTWARE ENGINEERING Model Driven Architecture Model Driven Architecture (MDA) è il termine con cui OMG identifica il suo tentativo di creare una architettura per lo sviluppo di applicazioni indipendenti dalla piattaforma di Lorenzo Vandoni C on il nome di Model Driven Architecture (MDA) viene indicato quello che, ultimamente, è diventato uno degli argomenti maggiormente dibattuti tra i progettisti legati ai metodi object-oriented. La proposta arriva dall’Object Management Group (OMG), e l’idea è quella di separare completamente la logica applicativa dalla piattaforma sulla quale l’applicazione dovrà essere implementata. Il modello dell’applicazione viene sviluppato utilizzando linguaggi di alto livello come UML, CWM (Common Warehouse Metamodel) e MOF (MetaObject Facility), e viene quindi mappato su diverse piattaforme, come CORBA, COM, Java o .NET. Il beneficio di MDA è costituito dal fatto che il modello applicativo rimane costante e può essere utilizzato anche per sfruttare le successive evoluzioni della tecnologia sottostante, o nuove piattaforme applicative non ancora esistenti nel momento in cui il programma è stato sviluppato. La conversione dal modello generico, indipendente dalla piattaforma, ad un modello più specifico, legato a una particolare tecnologia, viene aiutata da linee guida specifiche, e può essere automatizzata almeno parzialmente da strumenti CASE. Lorenzo Vandoni è laureato in Informatica, ed è uno specialista di progettazione e sviluppo con tecniche e linguaggi objectoriented. Attualmente è direttore del Laboratorio di Ricerca e Sviluppo di Emisfera e coordina un progetto di ricerca sulle Reti Wireless finanziato nell’ambito del Sesto Programma Quadro (FP6) della Comunità Europea. Può essere contattato tramite e-mail all’indirizzo [email protected] 48 VBJ N. 66 - Novembre/Dicembre 2005 OMG e MDA OMG è un’organizzazione che si occupa principalmente di standard nel mondo dell’analisi, della progettazione e della programmazione object-oriented (OOA, OOD, OOP). Uno degli scopi di OMG è quello di creare meccanismi che permettano di applicare le medesime tecniche di sviluppo, indipendentemente dal linguaggio di programmazione e dal sistema operativo utilizzato. Questi sforzi hanno portato, negli anni ’90, alla definizione di CORBA, uno standard per l’implementazione e l’utilizzo di oggetti distribuiti, multilinguaggio e multipiattaforma. CORBA ha avuto un buon successo, soprattutto in ambiente Unix, ed è tuttora molto utilizzato, anche se l’attenzione degli sviluppatori, soprattutto in ambiente Windows, è stata principalmente rivolta su meccanismi alternativi, come COM, DCOM e, ultimamente, i Web Service. Con MDA, OMG cerca di andare un passo al di là di CORBA, suggerendo un metodo di lavoro SOFTWARE ENGINEERING che si renda indipendentente da qualunque dettaglio implementativo. Applicando MDA, diviene teoricamente possibile definire il comportamento dell’applicazione in modo del tutto astratto, delegando poi la conversione degli schemi in codice a meccanismi di traduzione più o meno automatizzati. L’architettura di MDA Nella definizione di MDA, OMG ha cercato di considerare in modo pragmatico lo stato dell’arte nell’ambito di OOA, OOD e OOP. Per quanto riguarda analisi e progettazione, ovviamente, il linguaggio di riferimento è ormai da anni UML. A questo linguaggio, però, Figura 1 L’architettura di MDA OMG ha affiancato i meno noti CWM (Common Warehouse Metamodel), un linguaggio per la creare un modello UML dell’applicazione, inmodellazione di database e data warehouse, dipendente da ogni specifica piattaforma di MOF (Meta Object Facility), per la gestione implementazione, basandolo su un determidegli oggetti all’interno di repository, e XMI nato core model. Un core model, o UML profi(XML Metadata Interface), come formato di le, è una specie di meta-modello, che descriscambio dei modelli. ve le principali caratteristiche comuni ad una In pratica, questo significa che i modelli podeterminata classe di applicazioni, come ad tranno essere costruiti con UML, scambiati esempio i programmi gestionali. con XMI, e memorizzati in repository MOF. Come si può vedere dalla Figura 1, UML, MOF e CWM sono gli elementi centrali dell’architettura di MDA, e consentono di mascherare le differenze fra i vari “middleware” indicati nell’anello più esterno, e cioè Java, .NET, Web Service e CORBA. Tutti questi sistemi, anche se in modi diversi, forniscono un insieme di servizi che vengono utilizzati dalle applicazioni, tra cui funzionalità di sicurezza, transazioni e servizi di directory. In teoria, questo compito sarà riservato agli Lavorare con MDA analisti “puri”, mentre il passo successivo, costituito dalla conversione del modello geCostruire un’applicazione conforme all’arnerico in un modello specializzato che facchitettura di MDA significa, come prima cosa, cia riferimento ad una specifica piattaforma L’ultimo passo è costituito dalla generazione del codice applicativo N. 66 - Novembre/Dicembre 2005 VBJ 49 SOFTWARE ENGINEERING di implementazione, come Java o .NET, sarà ad appannaggio di personale che conosca i dettagli della piattaforma prescelta. Questo passaggio sarà però guidato da alcune regole di conversione, e quindi potrà essere almeno in parte automatizzato da parte di strumenti CASE. Questo primo processo di conversione potrà produrre, oltre ad un modello UML specializzato, anche del codice o altri elementi che potranno servire direttamente all’implementazione, come ad esempio dichiarazioni di interfacce o schemi di tabelle relazionali. Il modello UML specializzato potrà contenere elementi “dialettali” specifici della piattaforma di implementazione prescelta, per meglio identificare come un determinato aspetto dell’applicazione dovrà essere implementato con gli strumenti messi a disposizione. Avremo quindi, a seconda dei casi, elementi che rappresenteranno un Web Service, un componente EJB, o una stored procedure. L’ultimo passo è costituito dalla generazione del codice applicativo a partire dal modello specializzato. Questo processo viene facilitato dalla disponibilità degli elementi “dialettali” appena citati, che potranno essere facilmente messi in corrispondenza con parti di codice. L’obiettivo di quest’ultima conversione è quello di ottenere in modo automatico una percentuale significativa del codice applicativo effettivamente richiesto, in modo da ridurre il lavoro da effettuare, la probabilità di commettere errori, e quindi il costo dello sviluppo. Modellare i domini L’idea di creare dei meta-modelli di un particolare dominio applicativo non è nuova, ed in OMG era stata finora tradotta in pratica tramite la definizione di interfacce IDL che permettessero di creare servizi CORBA standardizzati per ogni specifico settore applicativo. Questa esperienza, che non ha avuto una grande risonanza, è servita però a genera- 50 VBJ N. 66 - Novembre/Dicembre 2005 re una profonda conoscenza di alcuni domini applicativi, che con MDA è stata messa in pratica creando dei modelli UML che traducono questa conoscenza in qualcosa di più facilmente condivisibile. Per ognuno di questi domini applicativi è stato creato un modello UML astratto, indipendente dalla piattaforma, ed un modello UML specifico, che serva anche come esempio di traduzione. Sfruttare i servizi delle piattaforme Ognuna delle piattaforme target considerate per l’implementazione offre una serie di servizi, tra cui quelli più comuni sono la gestione delle transazioni, della persistenza e della sicurezza. Ognuno di questi servizi viene definito in MDA in modo indipendente dalla piattaforma, in modo che possa essere referenziato nel modello generico dell’applicazione. L’idea è quella di separare completamente la logica applicativa dalla piattaforma sulla quale l’applicazione dovrà essere implementata In questo modello, cioè, si potrà specificare di volere utilizzare in modo generico una transazione. Saranno poi le regole di mapping specifiche per ogni piattaforma che tradurranno questa generica richiesta in un modello più specifico, che sfrutterà l’implementazione del servizio disponibile sulla piattaforma prescelta. Anche le applicazioni già sviluppate con tecnologie precedenti (il cosiddetto codice “legacy”) possono essere incorporate in MDA tramite uno strato di codice esterno, in modo da SOFTWARE ENGINEERING permettere, da una parte, di integrarle con le nuove applicazioni sviluppate e, dall’altra, di migliorare il vecchio codice sfruttando i servizi resi disponibili dalle nuove piattaforme e modellati con MDA. Potenziali benefici di MDA I potenziali benefici che questo approccio porta con sé sono molti, e principalmente legati alla netta separazione che viene imposta tra logica applicativa e tecniche di implementazione. Anzitutto, MDA risulta essere un’architettura del tutto aperta ed estendibile, anche in vista di possibili future evoluzioni tecnologiche. Ogni nuovo linguaggio, sistema operativo o piattaforma, è infatti potenzialmente incapsulabile in questa architettura, che promette il porting delle applicazioni tra piattaforme diverse, con uno sforzo di implementazione minimo. Ci saranno poi benefici a livello di normalizzazione e standardizzazione del codice, che favoriranno la possibilità da parte di più persone di intervenire sulla stessa applicazione. La stesura dei core models, inoltre, potrà favorire una migliore comprensione di vari domini applicativi, trasformando conoscenze più o meno vaghe in modelli condivisibili. Conclusioni L’idea alla base di MDA è sicuramente interessante, anche se non originalissima. Molti sono stati infatti i tentativi di portare la definizione delle applicazioni ad un livello di astrazione più alto, in modo da svincolarsi dai pericoli e dai costi della codifica. È interessante notare però, tra le motivazioni citate, il fatto che la presenza di molti diversi linguaggi e piattaforme di sviluppo viene ormai vista come un dato di fatto non superabile. L’obiettivo non è più, quindi, quello di trovare un linguaggio migliore degli altri, che possa imporsi come standard, quanto quello di trovare un modo per svincolare l’applicazione dal linguaggio sviluppato. Non si può negare che questo sia un problema reale. Molti di noi hanno vissuto nella propria carriera transizioni più o meno traumatiche da Dos a Windows, da 16 a 32 bit, da VB6 a .NET, per non parlare dei “veri” porting tra linguaggi e sistemi operativi diversi. A molti, più volte, è capitato di dover riscrivere la stessa applicazione. Il modello dell’applicazione viene sviluppato utilizzando linguaggi di alto livello come UML Forse MDA non sarà la soluzione definitiva per questi problemi, però il fatto di essere basata su standard e, soprattutto, la maggiore sensibilità emersa negli ultimi anni relativamente alla necessità di aderire ad essi, danno a questo approccio concrete possibilità di realizzazione. Va anche notato, d’altra parte, che MDA va quasi in direzione opposta rispetto ad altri movimenti emergenti, quelli delle metodologie agili. Da una parte abbiamo un’attenzione quasi ossessiva per le fasi di analisi, dall’altra un atteggiamento più pragmatico, e una maggiore attenzione per gli aspetti umanistici del proprio lavoro, con particolare enfasi per il ruolo dei programmatori. Chi avrà ragione? Sarà politicamente troppo corretto, ma forse potrebbero averla entrambi gli approcci, in quanto MDA è sicuramente più adatto per i grossi progetti, mentre le metodologie agili possono essere vincenti per applicazioni medio-piccole. Non è escluso, poi, che non si possano cogliere spunti da entrambi gli approcci. Per eventuali approfondimenti vi rimando al sito ufficiale, ovvero www.omg.org/mda. N. 66 - Novembre/Dicembre 2005 VBJ 51 REPORTAGE Il nuovo umanesimo tecnologico a cura di Alberto Rosotti L’ing. Umberto Paolucci, Senior Chairman di Microsoft Europa, Medio Oriente e Africa e Vice President Microsoft Corporation, è stato ospite dei Giovani Imprenditori di Assindustria nel corso di un dibattito dal tema “Il nuovo umanesimo tecnologico” tenuto il 16 settembre presso Assindustria di Pesaro. Dal suo osservatorio privilegiato Paolucci ha illustrato l’attuale scenario della globalizzazione, i rapporti umani, la comunicazione, la formazione e la ricerca. L’impatto della globalizzazione e delle moderne tecnologie è fonte di grandi cambiamenti che devono essere vissuti dalle imprese consapevolmente. L’incontro di questi due fattori incide ed inciderà in maniera crescente sulla vita dell’uomo, dal momento che l’uomo è il fulcro dell’impresa competitiva. Ciò che ha più colpito dell’uomo Paolucci è stata la sua umiltà, dote ormai rara nei manager di successo. Invitato a parlare, Paolucci ha rotto il ghiaccio esordendo: “Per essere credibile davanti ad un pubblico di giovani devo essere come loro, quindi permettetemi di togliere giacca e cravatta. Il mio amico Bill si mette sempre comodo prima di parlare ai giovani, la platea che preferisce”. Un incipit semplice, seguito da una “lezione” che ha catturato gli astanti. Ciò che segue è la trascrizione integrale, fatta eccezione per minimi adattamenti giornalistici, di un discorso appassionante durato più di un’ora e ricco di ottimismo; un esempio illuminante ricco di spunti sui quali riflettere. Il tema che ci vede qui riuniti è entusiasmante: vi parlerò innanzitutto del nuovo mondo, per il quale sto lavorando da 35 anni, poi parlerò dell’Europa e dell’Italia che mi sta a cuore in modo particolare e per terminare parlerò dell’uomo, che è la cosa in cui dobbiamo credere di più. Ma andiamo per ordine. Il nuovo mondo Alberto Rosotti è consigliere dell’Ordine degli Ingegneri di PesaroUrbino e dirigente sistemi informativi presso il gruppo di mobilifici FBL - Della Rovere. 52 VBJ N. 66 - Novembre/Dicembre 2005 Cominciamo dal software. Sono convinto che oggi, dopo tanti anni d’evoluzione, siamo arrivati ad un punto in cui si possono fare cose inimmaginabili, un punto in cui la complessità si riesce a delegare al sof- REPORTAGE tware e non alle persone. Questo è l’obiettivo che abbiamo e l’obiettivo della mia azienda in questi anni è stato quello di dare maggiore semplicità alle cose, di dare la possibilità ad un numero sempre più ampio di persone di usare questi strumenti, con un modello di business basato su volumi alti e prezzi bassi: tanti computer che costano poco, tanta gente che li può avere. Ovviamente affinchè ciò si avveri è necessario che i computer siano ragionevolmente facili da usare; di fatto il potenziale di trasformazione che i computer hanno è diventato così forte che si è creata una situazione di divisione tra coloro che possono avvalersene - che sanno avvalersene - e gli altri, sia a livello di aziende che a livello di persone. Si è creato quindi un fossato; noi dobbiamo cercare di colmare questo fossato, soprattutto per coloro che, senza volerlo, stanno dalla parte sbagliata. Quello che si è creato è un nuovo mondo che in alcuni casi ci è sfuggito di mano; per esempio la bolla della new economy ha proiettato entusiasmi eccessivi sul fatto ci fossero tanti nuovi modelli di business strani, che poi non c’erano. Pensavamo che si potesse avere un enorme incremento della quantità di traffico telefonico, e l’effetto lento di questa “illusione” è stato la creazione di un’infrastruttura ancora poco utilizzata, una banda larga di comunicazione sulla quale si basa il nostro modo di gestire e anche di subire oggi la globalizzazione. La globalizzazione si è sviluppata prepotentemente grazie al fatto che esistono le reti informatiche, ed esistono per tutti. Noi abbiamo creato delle porte di ingresso importantissime, larghissime, realizzate in un periodo di ottimismo eccessivo, che oggi possono e devono essere date a coloro che non ne hanno la disponibilità. Questo è il risvolto positivo legato al “disincanto” della bolla di internet. Quindi abbiamo un mondo globale nel quale, grazie alla tecnologia, si possono fare cose come lavorare insieme, indipendentemente da dove siamo, come dare valore all’informa- zione, al lavoratore, alla conoscenza, e possiamo farlo con professionalità che prima non esistevano; si sono creati degli stili di vita nuovi, degli stili di lavoro inimmaginabili fino a poco tempo fa. Avete visto, soprattutto voi giovani, la convergenza tra i diversi “oggetti” nella loro trasformazione al digitale (come fotografia, video, musica); questi oggetti si ritrovano a lavorare su piattaforme simili, si ritrovano a lavorare con modalità tali che non è più un obiettivo irraggiungibile pensare di metterli insieme nelle case e nelle aziende. Questo definisce delle modalità nuove con le quali viviamo la nostra giornata di persone, modalità nuove con le quali viviamo la nostra giornata di lavoratori. Noi possiamo stabilire dove vogliamo abitare, dato che è ormai semplice avere una forza lavoro globale sparsa sul pianeta, di fatto sempre connessa alla rete. Grazie alla larga banda condividiamo il nostro lavoro senza limiti geografici. Vi do qualche dato. Le email si sono mediamente moltiplicate per dieci dal 1997, ed è previsto che si moltiplichino ancora per cinque nel giro di tre, quattro anni. C’è una spinta su questo sempre più forte, legata alla produttività. Strumenti di questo tipo, che all’inizio erano opzionali, oggi sono indispensabili perché se i miei concorrenti li usano, rendono obbligatorio ciò che prima era solo opzionale. Semplicemente: l’asticella della produttività è diventata più alta. La sfida a cui siamo chiamati è quella di guidare questi strumenti, vedendoli come mezzi e mai come fini nell’ambito della concorrenza, per non farci superare dagli altri e, possibilmente, per essere noi a superare gli altri. Esistono ormai delle modalità operative con le quali il software può accompagnare le nostre giornate, ci può gestire l’agenda in maniera raffinata, può capire cosa stiamo facendo in un certo istante e quindi non interrompere il nostro lavoro, può coordinare tutte le possibili sorgenti che abbiamo di fronte, dai telefoni agli sms, quindi darci un insieme di stimoli produttivi ordinati e gestibili in funzione delle nostre priorità. N. 66 - Novembre/Dicembre 2005 VBJ 53 REPORTAGE Questi strumenti sono applicati a qualsiasi tipo di azienda, a qualsiasi tipo di ricerca, non solo nell’Information Communication Tecnology: se noi pensiamo per esempio che la ricerca automobilistica è basata per l’85% sull’uso avanzato dell’ICT, e che nella medicina siamo al 70%, come nella biologia ed in qualunque disciplina dove occorre ricerca applicata, comprendiamo che c’è una convergenza fortissima fra ICT e scienza. L’effetto netto della pervasività dell’ICT e del progresso del computer è che noi tutti abbiamo accelerato il passo; ciò ha portato ad una ricerca più veloce, ad una crescita maggiore, ha favorito l’occupazione e potenzialmente potrebbe essere ancora maggiore. Rispetto all’occupazione l’ICT è come un’onda: un’onda lascia dietro di sé delle cose (anche negative) ma davanti a sé crea delle opportunità. Quello che – in molti casi – abbiamo già visto nei secoli scorsi. La velocità alla quale ci siamo abituati, che è cresciuta relativamente nell’ultimo secolo, oggi è diventata tre volte, quattro volte superiore e noi ci ritroveremo nel 2030 in un mondo molto diverso da quello di adesso. Se siamo stati per quattro secoli, dal cinquecento in avanti, in Europa ed in particolare in Italia, avanti a tutti, gli Stati Uniti sono stati avanti a tutti nel secolo scorso. Cosa accadrà nei prossimi anni? Se paesi emergenti o già emersi come la Cina e l’India riusciranno a proiettare quello che già si vede oggi, avremo davanti un secolo - o per lo meno una metà di secolo - nel quale saranno loro ad avere un ruolo preminente. L’Europa e l’Italia Qui dobbiamo decidere, ed entro nel tema dell’Europa, se questi strumenti, se le nuove tecnologie, sono così forti, se hanno cioè un potenziale così forte da cambiare le cose. Dobbiamo cercare di capire se queste cose sono per noi - in Europa - una arma competitiva oppure un boomerang. In questo momento l’ICT per l’Europa sembra un boomerang che ci sta arrivando sulla fronte, non solo nell’ambito della tecnologia. 54 VBJ N. 66 - Novembre/Dicembre 2005 Vorrei toccare il tema dell’Europa in senso lato, perché non si può parlare di tecnologia in Europa se non si parla della visione che l’Europa ha di se stessa. L’Europa in questo momento una visione di se stessa non ce l’ha. Messa in crisi nel corso del tempo, crisi esplosa in maniera più visibile con il rifiuto di avallare la Costituzione da parte di Paesi importanti, in primis la Francia, adesso l’Europa sta cercando una visione di se stessa, deve capire dove vuole andare. Purtroppo l’Europa non è riuscita a darsi delle regole di governance quando era facile darsele, cioè quando eravamo in 15; adesso siamo in 25 a lavorare con le vecchie regole, all’unanimità. Oggi è più difficile sistemare le cose, però dobbiamo assolutamente farlo. Vorrei spezzare una lancia sull’idea di non buttarla via l’Europa, perché credo che sia un’ottima opportunità per tutti: l’Italia ad esempio da sola non può competere, pertanto sarà meglio che si inserisca in un sistema. Per l’Italia e per altri paesi è indispensabile arrivare alla convinzione che la nostra strada per la crescita e lo sviluppo passi prevalentemente dall’Europa. Ma permettetemi di parlare un po’ d’identità. Qual è l’identità dell’Europa oggi? Andiamo indietro nella storia: la Grecia, Roma, l’idea di uguaglianza, di fratellanza che è venuta dal mondo laico, poi cristiano, i nuovi valori che sono venuti fuori dal Medioevo, dal Rinascimento, l’illuminismo, la rivoluzione francese e a seguire quello che è successo nell’800 con le guerre ed il sangue che ci ha portato dove siamo arrivati adesso, agli Stati che abbiamo adesso, con forti ideali di democrazia, libertà. Non possiamo dimenticare tutto questo. Voglio condividere con voi la mia visione e la mia esperienza leggendovi una frase che ha scritto il sociologo Domenico De Masi, che applica questo pensiero ai Paesi di cultura greco-latina; dice: “ la nostra naturale inclinazione all’interclassismo, alla separazione dei poteri, alla tolleranza, alla solidarietà, alla flessibilità, alla sensualità, all’allegria, all’estroversione, alla prevalenza dei bisogni radicali dell’amicizia, del gioco e della convivialità, REPORTAGE dei bisogni di ricchezza e di dominio”. Ecco ciò che siamo. Questi concetti non sono applicabili alla realtà americana, dove la carriera è la priorità; in Europa la priorità è la famiglia. Non voglio dire chi sia nel giusto o chi sbagli, ma mi permetto di sottolineare di fronte a voi che gli europei sono coscenti di chi e cosa sono. Per questo, forse, di fronte al cambiamento noi cerchiamo di cambiare il meno possibile, cerchiamo di conservare il livello di benessere che abbiamo. Ma oggi se non abbiamo idee ambiziose rischiamo di perdere. Oggi spesso L’Italia ed anche l’Europa è vista in chiave meramente politica, un paese che non ha deciso cosa vuole fare. L’Europa era partita alla grande con la strategia introdotta dai temi di Lisbona, la strategia che l’Europa aveva adottato negli anni dell’ottimismo, all’inizio del 2000, per creare l’economia della conoscenza più dinamica e competitiva al mondo. Purtroppo dal 2000 ad oggi ci siamo accorti che siamo invece andati indietro. Ora dobbiamo riprendere quasta strada: la strategia di Lisbona era basata sulla Information Technology come anche l’economia della conoscenza: noi dobbiamo vincere questa corsa puntando sull’innovazione, sulla conoscenza, sul valore, caratteristiche che abbiamo e sulle quali non ci dobbiamo arroccare. Non possiamo vincere sul prezzo o sul massimo ribasso. Pensiamo al mondo dell’islam, quanto arroccato spesso sia nella difesa della sua cultura. Io non riesco a fare affari con una persona che tiene troppo alla sua religione; noi dobbiamo essere invece interoperabili. I Cinesi ad esempio hanno la loro religione ed hanno anche il loro modo di lavorare ma sono molto interoperabili e ci stanno battendo. La separazione tra il business, lo Stato, la Chiesa, la religione sono dogmi ai quali siamo abituati e con i quali dobbiamo fare i conti. L’Italia sta andando male, lo dimostra la nostra percentuale nell’ambito dell’economia mondiale; stiamo andando indietro, veniamo diluiti nel PIL mondiale perché entrano altri concorrenti, perché perdiamo terreno, perché la nostra capacità di produrre non ha tenuto il passo. Quindi certamente dobbiamo darci da fare di più, esplorare tutte le strade, non buttare via niente per raggiungere i nostri obiettivi futuri, che non sono maggiore disoccupazione o i figli che stanno peggio dei padri, ma esattamente l’inverso. Quando abbiamo delle situazioni facili da raggiungere o - come dicono gli americani - “i frutti bassi”, non li raccogliamo. Ad esempio, il turismo. Il turismo è un frutto basso per l’Italia: il 60-70% del patrimonio mondiale è qua; ma la Spagna ci ha battuto. Queste cose non le possiamo più accettare. Dobbiamo capire che cosa ci serve per vincere, qual è lo scenario nel quale l’Italia può vincere. Dobbiamo fare in modo che coloro che ci guidano abbiano chiara questa visione, dobbiamo fare in modo che le domande che gli imprenditori si fanno abbiano una risposta, dobbiamo avere una visione su ciò che vogliamo essere. Io sono un uomo di business, non sono io che devo dare una risposta se non per le componenti che mi riguardano. Certamente sto cercando di offrire il mio punto di vista a coloro che hanno l’obiettivo politico, nel senso più nobile della parola, coloro che devono indicarci la strada. Non possiamo assistere ad uno scenario nel quale i Paesi che costano di meno in tutto quello che fanno, come la Cina e l’India, diventino anche i più bravi nell’investire sulla ricerca, abbiano gli ingegneri migliori dei nostri, siano il punto di attrazione degli investimenti. La Microsoft - sono fiero di comunicarlo - a dicembre inaugurerà un centro di ricerca di base a Trento, realizzato con l’università di Trento. Vi assicuro che non è stato facile farlo in Italia. Da alcuni anni Microsoft ne ha realizzato uno ben più grande in Cina, a Pechino, ed uno in India, con minor difficoltà. Ecco la partita che ci giochiamo. Dobbiamo fare in modo che esista una strada, un piano per il nostro Paese che risolva i problemi che abbiamo citato. Ormai di fotografie della situazione e di diagnosi ce ne sono infinite; lo N. 66 - Novembre/Dicembre 2005 VBJ 55 REPORTAGE stesso Presidente Montezemolo vi ha parlato sicuramente di queste cose, ma di terapie possibili non ce ne sono poi tante, quindi noi dobbiamo pretendere che queste “terapie” ci vengano indicate. Io credo che i temi di cui parlo siano temi di lunga gittata, la strategia di Lisbona che possiamo riassumere come “capitale umano, infrastrutture, conoscenze” è una cosa che dà frutti nel tempo, però se non si comincia mai non si arriva mai, quindi bisogna lavorare per il lungo periodo. L’imprenditore se ne rende conto immediatamente, sa che servono certezze sul lungo periodo, pertanto è necessario che ci sia una condivisione politica su alcuni temi centrali che sono quelli della ricerca, dell’innovazione, del capitale umano, e che ci sia garanzia di lungo periodo sulle cose che portano il nostro Paese verso il futuro. Abbiamo bisogno che questi temi siano compresi e quindi io spendo un poco di tempo anche per parlare di questi argomenti. Secondo classifiche fatte su base mondiale, l’università italiana è al 121° posto. Le università devono competere sulla base dell’eccellenza non sulla base dell’ubiquità. L’università italiana non ha obiettivi di eccellenza. Dobbiamo pretendere una partnership forte fra università ed impresa, ci deve essere una osmosi, una condivisione della finalità della ricerca tra imprese ed università, soprattutto verso le imprese medie e piccole che non hanno capacità di fare ricerca, che non hanno capacità di guardare oltre l’orizzonte di breve. L’università deve avere un valore, deve avere una ricaduta imprenditoriale, questa deve essere la misura del suo successo, in modo che l’università sia una benedizione in termini di creazione di posti di lavoro, in tema di creazione di imprese. Microsoft ha un centro di ricerca a Cambridge; intorno a Cambridge c’è un fiorire di imprese che nascono dall’università e qui fiorisce l’eccellenza e la meritocrazia. In Italia i cervelli di qualità non mancano, dobbiamo fare anche noi i campioni del mondo, quindi meritocrazia ed eccellenza 56 VBJ N. 66 - Novembre/Dicembre 2005 devono essere i nostri stendardi. Dobbiamo smetterla di accettare l’emorragia che ci fa perdere le persone, la cosiddetta fuga dei cervelli. Il limite della nostra crescita sono le persone: la capacità di portare a termine un progetto non dipende tanto dalla quantità di soldi investiti nella ricerca ma piuttosto dal talento delle persone coinvolte nella ricerca. Abbiamo tanti esempi di ragazzi italiani che vanno in America, per fermare l’emorragia dobbiamo ricostruire un ecosistema che tenga conto di come siamo, del tipo di aziende che abbiamo, del tipo di università che abbiamo, e decidere quali siano le priorità di lungo periodo. Altrimenti le persone non hanno più fiducia. Bombardare i giovani con messaggi deprimenti non si può, bisogna cambiare le cose fornendo contributi positivi, in particolare nei momenti di crisi in cui questi contributi rappresentano qualcosa di concreto. A me non sta bene che l’Italia vada indietro, bisogna dare delle priorità e fare delle scelte, come in una azienda. Leggevo recentemente che un ragazzo americano di livello scolastico non eccellente aveva possibilità di carriera maggiori rispetto ad un genio in Cina; adesso non è più così, adesso un genio in Cina ha più possibilità di quelle che aveva prima. Ed in Cina di genî probabilmente ce ne sono tanti altri, quindi non dobbiamo perdere tempo. Sembra assurdo parlare di crescita dell’occupazione quando oggi un imprenditore che può va ad investire in altre parti, de-localizza. Una impresa deve fare ciò che è giusto per l’impresa, però un sistema Paese deve trovare una strada comune con l’obiettivo finale di mettere insieme i nostri punti di forza in un profilo, in una miscela vincente settore per settore. Alcuni punti di forza li abbiamo solo noi e possiamo vincere. Purtroppo dovremo renderci conto che qualche settore lo dovremmo perdere, perché per fare bene alcune cose, altre ne dovremo dismettere. Dobbiamo darci delle identità, dei brand che siano vendibili e possano avere successo. REPORTAGE L’uomo e l’umanesimo tecnologico L’uomo è al centro di tutto, le cose dette finora sono importanti ma contano relativamente poco rispetto all’uomo che c’è dentro, in particolare la categoria dei giovani. Chi ha in mano le cose che contano - per esempio la tecnologia – deve saper parlare ai giovani. La Microsoft è fatta di giovani, mentre le forze politiche spesso non parlano ai giovani. La Confindustria è un punto di grande eccellenza, riesce a lavorare con i giovani, a dargli spazio e tenerli nel sentiero principale. Dobbiamo fare in modo che a nessun giovane venga detto “devi cercare lavoro altrove se vuoi scappare da un declino”. I Giovani vanno attirati con una visione, con entusiasmo, con un obiettivo raggiungibile che non sia una sciocchezza. Se non c’è un obiettivo capace di entusiasmare le persone - io lo vedo in Microsoft - che sia capace di catalizzare gli entusiasmi che hanno, tirare fuori le energie che hanno e che spesso non sanno neanche di avere, non si va da nessuna parte. Bisogna che prendiamo in mano la responsabilità che abbiamo perché lo scenario che ci troviamo di fronte è di cambiamento; ci sono delle indagini che dicono che oggi un giovane di 25 anni quando avrà finito di lavorare avrà cambiato lavoro 15 volte. Quindi bisogna fare in modo che si capisca a cosa va incontro, per poter vivere con successo questo cambiamento, essere flessibile e diventare davvero l’architetto della propria vita. Serve potere guardare al lungo anziché al breve, a capire quello che è importante rispetto a quello che è urgente. È necessario potere costruire le pietre angolari sulle quali edificare il resto della casa; ci deve essere passione, divertimento, attrazione ed anche sacrificio, oltre al lavoro duro. Se una persona non ha la possibilità di vivere bene nell’azienda ha tre possibilità: o si rassegna, o cambia l’azienda da dentro oppure cambia azienda. La prima possibilità, rassegnarsi, non la consiglio e dico a chi ha la possibilità di guidare gli altri come i manager, di essere consapevoli del privilegio che hanno ed anche della grande responsabilità da assumere. Immaginate i danni che può fare un manager che dà messaggi sbagliati ai suoi, che gli smorza gli entusiasmi, che uccide la creatività, che danneggia l’autostima. Di questo dobbiamo renderci conto fino in fondo, perché altrimenti noi, i nostri collaboratori e le nostre aziende non vanno da nessuna parte. Le cose più importanti per un manager sono tre: la crescita dell’azienda, la crescita delle persone e la crescita della soddisfazione dei suoi collaboratori. Queste tre cose sono collegate tra di loro, ed il giudice di questo non sono i capi superiori: sono i collaboratori stessi che devono aiutare il loro capo ad essere un capo migliore, che devono guidarlo, che devono dire quello che non va bene, perché è facile lamentarsi ma serve a poco. Quindi bisogna che ciascuno di noi - anche rischiando - contribuisca al miglioramento dell’ambiente nel quale lavora, perché nessuno può avere niente per niente. Noi dobbiamo meritarci un lavoro migliore ed un capo migliore, il nostro stesso successo. Molti studi dimostrano che le aziende che lavorano veramente sui valori hanno più successo e crescono più delle altre. Se solo guardassimo come si vive dentro queste aziende, le cose che si fanno, come si passa la giornata, non c’è proprio confronto con il nostro contesto. In Italia ad esempio siamo più concentrati sui processi, le procedure, sulla catena di comando e controllo; si cerca di prevedere tutto ciò che è prevedibile: in questo caso fai così, in quell’altro fai cosà, ma in quell’altro caso ancora - a cui nessuno aveva pensato come ti comporti se non hai un riferimento valoriale? Se non c’è una cultura aziendale che ti possa guidare, per la quale puoi dare, una volta che l’hai capita, il meglio di quello che hai dentro, ed anche di più, scoprendo con meraviglia che non sapevi di poter dare di più, non valorizzi te stesso. In questo modo posso condividere con voi ancora una volta i valori nei quali io sto vivendo in Microsoft, valori nei quali ogni persona crede, valori verso i quali ogni persona riporta sistematicamente il suo progresso. N. 66 - Novembre/Dicembre 2005 VBJ 57 REPORTAGE Se diamo ad un persona un obiettivo che non è solo quello di raggiungere il suo fatturato, otteniamo la crescita personale ed il rispetto dei valori. I valori principali sono: l’integrità, l’onesta, la passione per quello che si fa e la passione nel farlo insieme agli altri e nel farlo per gli altri, il rispetto e l’apertura che non sono necessariamente la stessa cosa - perché io posso essere aperto e brutale verso una persona oppure aperto e rispettoso. Io preferisco dire ad un collaboratore che non ha svolto bene il suo lavoro in maniera rispettosa, costruttiva, perché l’obiettivo è farlo vivere meglio e di più insieme a me. Mi impegno a rendere gli altri migliori quindi a vivere il successo degli altri: il successo di un manager non è il suo, ma quello delle persone che gestisce, la capacità di prendersi grandi sfide e saperle vivere ogni giorno, spezzettarle in pezzi eseguibili nei quali si possa vedere ogni giorno l’autocritica. Occorre saper mettere in dubbio le certezze consolidate: io dico sempre a nostri ragazzi di non essere allineati ma di cercare di generare un po’ di caos costruttivo, perché se siamo tutti d’accordo e si passa la maggior parte del tempo a cercare di capire cosa il capo pensa e vuol sentirsi dire - come spesso succede in molte aziende - allora andremo a finire tutti verso il muro. Occorre mettere impegno verso l’eccellenza, capire quello che si può fare meglio, dedicare impegno per i risultati, per la qualità; chi è in una posizione di particolare forza deve essere particolarmente attento nel dosare la sua forza, nel rispettare i concorrenti, nel creare spazi anche per gli altri. Microsoft dosa continuamente la sua forza perché se le sue innovazioni fossero troppo invasive potrebbero avere un effetto brutale, mal accettato sia dai concorrenti che dalle persone in genere. La complessità, la semplicità, la diversità e la compassione Affrontiamo adesso il grande tema di ridurre la complessità verso la semplicità, ovvero 58 VBJ N. 66 - Novembre/Dicembre 2005 non nascondersi nella complessità ma ridurre le cose in termini semplici, perché le persone possano capirle, possano condividerle, possano viverle insieme. Quindi non nascondersi ma chiarirsi. La semplicità, la convinzione di quello che si fa e l’autenticità sono valori basilari: non si può simulare per molto tempo, bisogna essere convinti delle cose che si fanno, bisogna essere autentici. Le persone di fronte a noi sono il nostro comune denominatore, quindi dobbiamo rendere le cose semplici per poter parlare ed agire in modo comune. La tecnologia, anche la più complicata, l’obiettivo più raffinato, il messaggio più difficile, devono essere assolutamente comprensibili, a misura dell’uomo che abbiamo di fronte perché in tutti i possibili messaggi si mantenga il rispetto, l’equilibrio e l’integrità della persona. Poi il grande valore della diversità. Una cosa bella tra le tante che ho vissuto è quella del tirare fuori il valore della diversità: punti di vista diversi, persone diverse, obiettivi diversi, storie diverse, paesi diversi e anche semplicemente uomo verso donna. La donna spesso è discriminata perché viene tenuta indietro, perché giustamente ha delle priorità diverse dagli uomini nella vita. Quindi tirar fuori il valore dalle donne è complesso e molte aziende non sono capaci di farlo. Quando si parla di diversità non è un discorso di tollerarla, perché tollerare è “accetto, però un po’ mi da fastidio”, bensì bisogna valorizzare la diversità. Poi se posso permettermi un altro suggerimento: cercare di non essere monomaniaci, monocordi, monotematici, totalmente focalizzati su una cosa. Certo, soprattutto quando una azienda è leader di settore, ha bisogno di persone che siano altamente specializzate e paranoiche in quello che fanno, però non basta. Bisogna lavorare con gli altri, bisogna essere aperti, avere altri interessi, essere delle persone vere a 360 gradi, essere delle persone che sanno stare con gli altri. Una bella parola è “compassione” che significa avere passione insieme agli altri ma anche compassione degli altri. E qui irrompe anche il REPORTAGE tema dell’autostima, il tema della difficoltà che gli altri possono avere ed anche il tema della compassione di se stessi. È lecito che io mi ponga degli obiettivi irraggiungibili che distruggano la mia vita? Posso accettare il rischio se sono convinto di farcela però se poi in realtà non ci arrivo distruggo la mia vita e quella di chi crede in me; allora meglio essere realistici fin dall’inizio: questo è il tema della compassione e della comprensione di se stessi. Certo ci vuole anche la paranoia: molte aziende di successo sono nate anche dalla paranoia, dalla forza, dalla capacità di lavorare duro, dalla capacità di concentrarsi del loro leader. Però il successo deve essere anche accompagnato dalle altre cose. Io credo che noi possiamo cambiare non in tempi brevissimi - questo scenario, se avremo la fortuna di essere guidati dai leader giusti con i programmi giusti. Ma certamente non cambieremo governing di notte, ci vorrà tempo. I protagonisti di questo cambiamento sono inevitabilmente i giovani e credo che si debba identificare un insieme di forze positive per il cambiamento, in qualunque realtà, in una realtà aziendale, in una realtà di associazione, di federazione, come in una realtà politica. Un insieme di forze che operano per i cambiamenti in una direzione nella quale c’è una terapia condivisa. Serve che lo facciano con determinazione, con convinzione, con spirito di servizio, con umiltà, con autenticità, con rispetto, con fiducia, con umiltà e con fede nel futuro. Credo che il nostro compito sia avanzare verso un mondo migliore di quello che noi abbiamo trovato, creare opportunità per gli altri, generare un cambiamento positivo; voglio concludere con una frase, citando una canzone di Bono Vox, “Un uomo viene per cambiare le cose”. Io credo che noi non siamo qui per accettare passivamente quello che accade, ma siamo qui per cambiare le cose. I N O F F E R TA V B J 6 6 Imperfect XML Rants, Raves, Tips, and Tricks ... from an Insider di D. Megginson Prentice Hall ISBN 0131453491 256 pp - 35,95 € Pro MSMQ: Microsoft Message Queue Programming di A. Redkar et al. Apress ISBN 1590593464 423 pp - 50,24 € Model Driven Architecture Foundations and Applications di A. Hartman D. Kreischei Springer ISBN 3540300260 349 pp - 55,64 € Scrivi a [email protected] specificando nell’oggetto della e-mail: IN OFFERTA VBJ n. 66 OPPURE inviaci il coupon sottostante al numero di fax 0587/732232 Potrai acquistare i libri qui riportati con uno SCONTO ECCEZIONALE del 10% anche se acquisti solo un libro OPPURE del 20% se acquisti XML in a Nutshell, Third Edition di E. Harold W. Scott Means O’Reilly ISBN 0596007647 712 pp - 39,95 € Core C# and .NET The Complete and Comprehensive Developer’s Guide to C# 2.0 and .NET 2.0 di S. Perry Prentice Hall ISBN 0131472275 1008 pp - 46,95 € Model-Driven Software Development di S. Beydeda et al. Springer ISBN 354025613X 464 pp - 85,55 € 3 libri VBJ 66 .NET TOOLS Mail2Rss di Raffaele Di Natale Posta elettronica e news feed in formato RSS 2.0: due modi differenti di distribuire informazioni, ma che grazie a Mail2Rss possono interagire in totale sicurezza consentendoci di trarre il massimo dalle due tecnologie. Immaginiamo una casella di posta elettronica utilizzata da un gruppo d’utenti come una sorta di repository di novità e aggiornamenti vari da condividere quotidianamente. Supponiamo inoltre che gli utenti non vogliano ricevere i messaggi inviati dal resto del gruppo all’interno della propria casella, ma semplicemente vogliano visualizzarli tramite una pagina web. In altre parole vorremmo che i messaggi di posta elettronica fossero semplicemente “visti” in maniera differente e distribuiti in un formato standard solo se richiesti (come il testo che scorre in una porzione della home page del gruppo di utenti, per esempio). Mail2Rss è proprio lo strumento adatto a risolvere questo particolare tipo di problema e non solo. Un cenno su RSS 2.0 RSS 2.0 [Really Simple Syndication] è un formato orientato alla distribuzione d’informazioni sul web (content syndication) basato su un dialetto di XML [1]. Un documento RSS è costituito da un elemento <rss> che specifica la versione, in questo caso la 2.0, ed un elemento <channel> che contiene tutte le informazioni sui metadata ed i loro contenuti. Mai2Rss v.07 esegue una conversione dei messaggi di posta elettronica, presenti all’interno di una prefissata casella, aggregandoli in formato RSS. La particolarità dell’applicazione consiste nel fatto che i messaggi elaborati restano ancora non letti per l’utente che successivamente li apre tramite un normale client di posta elettronica. Si tratta di un web service .NET sviluppato in C# basato su due moduli denominati rispettivamente mai2rss.dll e Org.Mentalis.Security.dll [2]. Il primo rappresenta l’applicazione principale; il secondo gestisce gli aspetti legati alla sicurezza nella connessione ed è un modulo esterno. L’applicazione gira in ambiente Microsoft Windows 2000 Professional/Server, Windows XP Professional, Windows 2003 Server con IIS 5.0 e .NET Framework 1.1. È necessario predisporre almeno un account tra POP3, Passport-MSN, Yahoo o GMail. Il file XML prodotto dal servizio va poi manipolato mediante un software idoneo all’aggregazione di news feed: il tool è stato testato con SharpReader [3], FeedReader [4] e FeedDemon [5], ma funziona correttamente anche con altri news aggregator come RSSBandit [6]. L’applicazione descritta in quest’articolo è stata installata su Windows XP SP2 con IIS 5.0, .Net Framework 1.1, Internet Explorer 6.0. Installazione La procedura d’installazione è sorprendente se pensiamo di aver a che fare con un tool completamente gratuito. Dopo aver portato a termine il download dal sito [7] basta seguire il wizard che in pochi passi effettua l’installazione del web service con relativa creazione della directory virtuale all’interno della cartella Inetpub di default. Nel caso in cui la cartella Inetpub non dovesse coincidere con quella dell’installazione, l’unico modo è quello di eliminare in IIS la N. 65 - Settembre/Ottobre 2005 VBJ 61 .NET TOOLS directory virtuale presente e ricrearla in corrispondenza della locazione preferita, ricopiandovi il contenuto della vecchia cartella. Una volta installata è possibile rimuoverla dal pannello di controllo di Windows. Al termine dell’installazione Mail2Rss può essere richiamata digitando ad esempio http://localhost/mail2Rss/ se localhost è il web server in cui abbiamo installato il web service. Criptare la password All’interno della home page è presente una sezione “Encrypt the password” che consente all’utente di criptare agevolmente la password relativa al proprio account di posta. Prima di lanciare il web service è necessario infatti criptare la password, in quanto il servizio è richiamato tramite una richiesta http di tipo get con testo in chiaro e quindi facilmente intercettabile. Dopo aver inserito all’interno della testbox “password” la password ed aver premuto il tasto “Encrypt” sarà visualizzata una stringa che rappresenta appunto la password criptata attraverso i servizi specifici dell’applicazione. Mail2Rss può essere utilizzata senza alcuna variazione dei parametri di default: la home page descrive sinteticamente la modalità d’utilizzo del servizio. Si supponga che l’account di posta utilizzato sia di tipo Pop3, con username = nomeutente, password = miapassword e server POP3 = dominioposta.it. Seguendo le indicazioni dell’home page, per ottenere le news feed relative alle email presenti all’interno dell’account d’esempio digitiamo sul browser: l’interno della casella è presente un solo messaggio così costituito: 1. 2. 3. 4. 5. Nome mittente: Paolo Rossi; Indirizzo: [email protected] Oggetto: Test Mail2RSS Corpo del messaggio: Prova per Mail2Rss. Allegato: allegato1.doc il corrispondente file xml visualizzato è così strutturato: <?xml version=”1.0” encoding=”utf-8”?> <rss xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” version=”2.0”> <channel> <title>RSS 2.0 email feed from dominioposta. it</title> <link>http://mail2rss.sourceforge.net</link> <description>RSS 2.0 email feed from dominioposta.it</description> <generator>mail2rss v0.7</generator> <copyright>Copyright (C) 2004 by Charlie Kostov</copyright> <ttl>60</ttl> <language>en-us</language> <lastBuildDate>Sat, 22 Oct 2005 16:40:10 GMT</lastBuildDate> <item> <title>Test Mail2RSS</title> <link /> <description>Prova per Mail2Rss ===[ mail2rss detected attachment(s) list ]====== Attachment filename: allegato1.doc </description> <author>”Paolo Rossi” < http://localhost/mail2rss/mail2rss.asmx/GetMail?mail Server=dominioposta.it&userID=nomeutente&userPass= [email protected]></author> <pubDate>Fri, 21 Oct 2005 19:45:29 +0200</pubDate> password_criptata <category>mail message</category> </item> Dopo qualche secondo d’attesa, in relazione alla velocità di connessione, al tempo di risposta del server, al numero di messaggi ed al contenuto degli stessi, vedremo apparire un file XML in formato RSS 2.0 che aggrega tutti i messaggi trovati. Nel caso in cui un messaggio preveda uno o più file allegati, il loro nome verrà elencato all’interno della news relativa. Se per esempio al- 62 VBJ N. 66 - Novembre/Dicembre 2005 </channel> </rss> Tralasciando le tipiche intestazioni XML si possono notare l’elemento rss, il channel con i riferimenti all’applicazione e più in basso l’unico item di tipo mail message che rappresenta il messaggio in precedenza descritto. .NET TOOLS All’interno di tale item si riconoscono: l title, l’oggetto del messaggio; l description, corpo del messaggio, con gli even- tuali allegati; l author, il mittente con nome e indirizzo; l pubDate, data d’invio del messaggio All’interno della casella di posta elettronica possono essere presenti più messaggi; in questo caso si avrà una sequenza finita di item di tipo mail message. Utilizzando un visualizzatore di news feed, nel nostro esempio RSSBandit, il risultato finale avrà un aspetto simile a quello descritto in figura. Conclusioni Mail2Rss è un’applicazione che svolge un compito semplice, ma lo fa senz’altro in maniera soddisfacente. Come anticipato nell’introduzione, Mail2Rss risolve un problema specifico, ma considerando l’abuso che spesso si fa oggi con mailing list o strumenti simili, questo web service, grazie all’interazione tra due servizi apparentemente slegati tra loro rappresenta una valida alternativa nella distribuzione d’informazioni nel web. Riferimenti [1] http://blogs.law.harvard.edu/tech/rss [2] http://www.mentalis.org/soft/projects/ssocket/ [3] http://www.sharpreader.net/ [4] http://www.feedreader.com/ [5] http://www.bradsoft.com/feeddemon [6] http://www.rssbandit.org/ [7] http://sourceforge.net/projects/mail2rss Prodotto Configurazione Come detto in precedenza non è necessario variare alcun parametro per utilizzare il servizio in maniera standard. In ogni caso, come in tutti i web service .NET, il file denominato web.config permette di personalizzare la configurazione dell’applicazione. È possibile variare il numero massimo di messaggi elaborati per ogni account, la dimensione massima del singolo messaggio e gli indirizzi standard per i gateway relativi ai principali servizi di webmail come yahoo, msn, hotmail. Sempre all’interno del file di configurazione si può modificare la chiave utilizzata per crittografare la password, creandone una personale. La visualizzazione delle news feed può avvenire offline utilizzando un tool specifico oppure online. Nel secondo caso la strada senz’altro più agevole è quella di utilizzare i moduli standard, presenti ormai nella maggior parte dei CMS, atti alla visualizzazione di news feed da un URL specifico. Mail2Rss Url di riferimento http://sourceforge.net/projects/mail2rss Stato Release v. 07 Semplicità d’uso ★★★★✩ Eccellente la procedura d’installazione. Applicazione semplice da usare. L’unica limitazione è legata alla necessità di modificare manualmente l’indirizzo di connessione. Nella prossima release sarà fornito un supporto per testare i parametri di connessione. Utilità ★★★★✩ L’applicazione del servizio avviene in una nicchia della distribuzione dell’informazione. Risolve in maniera soddisfacente il problema di pubblicare come news feed RSS i messaggi di un dato account di posta elettronica. Qualità prodotto ★★★★✩ Buona, nei test effettuati il servizio ha sempre completato il suo compito nonostante alcuni casi di lentezza nella connessione al server di posta. Sono gestiti anche gli eventuali errori di connessione al server di posta. Qualità documentazione ★★★✩✩ Scarsa, se si eccettua la documentazione della home page del servizio N. 66 - Novembre/Dicembre 2005 VBJ 63 .NET TOOLS DevPartnerProfiler di Fabio Perrone Uno strumento gratuito per misurare le prestazioni di applicazioni .NET Considerare le prestazioni quando si scrive del codice dovrebbe sempre essere una priorità per ogni programmatore che si rispetti. Il problema è che non sempre si possiedono le conoscenze necessarie o una quantità sufficiente di tempo a disposizione per risolvere il problema alla radice, vale a dire scrivere codice che utilizzi le migliori pratiche offerte dal linguaggio in cui si sta sviluppando, per ottenere applicazioni prestazionalmente soddisfacenti. È ovvio che in applicazioni particolarmente complesse le cattive prestazioni di un programma non dipendono solamente dal codice scritto male, ma influscono negativamente altri fattori quali la lentezza della rete, l’accesso a database remoti e così via. Per ottimizzare il codice esistono tuttavia strumenti quali DevPartner Profiler della Compuware (che rientra nella categoria dei cosiddetti “Performance analysis tool”) che possono aiutare a scoprire eventuali colli di bottiglia nell’applicazione. DevPartner Profiler viene installato come addin per Visual Studio 2003, quindi lo ritroviamo nel menu Strumenti, sotto la voce DevPartner. La prima operazione da fare, se si vogliono effettivamente testare le prestazioni solo della propria applicazione, è quella di escludere il tempo trascorso in thread d’altre applicazioni che possono essere in esecuzione in quell’istante e considerare solo i thread del nostro programma. Così facendo, i dati che saranno prodotti risulteranno totalmente indipendenti da ingerenze esterne. La flessibilità di questo prodotto si denota anche dal fatto che può raccogliere dati da chiamate effettuate verso componenti scritti da terze parti, inclusi componenti COM per cui non si ha a disposizione il codice sorgente. È possibile effettuare test sia su applicazioni Windows Forms sia su applicazioni Web ASP.NET, sia su applicazioni native scritte in C++ utilizzando la tecnologia ATL, con l’unica limitazione che tutto venga eseguito su un’unica macchina, in pratica non in remoto (questa opzione viene superata se si acquista la versione non Community del prodotto). 64 VBJ N. 66 - Novembre/Dicembre 2005 Per attivare la procedura di raccolta dei dati, è sufficiente selezionare la voce di menu che abbiamo menzionato in precedenza e premere Ctrl+F5 per avviare l’applicazione senza debug – ciò non perché il programma non sia in grado di raccogliere dati anche in sessioni di debug, ma perché ci sono meno dati da raccogliere e meno interferenze. Una volta lanciata l’applicazione, è possibile effettuare qualsiasi genere d’operazione che si desidera, tutto sotto l’“occhio vigile” di DevPartner Profiler che registrerà (senza che lo sviluppatore se ne accorga, e quindi abbastanza rapidamente) ogni singola operazione effettuata. Per ultimare l’analisi è sufficiente terminare l’applicazione, ottenendo così, direttamente nell’IDE di Visual Studio.NET, un file con estensione .dpsession che conterrà una notevole mole d’informazioni, utili per capire quali metodi o addirittura quali singole istruzioni sono più lente. I dati raccolti sulle prestazioni includono informazioni sugli assembly, sulle classi e sul codice sorgente. Una volta che si hanno questi dati a disposizione, spetta in ogni caso al programmatore intervenire e correggere eventuali anomalie nel codice. Le unità di misura delle prestazioni possono essere cicli macchina, microsecondi, millisecondi o secondi, a scelta dell’utente. Le statistiche generali che ci vengono presentate variano a seconda se si analizzano i metodi o se si analizzano le singole righe d’ogni metodo. Nel primo caso si ha a disposizione una grande quantità di dati, i cui principali sono la percentuale del tempo trascorso in quel metodo rispetto al tempo totale trascorso, il numero di volte che un metodo è stato chiamato, il tempo medio d’esecuzione, l’esecuzione più rapida e più lenta del metodo, il tempo reale impiegato, in pratica considerando anche thread d’altre applicazioni (ignorando quindi l’impostazione accennata in precedenza) e il tempo percepito, vale a dire una misurazione che comprende anche il leggero sovraccarico dovuto alla presenza di DevPartner Profiler durante l’esecuzione della nostra applicazione. Nel secondo caso, cioè quando si analizzano le singole righe di codice, abbiamo a disposizione il numero di volte che una riga è stata eseguita, la percentuale di tempo relativo a quella riga rispetto al metodo che la contiene, il tempo totale della riga incluse le chiamate ad eventuali metodi e il numero di metodi richiamati da quella riga. .NET TOOLS Un ulteriore metodo visuale a nostra disposizione è rappresentato dal grafo delle chiamate effettuate verso un metodo e da quel metodo verso altri metodi: tramite i cosiddetti percorsi critici (cioè la successione di metodi figli chiamati da un metodo, che hanno accumulato la maggiore quantità di memoria) è possibile mettere in evidenza i cosiddetti “colli di bottiglia”. Nonostante alcune limitazioni della versione Community, quali l’impossibilità di confrontare due sessioni di test, l’esecuzione dei test all’esterno di Visual Studio.NET, l’obbligo dell’esecuzione delle applicazioni da un’unica macchina, DevPartner Profiler risulta un ottimo prodotto per chi ha esigenze urgenti di miglioramento di prestazioni. Utilizzato con altri tool quali FxCop, dovrebbe permettere di ottenere buoni risultati in poco tempo. È comunque bene ricordare che se le prestazioni di un’applicazione vengono monitorate sin dall’inizio dello sviluppo, i risultati saranno decisamente migliori che tentare di “mettere delle pezze” ad applicazioni già ultimate. Prodotto Compuware DevPartnerProfiler Community Edition Url di riferimento http://www.compuware.com/products/devpartner/profiler/default.asp Stato Release Stabile Semplicità d’uso ★★★★✩ La gran quantità di dati analizzati richiede un piccolo sforzo per poterli interpretare al meglio. Utilità ★★★★✩ Strumento fondamentale per chi desidera migliorare le prestazioni. Qualità prodotto ★★★★✩ Affidabile, nei test sul prodotto eseguiti non ha mai mostrato particolari problemi. Qualità documentazione ★★★✩✩ Presente help integrato con Visual Studio.NET molto esaustivo (in lingua inglese).