Home

 
 
 
 
 



 
 
 
 

 
 
 

 
 
 
 
 









Blog2theMax
Il blog del team di Code Architects

In 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.

 

12/16/2007 11:44:27 AM (GMT Standard Time, UTC+00:00) #  | Comments [2] | 

VB 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.

11/27/2007 1:20:44 PM (GMT Standard Time, UTC+00:00) #  | Comments [2] | 

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.

2/18/2007 10:15:07 AM (GMT Standard Time, UTC+00:00) #  | Comments [0] | 

Purtroppo 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...

6/12/2006 8:07:06 PM (GMT Daylight Time, UTC+01:00) #  | Comments [0] | 

In 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(); 
   }
}

 

5/27/2006 11:32:35 AM (GMT Daylight Time, UTC+01:00) #  | Comments [0] | 

Ieri 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.

4/29/2006 8:03:57 AM (GMT Daylight Time, UTC+01:00) #  | Comments [0] | 

Molti, 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.

4/28/2006 12:35:06 PM (GMT Daylight Time, UTC+01:00) #  | Comments [0] | 

In 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.

 

2/14/2006 12:55:33 PM (GMT Standard Time, UTC+00:00) #  | Comments [0] | 

Il 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 ?

2/9/2006 12:51:24 PM (GMT Standard Time, UTC+00:00) #  | Comments [0] | 

Continuano 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?

2/8/2006 9:31:57 AM (GMT Standard Time, UTC+00:00) #  | Comments [0] | 

In 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! :-)

1/29/2006 2:44:49 PM (GMT Standard Time, UTC+00:00) #  | Comments [0] | 
 
Feed di Blog2theMax
RSS 2.0 | Atom 0.2
Cerca nel blog
Archivio
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Categorie

Powered by: newtelligence dasBlog 1.7.5016.2

 ©2004-2005 Code Architects S.r.l. - All rights reserved  Sign In