Home

 
 
 
 
 



 
 
 
 

 
 
 

 
 
 
 
 









Blog2theMax
Il blog del team di Code Architects

In genere mi piace molto la possibilità di estendere all’infinito la potenza delle mie applicazioni semplicemente aggiungendo un reference a qualche assembly che contiene le funzioni o i controlli che mi servono. Mi piace molto meno il fatto di dover distribuire numerose DLL con i miei eseguibili. In questo articolo illustrerò una tecnica per comprimere tutte (o quasi) le DLL satelliti di una applicazione Windows Forms e “fonderle” con l’eseguibile principale.

Tutti i file che vi servono sono contenuti in questo ZIP, che contiene l’utility AsmZip.exe (che si lancia dalla riga di comando) e due file sorgenti, Unzipper.cs e Unzipper.vb. Copiate l’utility AsmZip in una directory presente nel path di sistema, per poterla richiamare facilmente.

I passi da seguire
Ecco i passi necessari per implementare questa tecnica.

1) Aggiungere il file Unzipper.vb o Unzipper.cs al progetto principale della vostra applicazione, rispettivamente se lavorate in Visual Basic o Visual C#.

2) Nella procedura Main, aggiungete una istruzione che inizializza la classe AssemblyUnzipper (contenuta nel file sorgente aggiunto al punto precedente):
       ' (Visual Basic 2005)
       CodeArchitects.AssemblyUnzipper.Initialize()
       // Visual C# 2005
       CodeArchitects.AssemblyUnzipper.Initialize();
È di fondamentale importanza che questo codice sia eseguito prima di ogni altra istruzione del programma, in particolare prima di mostrare un form che contiene al suo interno dei controlli di cui non volete distribuire le DLL. Se lavorate in VB e l’applicazione ha un form di partenza (e quindi non avete a disposizione la procedura Main), dovete inserire queste istruzioni nel costruttore statico del form di partenza:
      Shared Sub New()
        CodeArchitects.AssemblyUnzipper.Initialize()
       End Sub

3) compilate la soluzione, ovviamente in Release mode visto che utilizzerete questa tecnica poco prima di consegnare l’eseguibile al vostro cliente.

4) aprite una finestra con il prompt dei comandi nella directory di output del programma, e lanciate l’utility AsmZip in questo modo:
             AsmZip main.exe *.dll
dove main.exe è il nome dell’eseguibile principale. Il comando precedente comprime *tutte* le DLL nella directory e mette i dati compressi in coda al file main.exe. Per comprimere solo alcune DLL tra quelle presenti nella directory dovete specificarne i nomi, come in questo esempio:
             AsmZip main.exe CodeArchitects*.dll Microsoft*.dll
(Ci possono essere dei buoni motivi per non comprimere alcune DLL usate dalla applicazione, come spiego più avanti.)

5) a questo punto potete cancellare tutte le DLL che avete compresso, perchè l’applicazione – grazie alla classe AssemblyUnzipper - è in grado di trovarle da sola in coda al proprio eseguibile, di decomprimerle, e di caricarle in memoria.

I vantaggi
Prima di procedere ad una spiegazione più dettagliata di come funziona questa tecnica, proverò a riassumere i suoi vantaggi:

a) deployment semplificato: dovete distribuite un minor numero di file (spesso il solo EXE principale)
b) applicazioni più robuste: non vi è il rischio che il cliente renda il programma inutilizzabile cancellando inavvertitamente una DLL
c) minore occupazione su disco (tutte le DLL sono compresse e accodate all'EXE principale)
d) la possibilità di “nascondere” i vostri piccoli segretucci, ad esempio quali controlli di terze parti avete utilizzato nella applicazione.
e) un maggior grado di protezione: il codice nelle DLL compresse non può essere decompilato, perlomeno non senza avere estratto e decompresso i singoli eseguibili.

È evidente che gli ultimi due punti non costituiscono una vera barriera per un programmatore anche solo un po’ esperto, se è davvero intenzionato a sapere quali DLL avete usato e a decompilarle. Per ottenere queste informazioni un hacker dovrà soltanto decompilare l’EXE principale, capire come funziona la classe AssemblyUnzipper, e scrivere un programmino che ne ripete il funzionamento ma salva su disco gli assembly compressi. In definitiva, questa tecnica permette di nascondere il vostro sorgente e le vostre DLL alla prima occhiata, ma non potete certo considerarla una tecnica di protezione dal reverse engineering.

L’utility AsmZip usa la classe GZipStream per comprimere le DLL originarie, quindi il grado di compressione che si può raggiungere con questa tecnica non è paragonabile a quello ottenibile con WinZip o WinRar, ma è comunque più che sufficiente, come mostra la figura.



Come funziona
Questa tecnica si basa sull’evento AssemblyResolve dell’oggetto AppDomain, che scatenato quando il .NET Framework deve caricare un assembly richiesto dalla applicazione. Gestendo opportunamente questo evento potete mettere in pratica tante piccole “magie” che non sarebbero possibili in alcun altro modo. Ad esempio, potreste caricare gli assembly satelliti di una applicazione da uno share di rete oppure da un campo blob di un database.

La classe AssemblyUnzipper, invece, usa questo evento per cercare l’assembly richiesto da uno stream compresso che è stato accodato all’eseguibile del programma principale.
      // the handler for AssemblyResolve event
      static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e)
      {
         // find the assembly with given name, cause error if not found
         AssemblyInfo info = null;
         if ( AsmInfos.TryGetValue(e.Name, out info) )
            return ExtractAssembly(info);
         // signal error
         Debug.WriteLine("Failed to uncompress assembly " + info.Name);
         return null;
      }
Ciascun oggetto AssemblyInfo tiene traccia di dove, nel file EXE, è memorizzato ciascuna DLL compressa. Il dictionary AsmInfos permette di trovare subito le informazioni associate ad una DLL con un certo nome; questo dictionary è creato nel metodo Initialize, alla partenza del programma, e viene poi usato ogni volta che l’applicazione richiede una DLL.

Non spiegherò nel dettaglio come funziona il codice, perchè il sorgente è commentato in modo adeguato, sia nella versione VB che C#.

I limiti
Ho provato questa tecnica con numerose applicazioni Windows Form, senza alcun problema. Il limite principale deriva dal fatto che gli assembly caricati in questo modo hanno la proprietà Location nulla, ma se non utilizzate reflection per esplorare le proprietà degli assembly non ve ne accorgerete neanche. Ad esempio, se la vostra applicazione carica gli assembly dinamicamente da una certa directory, magari per esplorarne gli attributi, è evidente che quel codice non può funzionare se le DLL sono state compresse e cancellate. In tal caso dovrete evitare di processare queste DLL con AsmZip.

La classe AssemblyUnzipper funziona solo con applicazioni Windows Forms. Da quanto ne so, è possibile utilizzare l’evento AssemblyResolve anche con applicazioni ASP.NET, ma non è possibile usare la mia classe in quel contesto. In realtà, i problemi che questa tecnica risolve non sono molto sentiti in ambito ASP.NET, quindi non credo che abbia senso preparare una versione per applicazioni di quel tipo.

L'unico altro limite è che questa tecnica funziona con le DLL, ma non con l'EXE principale. Se avete un EXE di grosse dimensioni che richiamo poche piccole DLL, il vantaggio che ne potete ricavare è molto limitato. In tal caso, potete comunque raggiungere il massimo grado di compressione "complessiva" spostando i form dall’eseguibile principale in una DLL, e comprimendo poi tale DLL con AsmZip. In teoria, l’EXE di partenza dovrebbe contenere solo lo splash screen (se ne avete uno), e poi dovrebbe caricare il form principale dalla DLL che contiene l’applicazione vera e propria. In questo modo è spesso possibile ottenere un fattore di compressione complessivo superiore al 60 per cento.

Nota: nella prima implementazione di questa tecnica ero riuscito a comprimere anche l'EXE. In tal caso accodavo poi tutti i dati compressi a un piccolo eseguibile "stub" che aveva il solo compito di fare partire l'eseguibile vero e proprio. Dopo alcune prove, però, mi sono reso conto che i problemi che si venivano a creare erano superiori ai vantaggi, per cui ho deciso di utilizzare il metodo descritto in questo articolo.

 

 
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