di Francesco BalenaIn questo periodo sto facendo molta consulenza per aziende italiane alle prese con la migrazione da VB6, usando ovviamente la versione preliminare di VB Migration Partner. Oramai la beta è stabile e si comporta davvero molto bene. In un solo giorno, ad esempio, abbiamo migrato una applicazione di circa 20mila righe che lavora in modo intensivo con ADO in circa mezza giornata, e tutto funziona a meraviglia!
Anche se la migrazione sta andando bene, a volte i programmi migrati sono più lenti del codice VB6 originario. Per esperienza le cause sono principalmente due: (1) l'uso di COM Interop per accedere agli oggetti ADO, (2) un uso massiccio di concatenazione di stringhe. Per il primo problema non c'è molto da fare, purtroppo: occorre migrare il codice di accesso al database da ADO a ADO.NET, un compito più semplice a dirsi che a farsi. Per il secondo problema ci sarebbe invece una soluzione semplice: basterebbe sostituire la variabile stringa con una variabile StringBuilder, e le concatenazioni sarebbero automaticamente più veloci. Peccato che questa sostituzione comporti una revisione completa del codice, perchè richiede di trasformare tutti gli operatori & (concatenazione) con il metodo Append, per non parlare dei casi in cui la variabile stringa viene usata come argomento a funzioni come Trim o Left. Ad esempio, il seguente codice richiede ben 17 secondi sul mio sistema a 3GHz:
Dim s As String = "" Dim sw As Stopwatch = Stopwatch.StartNew() For i As Integer = 1 To 100000 s = s + "*" Next MsgBox(sw.Elapsed.ToString)
Per fortuna la soluzione è davvero semplice: basta creare una classe che utilizzi internamente un oggetto StringBuilder, che ridefinisca gli operatori & e +, e che supporti la conversione implicita da-a stringa. Il codice di questa classe StringBuilder6 si scrive davvero in pochi minuti:
Imports System.Text
' a wrapper for the StringBuilder object, with support for + and & operators
Public Class StringBuilder6
Private buffer As New StringBuilder
' return the inner string
Public Overrides Function ToString() As String Return buffer.ToString() End Function
Public Shared Operator +(ByVal op1 As StringBuilder6, ByVal op2 As String) As StringBuilder6 op1.buffer.Append(op2) Return op1 End Operator
Public Shared Operator &(ByVal op1 As StringBuilder6, ByVal op2 As String) As StringBuilder6 op1.buffer.Append(op2) Return op1 End Operator
' convert to string
Public Shared Widening Operator CType(ByVal op As StringBuilder6) As String Return op.ToString() End Operator
' convert from string
Public Shared Widening Operator CType(ByVal str As String) As StringBuilder6 Dim op As New StringBuilder6() op.buffer.Append(str) Return op End Operator
End Class
A questo punto per velocizzare il codice visto prima è sufficiente modificare UNA SOLA ISTRUZIONE, ovvero la dichiarazione della variabile stringa:
Dim s As StringBuilder6 = ""
Dopo questa sostituzione, il ciclo visto prima viene eseguito in 8 millesimi di secondo, ovvero circa 2000 volte più velocemente!!! Non male, per una fix così semplice 
Indipendentemente dal fatto che state migrando codice da VB6 o se avete scritto codice VB.NET (o C#) da zero: se trovate dei punti in cui fate uso massiccio di concatenazione di stringhe e che pensate possano rallentare l'esecuzione, provate a sostituire la variabile stringa con un oggetto StringBuilder6: in alcuni casi il programma girerà molto ma molto più velocemente.
di Francesco BalenaVB Migration Partner - il progetto a cui ho lavorato praticamente a tempo pieno negli ultimi 22 mesi è finalmente in
beta!
Cosa faccia questo tool lo dice il suo
nome: è un convertitore
di applicazioni da VB6 a VB.NET. Immagino bene che a questo
punto molti di voi scrolleranno le spalle e penseranno "Ancora VB6?"
oppure "Ecco un altro che ci tenta!", ma la verità è che VB6 continua
a imperversare tra le software house, anche e soprattutto perchè non ci sono
strumenti decenti per convertire centinaia di migliaia di righe di codice VB6
in VB.NET, in modo veloce e (almeno relativamente) indolore. Davanti alla
prospettiva di sprecare diversi anni uomo di lavoro sulla migrazione di un
gestionale VB6 complesso, e di dover fare tutto a mano, beh... sono in molti a
farsi indietro. Ora però ci si sta rendendo conto che la decisione non può più
essere rimandata: è risaputo che i programmi VB6 non funzionano bene sotto
Vista (in particolare, non funzionano molti controlli ActiveX) e soprattutto
Microsoft ha dichiarato che il supporto esteso per VB6 termina a Marzo del
2008
Il problema dei convertitori automatici di
codice è che quasi mai mantengono le promesse, si tratti di conversione da VB6,
Java, Delphi, o qualsiasi altro linguaggio. Nel migliore dei casi, riescono a
convertire circa il 95% delle istruzioni, ma con una applicazione VB6 da
100mila righe (quindi neanche estremamente complesse), il 5% di istruzioni
errate significa dover mettere mano a cinque
mila righe! Quel che è peggio è che quasi sempre non si
tratta di piccole modifiche che prendono qualche minuto. Ad es. VB6 e VB.NET
espongono due modelli completamente differenti per le istruzioni grafiche, il
printing, e il drag-and-drop, quindi in realtà anche se il compilatore
VB.NET segnala relativamente pochi errori, in effetti per implementare grafica,
printing, e D&D in VB.NET occorrerà rifare intere porzioni di
programma. Per non parlare di tutti i controlli ActiveX non supportati, il
databinding, e tanto altro.
Nei test che abbiamo eseguito su centinaia
di applicazioni VB6 (circa un milione di righe di codice in totale), VB
Migration Partner offre la notevole media di un errore di compilazione ogni 1,100 righe di codice,
ovvero una percentuale di precisione superiore al 99.9%. Scusate se è poco! Del
resto, per avere una idea della precisione di questo strumento, basta un
piccolo elenco delle feature di VB6 che sono pienamente supportate (eccetto
dove indicato) e che sono quasi tutti fuori dalla portata di qualsiasi altro
strumento sul mercato, incluso ovviamente l’Upgrade Wizard incluso in Visual
Studio:
- array con indice LBound diverso da zero
- Gosub, On...Goto, e On ...Gosub
- variabili auto-instancing (Dim x As New Person) che mantengono il comportamento anche in
VB.NET
- Declare con parametri As Any e indirizzi di callback (es. EnumWindows)
- proprietà di default risolte correttamente anche in modalità late-bound
- finalizzazione deterministica per le variabili di tipo Connection, Recordset, ecc., ad es. per
ottenere che la connessione sia chiusa quando la variabile è messa a Nothing o
esce dallo scope
- supporto (limitato) per Variant e propagazione dei valori Null
- tutti i 60+ controlli di VB6 (uniche eccezioni: OLE e Repeater)
- control array, anche di controlli ActiveX di terze parti
- menu popup
- metodo Controls.Add e oggetti VBControlExtender per creare dinamicamente form di data
entry
- metodi grafici (Line, Circle, PSet, PaintPicture, ecc.) con supporto per ScaleMode
custom
- oggetto Printer e collection Printers
- OLE drag-and-drop
- UserControl, con supporto di feature avanzate e oggetti Ambient e Extender
- data-binding con controlli Data ADO, RDO, ADO, con recordset ADO, con DataEnvironment
- classi DataEnvironment non gerarchici
- classi e user control ADO data source e simple data consumer
- classi MultiUse, PublicNotCreatable, GlobalMultiUse
- classi persistable, classi MTS/COM+
In realtà, è più semplice elencare cosa il
nostro tool non supporta: gli UserDocument, le PropertyPage, le WebClass,
gli DHTML Page designer, istruzioni non documentate (VarPtr, ObjPtr, StrPtr),
il drag-and-drop “classico” (non OLE D&D), e poco altro.
VB Migration Partner è in grado di
convertire un intero gruppo di progetti
VB6 in una sola operazione, e riesce a generare codice di migliore qualità
proprio perchè riesce a vedere “dentro” una DLL che fa parte del project group.
Il tool dispone anche di un code
analyzer molto sofisticato, in grado ad esempio di segnalare classi,
metodi, e variabili non utilizzate: se si considera che in un progetto di
grandi dimensioni che si è evoluto negli anni molto spesso il 5-10% di codice è
“dead code”, si vede che questa feature da sola è in grado di far risparmiare
parecchio tempo prezioso.
Uno dei segreti dell’alto tasso di
conversione senza errori è il supporto per i migration pragmas. Un pragma di migrazione non è che uno speciale
commento inserito nel codice VB6 che indica a VB Migration Partner come
convertire certi costrutti che permettono approcci differenti. Per esempio, il
seguente pragma ArrayBounds indica che l’indice di tutti gli array nel progetto
corrente deve essere forzato a zero:
'##project:ArrayBounds ForceZero
I pragma possono avere scope di progetto,
di classe, di metodo o di singolo elemento; un pragma ha sempre la precedenza
su tutti i pragma dello stesso tipo con scope più ampio. Per esempio, io posso
fare l’override del precedente pragma dentro un metodo, e specificare un
ulteriore pragma relativo ad uno specifico array:
Sub Test()
'## ArrayBounds Shift
'##arr.ForceZero
Dim names(1 To 10) As String
Dim values(1 To 10) As Long
Dim arr(1 To 10) As Integer
End Sub
L’opzione Shift indica a VB Migration Partner che i
valori di LBound e UBound dell’array devono essere “shiftati” verso lo zero, in
modo da preservare il numero di elementi dell’array originario in VB6:
Sub Test()
Dim names(9) As String
Dim values(9) As Integer
Dim arr(0 To 10) As Short
End Sub
VB Migration Partner supporta una cinquantina di pragma
per gli scope più differenti. Ad esempio, SetName modifica il nome di un
elemento, SetType il suo tipo, AutoNew implementa il comportamento
auto-instancing delle variabili As New, AutoDispose fa sì che le variabili
siano “disposed” correttamente prima di essere impostate a Nothing, ecc.
Uno dei difetti principali di un tool di conversione
“tradizionale” è il fatto che – dopo la prima conversione – il progetto
originario VB6 e il nuovo progetto VB.NET vivono due vite completamente
separate. Se la migrazione dura settimane o mesi – com’è plausibile per i
gestionali di grandi dimensioni – quando finalmente il programma VB.NET compila
ed esegue senza errori il codice sarà già “vecchio”, ovvero non allineato alle
modifiche e ai bugfix che nel frattempo sono state apportate al programma VB6.
Per evitare questo problema, VB Migration Partner offre
il supporto al cosiddetto ciclo
convert-test-fix. In pratica, si tratta della possibilità di migrare in
modo iterativo un determinato progetto VB6 raggiungendo lo stadio “zero compilation error” e poi “zero runtime error” semplicemente
arricchendo il sorgente VB6 con degli opportuni pragma, ma senza modificarlo in
altro modo. Grazie al ciclo convert-test-fix è possibile affrontare migrazioni
complesse – che possono durare anche settimane o mesi – mantenendo sempre
sincronizzati le versioni VB6 e VB.NET della applicazione.
Io sono particolarmente contento delle
feature del refactoring engine, che
dopo la prima conversione “grezza” applica al codice VB.NET numerose tecniche
di ottimizzazione. Ad esempio, è in grado di accorpare la dichiarazione e
l’inizializzazione di variabili, di generare istruzioni Return, di sfruttare
gli operatori composti di assegnazione (tipo += e *=), e di altro ancora:
Function GetValue() As Long => Function GetValue() As Integer
Dim x As Long Dim x As Integer = 123
x = 123 ...
... If x > 0 Then
If x > 0 Then Return x
GetValue = x ElseIf x = 0 Then
Exit Function Return -1
ElseIf x = 0 Then End If
GetValue = -1 ...
Exit Function x += 1
End If ...
... End Function
x = x + 1
End Function
Ci sono altri tipi di refactoring che si
possono ottenere mediante opportuni pragma. Ad esempio, è possibile inserire la
dichiarazione di una variabile dentro un ciclo For o For Each, come in questo
esempio:
Dim i As Integer => For i As Integer = 1 To 100
For i = 1 To 100 ...
... Next
Next
Ciliegina sulla torta: nonostante tutte
queste feature VB Migration Partner è circa 8 volte più veloce dell’Upgrade Wizard incluso in Visual Studio.
Devo dire che mi sono tolto una bella soddisfazione!
Siamo alla fine di questo lungo post, ma ne
seguiranno altri. Potete scoprire tutto (ma veramente tutto!) su VB Migration
Partner sul nuovo sito www.vbmigration.com, dove
troverete anche molto materiale sulla migrazione tout court nonchè il mio nuovo blog in inglese, tutto
dedicato alle problematiche di migrazione da VB6.
di Francesco Balena
|
Parte la serie dei seminari Microsoft Innovation Days, organizzati (quasi tutti) in collaborazione con Code Architects.
- Marco Bellinaso sarà domani 19 febbrario a Torino, e parlerà di Programmare per il web con ASP.NET e le Ajax extensions.
- Il sottoscritto sarà giovedi 22 febbrario a Padova, con una giornata dedicata alla Migrazione da Visual Basic 6 a Visual Basic.NET 2005.
- Il 17 aprile sarà la volta di Giuseppe Dimauro, che a Roma illustrerà le potenzialità di Windows Vista for Developers.
- La serie di seminari si conclude il 18 aprile a Roma, dove Marco Bellinaso mostrerà tecniche e trucchi dello Sviluppo con Microsoft Office System 2007.
|
Giovedì parlerò quindi a Padova di migrazione da VB6 a VB2005. Certo, è un argomento "vecchio" e molti lettori di questo blog probabilmente si chiederanno "come, ancora VB6?" La realtà è che sono ancora tantissime le aziende che NON sono ancora passate a .NET Framework, e spesso - ad essere onesti - per buoni motivi. La verità è che gli attuali strumenti di migrazione, ovvero l'Upgrade Wizard di Artinsoft (inserito in Visual Studio 2005) fanno un lavoro di qualità abbastanza discutibile (per usare un eufemismo), non perchè siano scritti male ma perchè la distanza tra i due linguaggi è davvero troppo ampia.
Nell'ultimo anno ho lavorato a tempo pieno sui problemi della migrazione di applicazioni VB6 e ho accumulato un bel po' di tecniche interessanti su come affrontare la migrazione, sia preparando adeguatamente il sorgente VB6 sia procedendo alla creazione di una libreria di supporto VB2005 che accorcia questa distanza tra i linguaggi. E ho anche una bella quantità di piccole e grandi "trucchi" per risolvere i tanti problemi che la migrazione crea, inclusi molti problemi che non ho mai trovato documentati in nessun libro o articolo.
di Francesco BalenaPurtroppo non riesco a stare dietro ai vari commenti a questo blog, e ogni tanto me ne perdo qualcuno davvero interessante. In particolare, non avevo letto subito quello di Pasquale Esposito a un mio post di fine Aprile. In quel post parlavo di una mia DLL per VB6 e chiudevo chiedendo ai lettori VB6 quando si sarebbero decisi a passare a .NET. Pasquale ha risposto con un commento lungo e esauriente, di cui riporto qui i pezzi salienti:
Ci chiedi quand'è che ci decidiamo a migrare a VB.NET. Vedi, io sono rimasto profondamente deluso dal passaggio al byte code operato dalla Microsoft. Da circa dieci anni produco software shareware che distribuisco su Internet o nei CD allegati a riviste di informatica (ultima pubblicazione: Soluzione Bilancio 1.0 recensito a pag. 97 di PC Magazine di maggio 2006). Ho provato a tastare il terreno distribuendo qualche piccolo applicativo in .NET e mi sono subito reso conto che i tempi non sono affatto maturi per passare a questa tecnologia, almeno per chi utilizza la metodologia shareware come canale di distribuzione. Lo affermo per almeno due motivi:
1) Non è per niente facile chiedere ad un utente che non ha il Framework installato sulla propria macchina di scaricarlo da Internet solo per far funzionare la propria applicazione: per chi non ha una connessione ADSL sognificherebbe effettuare un download di qualche ora. Inoltre, anche chi ha installato il SP2 di WinXP, non è detto che abbia scelto di includere il Framework. Conclusione: molto probabilmente, l'utente in questione sceglierà di rinunciare all'applicativo in .NET e cercherà qualcos'altro di meno problematico. In poche parole, il "DLL Hell" di VB6 ha lasciato il posto al ".NET Framework Hell" di VB.NET.
2) Programmare in byte code significa produrre software estremamente vulnerabile, non solo dal punto di vista del cracking ma perfino da quello del reverse-engineering. Basta pochissima esperienza per entrare in possesso del codice sorgente altrui ed è necessario ricorrere a buoni obfuscator di terze parti per proteggere le proprie applicazioni. Questi strumenti non soltanto sono alquanto costosi ma molto spesso fanno uso di codice nativo (anziché di byte code), rendendo la piattaforma .NET non più autosufficiente. Conclusione: non ritengo che programmare in byte code sia conveniente per chi produce shareware. Al contrario, un applicativo in VB6 può essere crackato soltanto da esperti e in nessun caso è possibile ottenere i sorgenti. Il byte code, quindi, non è adatto a fini commerciali. Molti programmatori VB6 non lo hanno mai preso in considerazione, altrimenti sarebbero passati a Java già molto tempo fa.
.....
L'unico motivo che mi potrebbe far pensare alla migrazione a VB.NET è il timore che, dopo Windows Vista, Microsoft possa abbandonare strategicamente la tecnologia COM a 32 bit. Spero davvero che ciò non accada perché ciò significherebbe costringere la gente a buttare via tutto il software attualmente in commercio. Insomma, VB6 è senz'altro lo strumento che utilizzerò nei prossimi 4-5 anni dato che i runtime di base saranno ancora presenti in Windows Vista (lo stesso Microsoft Anti-Spyware è stato progettato in VB6), dopodiché potrò considerare l'ipotesi di effettuare la migrazione.
.....
La richiesta che vorrei rivolgerti è questa: VB6 non è morto. E non è neanche obsoleto. Molti programmatori si g uadagnano da vivere con VB6 e vorrebbero che tu ti occupassi ancora di questo strumento. Come hai reso il tuo MsgHookX nuovamente disponibile online, così dovresti fare con tutta la tua produzione che riguarda VB6. Anzi, dovresti creare ancora per l'ambiente VB6. Infine, se puoi, riferisci alla Microsoft che esiste ancora un esercito di programmatori VB6 che sarebbe pronto ad acquistare una nuova versione unmanaged del suo strumento di sviluppo. Dal punto di vista commerciale, sarebbe senz'altro una mossa vincente!
Io trovo che l’opinione di Pasquale sia fondata, nel senso che se io facessi il suo lavoro (vendere shareware) potrei avere dei problemi a passare armi e bagagli a .NET. Pero’ ci sono alcune considerazioni da fare.
*) Le dimensioni del runtime: La stesso problema se lo ponevano i programmatori shareware 10 anni fa, quando confrontavano il runtime di VB6 con i piccoli eseguibili scritti in C. Ma allora c'è da chiedersi: perchè gli shareware-isti che lavorano in VB hanno avuto spesso più successo di quelli che lavorano in altri linguaggi come C ? La risposta, a mio avviso, è che scrivere un programma in VB richiedeva una frazione del tempo necessario a scriverlo in C quindi a parità di impegno è possibile creare programmi più potenti e ricchi di funzioni. (solo il Delphi può competere con VB quanto a produttività ) Forse non altrettanto veloci di quelli scritti in C, ma sufficientemente veloci per la maggior parte dei compiti.
Rapportiamo questa esperienza ad oggi: i 20M circa del framework sono circa 15 volte più grandi del runtime di VB6, pero' è anche vero che ADSL è almeno 15 volte più veloce del dialup, quindi la proporzione regge. Anzi, se i vostri clienti hanno la fibra ottica, neanche se ne accorgono. Insomma, chi tanti anni fa ha fatto la scelta di passare a VB infischiandosene delle dimensioni del runtime ha avuto ragione. Secondo me, lo stesso accade a chi oggi decide di passare a VB.NET
Certo, non tutti gli utenti hanno la ADSL, ma il ragionamento che farei io se fossi un autore shareware è: quanto mi interessano davvero questi utenti? i clienti migliori per il software e per i servizi sono le aziende e il power-user, e quelli l'ADSL ce l'hanno sempre. Se qualcuno non ha i 20€ al mese per pagare la connettività, difficilmente pagherebbe per i miei programmi. Gli unici che non rientrano in questo mio ragionamento sono le aziende e gli utenti che spenderebbero volentieri questi soldi, ma purtroppo non sono serviti da ADSL. Pero' oggettivamente si tratta di casi che diventeranno sempre più rari, e penso che in 2-3 anni saranno impossibili da trovare. (A parte il fatto che uno si puo' collegare a internet anche con una scheda UMTS...)
Allora, la domanda da porsi è: se sono un autore di software (shareware o non), vale la pena davvero continuare ad usare uno strumento che era eccezionale 10 anni fa ma adesso è decisamente superato? In questi anni io credo di avere dimostrato di saper fare davvero di tutto con il "vecchio Visual Basic", eppure oggi quando devo tornare a scrivere codice con VB6 mi sento un impedito. Non si tratta solo del linguaggio, ma anche dell'IDE e degli strumenti a corredo. Dopo pochi mesi con il VB.NET ero già molto più produttivo che in VB6 (che avevo usato per 10 anni). Oggi che conosco bene il .NET Framework credo di essere, mediamente, almeno tre volte più veloce. Ovvero scrivo un programma in un terzo del tempo che ci mettevo prima. Io vendo programmi principalmente ad aziende, ma anche se facessi shareware mi porre la stessa domanda: vale la pena rinunciare a questa enorme produttività per raggiungere qualche utente in più, che probabilmente non comprerebbe comunque il mio software?
*) Anche se il .NET Framework è ben pesante, non è strettamente necessario distribuirlo tutto con le proprie applicazioni. In teoria una applicazione WinForm ha bisogno di circa un terzo dell'intero framework. Anche se non mi sono mai interessato più di tanto alla questione, ho letto che ci sono dei programmi che sono in grado di comprire un eseguibile .NET e tutte e sole le librerie che utilizza , senza cioè richiedere una installazione completa del framework. Se le dimensioni del runtime fossero davvero un problema, proverei a fare qualche ricerca più approfondita su questi prodotti.
*) come lo stesso Pasquale fa notare, lo shareware si puo' diffondere anche con mezzi che non siano Internet, ad es. sui CD allegati alle riviste. Pero' è un dato di fatto che ci sono sempre meno riviste che allegano i CD, proprio perchè la maggior parte degli utenti ha una linea veloce e preferisce scaricare dalla Rete per essere sicuri di avere la versione più recente. Ad esempio, Microsoft Press, Mondadori e altri publisher importanti non allegano più i CD ai propri libri (a meno che il contenuto non superi i 30-40M) e loro prendono queste decisioni solo dopo analisi di mercato fatte per bene.
*) se fossi un programmatore VB6, a rendere ancora più semplice e più netta la mia decisione di passare a .NET c'è la considerazione che tutto quello che scrivo oggi con VB6 dovrà forzatamente essere buttato via tra qualche anno, vuoi perchè Microsoft non supporterà più VB, vuoi perchè i programmi e i controlli ActiveX potrebbero non funzionare bene con le prossime versioni di Windows. E' giusto fare pressione su Microsoft per evitare che cio' avvenga, ma le probabilità di fare rimangiare le loro decisioni sono prossime allo zero. Quindi è un piccolo "suicidio professionale" puntare tutto su questa speranza e nel frattempo fare finta che il mondo sia quello di dieci anni fa. Se consigliassi a qualcuno di continuare a scrivere codice VB6 sarei un vero incosciente.
*) Il discorso sul byte code: E' innegabile che un programma che possa essere decompilato facilmente pone dei seri problemi. Purtroppo .NET non offre una netta risposta a questo problema, ma solo mezze soluzioni (tipo installare sul server come servizio o sito asp.net). Quando Microsoft lanciò le prime beta di .NET cinque anni fa, questo aspetto mi sembrava davvero fondamentale, in grado addirittura di rallentare la diffusione del nuovo linguaggio.
Le cose sono andate diversamente, per fortuna. C'è da tenere presente che il problema del reverse-engineering è molto sentito da tutti i programmatori, non solo quelli che vendono shareware. Se uno mette sul mercato uno shareware e qualcuno ne fa il reverse engineering, è molto facile dimostrare che il nuovo programma è un clone. Basta decompilarli entrambi e mostrare a tutto il mondo il risultato, sottolineando che il proprio programma è stato messo sul mercato prima del clone. Certo non è efficace quanto una azione legale (che pure è possibile, ma costosa) ma nel mondo di Internet è sufficiente per perdere la reputazione. Certo, uno puo' fare il reverse engineering e poi modificare il codice, ma è facile mettere delle "trappole" in giro per il sorgente, ossia delle istruzioni che non fanno nulla e che chi ha clonato il programma non saprebbe giustificare. Per togliere queste trappole uno si deve studiare meglio tutto il sorgente, ma allora il lavoro del copiatore si complica.
A parte queste considerazioni, c'è da sottolineare il fatto che questo problema è sempre esistito nel mondo dello sviluppo software. Molti linguaggi che in passato hanno avuto successo erano dei byte-code decompilabili: ad esempio dBASE, il Clipper (spacciato come compilatore), Java, e lo stesso Visual Basic fino alla versione 4.0. E anche se il linguaggio era compilato, resta il fatto che i tracciati dei record e le tabelle dei database - ovvero un aspetto fondamentale delle applicazioni gestionali - sono sempre stati visibili e interpretabili. Se vuoi capire come funziona un gestionale, la prima cosa da fare è vedere come sono strutturate le sue tabelle del DB.
Insomma, non sto dicendo che il problema non esiste: sto dicendo che è meno serio di quello che molti programmatori credono.
Termino il post rispondendo direttamente a Pasquale: hai ragione a dire che VB6 non è morto. Ci sono moltissimi programmatori che ancora lo usano e il mio libro su VB6 continua a vendere abbastanza bene, addirittura meglio di alcuni libri su VB.NET (non dei miei libri su VB.NET, per fortuna! )
Pero' hai (molto) torto a dire che NON è obsoleto. Non si tratta di "rinnegare" il passato e soprattutto non dobbiamo parlare di VB come di un cagnolino a cui ci siamo affezionati, che non si vuole rottamare per ragioni sentimentali (ho letto anche interventi di questo tenore sui vari forum....). Quando è nato era eccezionale e per anni io ho campato (e bene!) con questo prodotto, ma se lo confronti con VB.NET (e con l'IDE di Visual Studio 2005) ti accorgi che il Visual Basic 6 è davvero morto e sepolto.
Ecco perchè non ho firmato alcuna petizione pro-VB6 e perchè sto facendo di tutto per convincere gli sviluppatori VB6 a passare il prima possibile a .NET: quando cominci a lavorare con la programmazione ad oggetti "vera", con gli attributi e reflection, i controlli di Windows Forms, le applicazioni ASP.NET e tutto il resto, il Visual Basic 6 ti sembra tanto ma tanto lontano.
NOTA: anche se non sviluppo più sotto VB6, forse cercando sul mio disco rigido troverei qualche cosa interessante che non ho ancora pubblicato. In tal caso sarà un piacere metterla a disposizione di tutti su questo sito. L'ho fatto per anni e posso continuare a farlo anche ora...
di Francesco BalenaIn Visual Basic 6 esiste una comodissima proprietà dell'oggetto App che permette di determinare se vi sono altre istanze in esecuzione della stessa applicazione. Manco a dirlo, questa proprietà non è mai stata migrata in VB.NET anche se, per fortuna, una applicazione scritta in VB 2005 può almeno usare l'evento StartupNextInstance:
' to display this code, open the Application page of the My Project designer and click the Application Events button
Namespace My Partial Friend Class MyApplication Private Sub MyApplication_StartupNextInstance( ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance ' another instance of this application has been launched End Sub End Class End Namespace
Il problema di questo approccio è che l'evento viene sollevato nella prima istanza della applicazione, non nella nuova istanza, quindi alla partenza una applicazione VB.NET non può sapere con certezza se è l'unica istanza o meno. In altre parole, può solo sapere se e quando un'altra applicazione parte, non se l'applicazione corrente è la prima e unica istanza in esecuzione.
In .NET 2.0 è stato introdotto il nuovo tipo System.Threading.Semaphore che permette di risolvere il problema in modo davvero elegante. Un semaforo è un oggetto che può essere incrementato e decrementato. La cosa interessante è che se il semaforo ha un nome, allora esso è condiviso da tutte le applicazioni .NET in esecuzione che richiedono un riferimento al semaforo con quel particolare nome. Basta quindi che l'applicazione crei alla partenza un semaforo con un nome univoco (ad es. un nome che include il percorso dell'eseguibile, che è quasi certamente univoco) e più applicazioni possono condividere il contatore interno al semaforo. L'unico problema che resta da risolvere è assicurarsi che il valore del semaforo sia correttamente ripristinato quanto l'applicazione termina, ma anche questo è facile da ottenere usando un metodo Finalize.
In aggiunta alla proprietà PrevInstance - che restituisce False se l'applicazione era l'unica istanza al momento della partenza - la seguente classe VB6App espone anche la proprietà InstanceCount, che restituisce il numero totale di istanze in esecuzione in quel momento, incluso quindi l'applicazione corrente. Ecco il codice della classe VB2005:
Class VB6App ' the default instance Private Shared DefValue As New VB6App
' the system-wide semaphore Private semaphore As System.Threading.Semaphore ' initial count for the semaphore (very high value) Private Const MAXCOUNT As Integer = 10000
Private Sub New() Dim ownership As Boolean = False ' create a unique name, but strip invalid characters Dim name As String = "VB6App_" & System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\", "") semaphore = New System.Threading.Semaphore(MAXCOUNT, MAXCOUNT, name, ownership) ' decrement its value semaphore.WaitOne() ' if we got ownership, this app has no previous instances m_PrevInstance = Not ownership End Sub
' the PrevInstance property returns True if there was a previous instance running ' when the default instance was created Private Shared m_PrevInstance As Boolean
Public Shared ReadOnly Property PrevInstance() As Boolean Get Return m_PrevInstance End Get End Property
' return the total number of instances of the same application that are currently running Public Shared ReadOnly Property InstanceCount() As Integer Get ' release the semaphore and grab the previous count Dim prevCount As Integer = DefValue.semaphore.Release() ' acquire the semaphore again DefValue.semaphore.WaitOne() ' eval the number of other instances that are currently running Return MAXCOUNT - prevCount End Get End Property
Protected Overrides Sub Finalize() ' increment the semaphore when the application terminates semaphore.Release() End Sub
End Class
Notate che questa classe contiene un metodo Finalize senza implementare IDisposable. Si tratta di uno dei casi speciali in cui è giusto violare il pattern Dispose-Finalize.
Per comprende come funziona la classe, basta ricordare che il metodo Release dell'oggetto Semaphore incrementa il valore interno, mentre WaitOne lo decrementa. L'unica accortezza da usare con questo codice è di testare la proprietà VB6App.PrevInstance il prima possibile, ad esempio nel metodo Sub Main o nell'evento Load del form principale, per dare la possibilità alla classe di conservare il suo valore alla partenza del programma. Lo stesso form potrebbe poi testare il valore di InstanceCount in uscita, ad esempio se è necessario eseguire un codice di cleanup quando l'ultima istanza della applicazione termina:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load If Not VB6App.PrevInstance Then ' open the common log file ' ... End If End Sub
Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing If VB6App.InstanceCount = 1 Then ' close the common log file. ' ... End If End Sub
Visto che l'utilità di questa classe, l'ho anche tradotta in C#:
public class VB6App { // the default instance private static VB6App DefValue = new VB6App(); // the system-wide semaphore private System.Threading.Semaphore semaphore; // initial count for the semaphore (very high value) private const int MAXCOUNT = 10000;
private VB6App() { // create a named (system-wide semaphore) bool ownership = false; // create the semaphore or get a reference to an existing semaphore
string name = "VB6App_" + System.Reflection.Assembly.GetExecutingAssembly().Location.Replace(":", "").Replace("\", ""); semaphore = new System.Threading.Semaphore( MAXCOUNT, MAXCOUNT, name, ref ownership); // decrement its value semaphore.WaitOne(); // if we got ownership, this app has no previous instances m_PrevInstance = !ownership; }
// the PrevInstance property returns True if there was a previous instance running // when the default instance was created
private static bool m_PrevInstance ;
public static bool PrevInstance { get { return m_PrevInstance ; } }
// return the total number of instances of the same application that are currently running
public static int InstanceCount { get { // release the semaphore and grab the previous count int prevCount = DefValue.semaphore.Release(); // acquire the semaphore again DefValue.semaphore.WaitOne(); // eval the number of other instances that are currently running return MAXCOUNT - prevCount; } }
~VB6App() { // increment the semaphore when the application terminates semaphore.Release(); } }
di Francesco BalenaIeri ho dovuto risolvere un piccolo problema: durante la migrazione di una applicazione VB6 era necessario scartare un metodo, in modo cioè che fosse "invisibile" a VB.NET. Questo era necessario perchè il metodo in questione svolgeva le stesse funzioni di un metodo nativo di .NET. Ovviamente, è sempre possibile cancellare il metodo dopo la migrazione, ma se i metodi sono tanti oppure se occorre lanciare più volte il wizard sullo stesso programma VB6 dopo rifiniture successive, allora la cosa diventa scocciante.
Apparentemente, non si puo' dire al migration wizard di ignorare dei pezzi di codice, però la soluzione è comunque molto semplice. Basta racchiudere il pezzo di codice in questione (interi metodi o anche singole istruzioni) in un blocco #IF WIN32. La costante di compilazione WIN32, di cui probabilmente pochi programmatori VB6 si ricordano, fu introdotta ai tempi di Visual Basic 4, l'unica versione di questo linguaggio ad essere disponibile nella versione a 16 e 32 bit, e questa costante (insieme a WIN16) permetteva appunto di definire blocchi di codice che venivano compilati solo in una delle due versioni, dando allo stesso tempo la possibilità di manutenere un unico sorgente per entrambe. In definitiva quindi, il seguente pezzo di codice VB6:
#If WIN32 Then Private Sub Do Something(ByVal n As Integer) ' ... End Sub #End If
viene correttamente migrato dal wizard nel seguente codice VB.NET
#If WIN32 Then Private Sub DoSomething(ByVal n As Short) ' ... End Sub #End If
ma poichè la costante WIN32 non è definita in VB.NET il pezzo di codice racchiuso tra #IF e #ENDIF sarà ignorato dal compilatore VB.NET.
Attenzione anche al fatto che se state migrando una applicazione VB6 che a sua volta è l'evoluzione di una applicazione scritta originariamente in VB4, è anche possibile che ci siano dei pezzi di codice che voi *volete* migrare in VB.NET, ma per quanto detto sopra queste porzioni non saranno poi eseguite in VB.NET. In tal caso dovrete cercare tutte le occorrenze di #IF WIN32 nel codice e cancellarle.
di Francesco BalenaMolti, se non la maggior parte, degli esempi di controlli Windows Forms che si trovano in giro per la rete contengono chiamate a codice unmanaged nelle DLL di Windows, in particolare uno o più metodi SendMessage per ovviare alle (pochissime) mancanze controlli del .NET Framework. Il problema è che un controllo del genere crea dei problemi quando l'applicazione viene eseguita in modalità ClickOnce, poichè richiede di modificare la CAS associata alla particolare applicazione.
Anche se questo problema non ha soluzione in generale, quando si intende inviare un messaggio al controllo che si sta subclassando è possibile fare a meno di una chiamata esplicita a SendMessage e usare invece il metodo protetto DefWndProc che viene ereditato dalla classe Control. Supponiamo ad esempioi di voler scrivere una ComboBox "enhanced", che espone la proprietà TopIndex, che imposta o restituisce l'indice del primo elemento visibile nella porzione di lista del controllo. Queste due operazioni si possono implementare inviando rispettivamente i messaggi CB_SETTOPINDEX e CB_GETTOPINDEX al controllo. Ecco come fare, usando il metodo DefWndProc invece di SendMessage:
Public Class ComboBoxEx Inherits System.Windows.Forms.ComboBox
Public Property TopIndex() As Integer Get Const CB_GETTOPINDEX As Int32 = &H15B Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_GETTOPINDEX Me.DefWndProc(m) Return m.Result.ToInt32() End Get Set(ByVal value As Integer) Const CB_SETTOPINDEX As Int32 = &H15C Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_SETTOPINDEX m.WParam = New IntPtr(value) Me.DefWndProc(m) End Set End Property
End Class
Incidentalmente, questa ComboBox enhanced vi può venire utile se state migrando applicazioni VB6. Infatti, la ComboBox e la ListBox di VB6 supportano la proprietà TopIndex, mentre in .NET solo la ListBox espone questa proprietà. Se avete del codice VB6 che utilizza la proprietà TopIndex di una ComboBox, l'approccio più semplice è sostituire il controllo con una ComboBoxEx.
di Francesco BalenaIn VB6 - e più in generale, nel mondo COM - un oggetto si distrugge semplicemente impostandolo a Nothing (ovviamente, nell'ipotesi in cui non vi siano altre variabili che puntano a quella particolare istanza). Il runtime di VB invoca il metodo Class_Terminate, se esiste un handler per tale evento, poi dealloca tutte le risorse assegnate all'oggetto, inclusa la mamoria. Questo processo è ricorsivo, quindi se l'oggetto possiede altri oggetti COM (ad es. una connessione ADO), anche quegli oggetti sono distrutti.
Sappiamo bene, invece, che in .NET impostare una variabile oggetto a Nothing non fa scattare alcun evento. Se l'oggetto implementa il metodo Finalize, tale metodo sarà chiamato dal garbage collector di .NET solo qualche tempo dopo. L'intervallo di tempo tra l'impostazione a Nothing e l'esecuzione del codice di cleanup contentuo nel metodo Finalize dipende da tanti fattori, e potrebbe arrivare anche a minuti o ore, se il programma non alloca molti altri oggetti e quindi non stressa il garbage collector.
Questa differenza nel comportamento tra VB6 e VB.NET diventa spesso un problema davvero serio nella migrazione delle applicazioni. Se ad esempio il codice nell'evento Class_Terminate chiude una connessione al database, oppure un file o una porta seriale, oppure cancella delle informazioni confidenziali, o chiude un form, allora il ritardo tra la distruzione "logica" dell'oggetto e la sua distruzione "fisica" può compromettere il funzionamento della applicazione stesso, dopo la sua migrazione verso il mondo .NET.
Purtroppo non è possibile ottenere "in automatico" che VB.NET si comporti esattamente come VB6 in questo caso, pero' possiamo fare qualcosa che riduce il problema. Questo è possibile, ancora una volta, grazie ai generics. Basta inserire il metodo seguente in un modulo, in modo da renderlo visibile a tutta l'applicazione:
Public Sub SetNothing(Of T)(ByRef obj As T) ' Dispose of the object if possible If obj IsNot Nothing AndAlso TypeOf obj Is IDisposable Then DirectCast(obj, IDisposable).Dispose() End If ' Decrease the reference counter, if it's a COM object If Marshal.IsComObject(obj) Then Marshal.ReleaseComObject(obj) End If obj = Nothing End Sub
A questo punto, possiamo fare un "search and replace" in tutto il codice VB.NET prodotto dal wizard di migrazione, sostituendo le istruzioni var = Nothing con SetNothing.(var). Ovviamente, questo tecnica non risolve tutti i problemi menzionati in precedenza, perchè funziona solo con le variabili esplicitamente messe a Nothing via codice, e non quelle che sono azzerato implicitamente quando escono dallo scope corrente.
Poichè si tratta di una sostituzione con parti variabili, sembrerebbe che non sia possibile farlo in modo davvero automatico, e invece non è così, perchè possiamo usare le feature di ricerca e sostituzione di Visual Studio basate sulle regular expression. (Per maggiori info, vedere questo post.) Infatti, basta attivare la ricerca per regular expression, inserire la stringa <{:i} = Nothing come stringa da ricercare, e la stringa SetNothing(\1) come stringa di sostituzione. Et voilà 
NOTA per i refrattari alle regex: la prima stringa dice di ricercare una variabile (:i) che si trova all'inizio di parola (<); le graffe in {:i} eseguono il tagging del nome dell'identificatore, in modo da poter poi usare il nome della variabile nel testo di sostituzione (mediante il placeholder \1). Tutto qui, non era mica difficile.
di Francesco BalenaIl tipo Date di VB6 e il tipo Date di VB.NET (o il tipo System.DateTime di .NET, il che è lo stesso) sono molto simili, eccetto che per un dettaglio importante: in VB6 le date sono conservate internamente con valori Double, la cui parte intera rappresenta il numero di giorni trascorsi dal 30-12-1899 (non chiedetemi perchè non dal 31 dicembre...), e la cui parte decimale rappresenta le ore, minuti, e secondi sotto forma di frazione di giorno.
Il wizard che migra le applicazioni VB6 in VB.NET non modifica in alcun modo le variabili di tipo Date però deve tenere conto del fatto che i programma VB6 eseguono spesso operazioni con le date sfruttando il formato di memorizzazione interno. Ad esempio, ecco come in VB6 si calcola la data corrispondente a sette giorni dalla data odierna:
Dim da As Date da = Now + 7 MsgBox da
Per migrare correttamente le operazioni aritmetiche sulle date, il wizard usa i metodi FromOADate e ToOADate della classe Date, in questo modo:
Dim da As Date da = Now da = System.Date.FromOADate(da.ToOADate + 7)
Non è un bel codice a vedersi, ma funziona. Anche in questo caso, però, è possibile applicare qualche trucchetto per ridurre al minimo le differenze sintattiche tra il codice VB6 e VB.NET e supportare l'aritmetica tra le date anche in VB.NET senza inserire nuovi metodi. Il meccanismo si basa sulla definizione di un nuovo tipo VB6Date, che conserva internamente un valore Date, che supporta le conversioni implicite da e verso i tipi Date, String, e Double, e che supporta anche gli operatori + e - con interi e Double:
<DebuggerDisplay( "{DateValue}")> _ Public Structure VB6Date
' the origin of all dates Private Shared ReadOnly StartDate As Date = New Date(1899, 12, 30)
' this is where the actual date value is stored Private DateValue As Date
' private constructor (Friend to be visible from the VB6DateFunctions module) Friend Sub New(ByVal value As Date) Me.DateValue = value End Sub
' override the ToString method, to support Console.Write, etc. Public Overrides Function ToString() As String Return DateValue.ToString() End Function
' convert to/from Date values Public Shared Widening Operator CType(ByVal value As VB6Date) As Date Return value.DateValue End Operator
Public Shared Widening Operator CType(ByVal value As Date) As VB6Date Return New VB6Date(value) End Operator
' convert to/from Integer values Public Shared Widening Operator CType(ByVal value As VB6Date) As Integer Return value.DateValue.Subtract(StartDate).Days End Operator
Public Shared Widening Operator CType(ByVal value As Integer) As VB6Date Return New VB6Date(StartDate.Add(New TimeSpan(value, 0, 0, 0))) End Operator
' convert to/from Double values Public Shared Widening Operator CType(ByVal value As VB6Date) As Double Return value.DateValue.Subtract(StartDate).Ticks / 864000000000 End Operator
Public Shared Widening Operator CType(ByVal value As Double) As VB6Date Return New VB6Date(StartDate.Add(New TimeSpan(CLng(value * 864000000000)))) End Operator
' convert to/from strings Public Shared Widening Operator CType(ByVal value As VB6Date) As String Return value.ToString() End Operator
Public Shared Widening Operator CType(ByVal value As String) As VB6Date Return New VB6Date(Date.Parse(value)) End Operator
' the + and - operators, with Integer and Double values Public Shared Operator +(ByVal value As VB6Date, ByVal days As Integer) As VB6Date Return New VB6Date(value.DateValue.Add(New TimeSpan(days, 0, 0, 0))) End Operator
Public Shared Operator -(ByVal value As VB6Date, ByVal days As Integer) As VB6Date Return New VB6Date(value.DateValue.Subtract(New TimeSpan(days, 0, 0, 0))) End Operator
Public Shared Operator +(ByVal value As VB6Date, ByVal days As Double) As VB6Date Return New VB6Date(value.DateValue.Add(New TimeSpan(CLng(days * 864000000000)))) End Operator
Public Shared Operator -(ByVal value As VB6Date, ByVal days As Double) As VB6Date Return New VB6Date(value.DateValue.Subtract(New TimeSpan(CLng(days * 864000000000)))) End Operator
End Structure
Notate l'attributo DebuggerDisplay, che permette di visualizzare la data contenuta in un oggetto VB6Date quando si sposta il cursore del mouse sul nome di una variabile (solo in Visual Studio 2005).
Per completare l'effetto, occorre scrivere anche dei rimpiazzi per le funzioni Now, Date (ovvero Today in VB.NET), e Time, in modo che anche queste funzioni restituiscano un oggetto di tipo VB6Date:
Public Module VB6DateFunctions Public ReadOnly Property Now() As VB6Date Get Return New VB6Date(Microsoft.VisualBasic.DateAndTime.Now) End Get End Property
Public ReadOnly Property [Today]() As VB6Date Get Return New VB6Date(Microsoft.VisualBasic.DateAndTime.Today) End Get End Property
Public ReadOnly Property Time() As VB6Date Get Return New VB6Date(Now - Today) End Get End Property End Module
A questo punto è possibile migrare il codice VB6 contenente operazioni aritmetiche sulle date semplicemente sostituendo il tipo della variabile che ospita tali valori:
' VB.NET code Dim da As VB6Date ' <-- the only modified statement da = Now + 7 MsgBox(da)
Con questo esempio non sto certo suggerendo di cambiare TUTTE le vostre variabili Date per usare il tipo custom VB6Date. Il mio obiettivo principale è soprattutto di mostrare come - con un po' di fantasia e soprattutto sfruttando le nuove feature di VB2005, in questo caso l'overloading degli operatori - sia spesso possibile ridurre la distanza tra due linguaggi simili ma subdolamente differenti come VB6 e VB.NET. Ovviamente il discorso potrebbe valere anche nella migrazione in .NET da altri linguaggi ancora (ad esempio Java).
In chiusura, mi piacerebbe fare un piccolo sondaggio:
1) quanti di voi sviluppano o anche solo manutengono applicazioni VB6 ? 2) se si, perchè l'applicazione non è stata ancora convertita in VB.NET ? 3) in generale, quali ritenete siano gli ostacoli principali alla conversione ? 4) e quali sono i principali pregi e difetti del wizard di migrazione fornito con Visual Studio ?
di Francesco BalenaContinuano le mie ricerche su come tradurre in modo indolore da VB6 a VB.NET, cercando di superare i molti limiti del wizard di migrazione incluso in Visual Studio. Il wizard in questione, che si comporta egregiamente quando deve convertire alcuni costrutti particolari (un esempio per tutto: On ... Goto), si arrende invece davanti ad alcune banalità che potrebbero essere superate facilmente con un po' di fantasia.
Per esempio, in VB.NET manca la funzione IsMissing (da cui il titolo sibillino di questo post...). Come i VB-isti ricorderanno sicuramente, questa funzione permette di capire se il chiamante ha effettivamente omesso un argomento passato a un parametro Optional. Ecco ad esempio un esempio di codice VB6, peraltro abbastanza comune, che il wizard non riesce a convertire correttamente:
Sub DoSomething(Optional ByVal value As Variant) If IsMissing(value) Then value = Now ' ... End Sub
I Variant sono convertiti in Object e in VB.NET è obbligatorio specificare il valore di default di un argomento opzionale, ma poichè non esiste un equivalente del valore "missing" in VB.NET, il wizard non sa bene come tradurre la funzione IsMissing. Eppure la soluzione è davvero semplice: basta definire una constante stringa "improbabile", usare tale stringa come valore di default per i valori opzionali, e scrivere poi una funzione IsMissing personalizzata che confronta il suo argomento con tale stringa. Ecco un esempio :
Public Module Functions
' The MissingValue6 constant (a very unusual string) Public Const MissingValue6 As String = ControlChars.VerticalTab & ControlChars.FormFeed & "MissingValue" & ControlChars.VerticalTab
' The IsMissing function Public Function IsMissing(ByVal value As Object) As Boolean Return MissingValue6.Equals(value) End Function
End Module
A questo punto il codice VB6 in questione puo' essere tradotto quasi alla lettera:
' this is VB.NET code Sub DoSomething(Optional ByVal value As Object = MissingValue6 ) If IsMissing(value) Then value = Now ' ... End Sub
Certo, è uno "sporco trucco", ma sempre meglio che dover rivedere tutte le procedure VB6 che usano argomenti opzionali. Non era difficile implementare qualcosa del genere, bastava pensarci, no?
di Francesco BalenaIn questo periodo mi sto interessando molto ai problemi di migrazione delle applicazioni VB6 verso VB.NET. Personalmente non ho realmente "migrato" nessuna delle mie applicazioni, perchè ho sempre preferito riscriverle da zero per sfruttare al massimo le feature di .NET e soprattutto perchè il wizard di conversione fa un lavoro oggettivamente penoso, producendo un codice brutto e non manutenibile. O meglio, il wizard fa un lavoro decente, ma il vero problema sono le incompatiblità tra VB6 e VB.NET.
Alcune di queste incompatibilità, tuttavia, possono essere superate con un po' di fantasia, soprattutto ora che VB2005 offre delle feature che prima non erano disponibili. Prendiamo ad esempio il problema "classico" degli array con indice diverso da zero, che hanno fatto dannare tutti gli sviluppatori VB6 alle prese con il porting verso VB.NET. Supponiamo di avere il seguente codice VB6
Dim arr(1 to 10) as Integer Dim i As Integer, prod As Integer, v As Variant For i = LBound(arr) To UBound(arr) arr(i) = i Next For Each v in arr prod = prod * v Next
Il wizard di conversione sostituirà l'indice iniziale 1 con l'indice 0, quindi l'array avrà un elemento in più. E' evidente che alla fine dell'esecuzione il valore di prod sarà zero, mentre avrebbe dovuto pari al fattoriale di 10!. Si tratta di errori molto subdoli, che costringono in pratica a ristudiare attentamente tutto il codice e a testare da zero l'applicazione. L'approccio manuale migliore è sicuramente quello di modificare la dichiarazione per "scalare" l'array in modo che il suo primo elemento reale abbia indice zero, e poi modificare TUTTI i riferimenti agli elementi dell'array, per tenere conto dello shift:
Dim arr(0 to 10-1) as Integer Dim i As Integer, prod As Integer For i = LBound(arr) To UBound(arr) arr(i - 1) = i Next
Ma anche questo approccio richiede molta attenzione e in certi casi non puo' essere usato, ad esempio quando l'array viene passato a una procedura che deve funzionare con array di qualunque tipo (e il cui codice non sa che deve scalare l'indice); in altri casi ancora, il programma potrebbe prendere delle decisioni in base al valore di LBound e UBound, che nella nuova versione è modificato.
La domanda che mi sono posto è: è possibile convertire quel codice in VB2005 senza doversi preoccupare di tutti questi problemi? La soluzione è stata relativamente semplice, grazie ai generics e a qualche trucchetto con l'ereditarietà.
' Base class
Public Class VBArrayBase Protected Friend lowerIndex As Integer Protected Friend upperIndex As Integer End Class
' One dimensional array of type T
Public Class VBArray(Of T) Inherits VBArrayBase Implements IEnumerable
Dim items() As T
Sub New(ByVal lowerIndex As Integer, ByVal upperIndex As Integer) Me.lowerIndex = lowerIndex Me.upperIndex = upperIndex ReDim items(upperIndex - lowerIndex) End Sub
Default Property Item(ByVal index As Integer) As T Get Return items(index - lowerIndex) End Get Set(ByVal value As T) items(index - lowerIndex) = value End Set End Property
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator Return items.GetEnumerator() End Function
End Class
Notate con quanta semplicità è possibile supportare i cicli For Each: basta restituire l'oggetto IEnumerator dell'array interno. Occorre a questo punto estendere le istruzioni LBound e UBound alla nuova classe VBArray. Per fare ciò dovete creare un modulo pubblico a parte, con queste istruzioni:
Public Module ArrayFunctionsVB6 Function LBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer Return Microsoft.VisualBasic.Information.LBound(arr, rank) End Function
Function UBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer Return Microsoft.VisualBasic.Information.LBound(arr, rank) End Function
Function LBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer If rank = 1 Then Return arr.lowerIndex Else Throw New IndexOutOfRangeException() End If End Function
Function UBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer If rank = 1 Then Return arr.upperIndex Else Throw New IndexOutOfRangeException() End If End Function End Module
Notate che il modulo deve contenere due overload per LBound e UBound, uno per gli array standard e uno per i nostri nuovi array. Purtroppo non è possibile avere un progetto che referenzia due moduli differenti (uno nella libreria di compatibiltà di VB e uno in una nostra DLL) e prenda due overload dello stesso metodo da entrambi i moduli. Se cio' avviene solo uno dei due metodi è visto dal programma principale.
Altro dettaglio interessante: Il codice dei metodi LBound e UBound ha bisogno di accedere ai membri Friend della classe VBArray(Of T), ma non è possible usare il tipo VBArray(Of T) nella dichiarazione di questi metodi. Ecco perchè ho dovuto definire una classe base VBArrayBase e fare ereditare VBArray(Of T) da quella classe base.
Grazie alla classe VBArray(Of T) e al modulo ArrayFunctionsVB6, possiamo convertire il codice VB6 cambiando soltanto la dichiarazione del vettore:
Dim arr As New VBArray(Of Short)(1, 10) ' Short invece che Integer
Tutto il resto del programma funzionarà esattamente come in VB6, incluso il ciclo For Each e le chiamate a LBound/UBound. Provare per credere! 
|
|
Feed di Blog2theMax
Cerca nel blog
Archivio
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 28 | 1 | 2 | 3 | 4 | 5 | 6 | | 7 | 8 | 9 | 10 | 11 | 12 | 13 | | 14 | 15 | 16 | 17 | 18 | 19 | 20 | | 21 | 22 | 23 | 24 | 25 | 26 | 27 | | 28 | 29 | 30 | 31 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
Categorie
Powered by: newtelligence dasBlog 1.7.5016.2
|