xplayn > share your experience     
RSS Feed Login     Password        » Registrati «
  
          Home
  Team & Mission
          News
  Development
  Database
          Pubblicazioni
  Articoli
  Tips
  IMHO
          Risorse
  Guide/Manuali/Tools
  Glossario SQLServer
  Collabora con noi
          Blogs
  Maurizio Tammacco
  Francesco Quaratino
  Renzo Pampani
          Formazione
  Database
 
                Partners
      http://www.dotnetside.org
 
 
Pubblicazioni »
Esecuzione di codice managed come script
La classe System.CodeDom.Compiler.CodeDomProvider è una classe astratta dalla quale derivano classi specifiche utilizzate dal .NET Framework per implementare i compilatori dei vari linguaggi da esso supportati. Il compilatore del linguaggio C#, ad esempio, è basato sulla classe Microsoft.CSharp.CSharpCodeProvider che eredita dalla classe astratta CodeDomProvider e permette di accedere ad una istanza del compilatore C# per la gestione del codice sorgente in detto linguaggio, così come il compilatore del linguaggio Visual Basic .NET è basato sulla classe Microsoft.VisualBasic.VBCodeProvider. Utilizzando tali classi è possibile compilare il codice sorgente, proveniente ad esempio da un file di script, per generare ed eseguire codice managed direttamente in memoria oppure creando un assembly. Ciò si rileva molto utile quando è necessario provare il funzionamento di porzioni di codice sorgente senza dover creare una soluzione da salvare su disco, come ad esempio accade se si utilizza l'ambiente Visual Studio .NET, oppure per fornire ad una applicazione la possibilità di integrazioni software da implementare a livello di scripting. Il primo passaggio consiste nel creare una istanza del compilatore C# oppure VB .NET rispettivamente attraverso la classe CSharpCodeProvider e VBCodeProvider, e di ottenere quindi un oggetto ICodeCompiler che rappresenta una interfaccia la cui implementazione da parte dello specifico compilatore permette di compilare dinamicamente il codice sorgente. E' opportuno inoltre creare una istanza della classe CompilerParameters per impostare le opzioni di compilazione appropriate.
 
C#
// questo codice utilizza le seguenti istruzioni using
// using Microsoft.CSharp;
// using System.CodeDom.Compiler;
CSharpCodeProvider CSharpProvider = new CSharpCodeProvider();
ICodeCompiler Comp = CSharpProvider.CreateCompiler();
CompilerParameters CompParameters = new CompilerParameters();
VB .NET
' questo codice utilizza le seguenti istruzioni Imports
' Imports Microsoft.VisualBasic
' Imports System.CodeDom.Compiler
Dim VBNetProvider As New VBCodeProvider
Dim Comp As ICodeCompiler = VBNetProvider.CreateCompiler
Dim CompParameters As New CompilerParameters

Alcune di queste opzioni permettono ad esempio di generare un eseguibile oppure una dll, di includere i simboli di debug, di creare un assembly in memoria o di produrre un file su disco (in questo caso è possibile assegnare un nome specifico al file attraverso la proprietà OutputAssembly). Una proprietà importante della classe CompilersParameters è ReferencedAssemblies, ovvero un oggetto di tipo collezione contenente la lista degli assemblies impostati come riferimento dell'assembly in compilazione. Occorre prevedere almeno gli assembly di uso più comune, altrimenti l'utilizzo di una classe senza il riferimento all'assembly che la contiene produce un errore.

C#
// questo codice utilizza la seguente istruzione using
// using System.CodeDom.Compiler;
// sarà generato file .EXE in memoria con i simboli di debug caricati CompParameters.GenerateExecutable=true;
CompParameters.GenerateInMemory = true;
CompParameters.IncludeDebugInformation = true;
// aggiunta delle referenze
CompParameters.ReferencedAssemblies.Add("System.Security.dll"); CompParameters.ReferencedAssemblies.Add("System.dll"); CompParameters.ReferencedAssemblies.Add("System.Data.dll"); CompParameters.ReferencedAssemblies.Add("System.Xml.dll"); CompParameters.ReferencedAssemblies.Add("System.Drawing.dll"); CompParameters.ReferencedAssemblies.Add("System.Management.dll");
VB .NET
' questo codice utilizza la seguente istruzione Imports
' Imports System.CodeDom.Compiler
' sarà generato file .EXE in memoria con i simboli di debug caricati CompParameters.GenerateExecutable = True
CompParameters.GenerateInMemory = True
CompParameters.IncludeDebugInformation = True
' aggiunta delle referenze
CompParameters.ReferencedAssemblies.Add("System.Security.dll") CompParameters.ReferencedAssemblies.Add("System.dll") CompParameters.ReferencedAssemblies.Add("System.Data.dll") CompParameters.ReferencedAssemblies.Add("System.Xml.dll") CompParameters.ReferencedAssemblies.Add("System.Drawing.dll") CompParameters.ReferencedAssemblies.Add("System.Management.dll")
A questo punto è possibile leggere il codice sorgente da compilare, tipicamente da un file di testo, e costruire l'entry point dell'assembly insieme al metodo Main che sarà invocato dinamicamente:

C#
// questo codice utilizza la seguente istruzione using
// using System.IO;
// using System.Text;
// lettura del codice sorgente e costruzione dell'entry point scripter e del membro Main: StreamReader sr =new StreamReader( fileName);
string Line, CodeExec;
string Header = "namespace CSharpScript\n" + "{\n" + "public class scripter\n" + "{\n" + 
          "public static void Main()\n" + " {\n";
StringBuilder SourceCode = new StringBuilder();
StringBuilder Using = new StringBuilder();
while ( (Line = sr.ReadLine()) != null)
{
     if (Line.Equals("")) continue;
     // lettura della direttiva using
     if (Line.ToLower().StartsWith("using") && Line.IndexOf("(") < 0)
        {
           Using.Append(Line);
         }
     else
        {
           SourceCode.Append(Line);
        }
}
CodeExec = Using.ToString() + "\n" + Header + "\n" +
               SourceCode.ToString() + "\n}\n}\n}\n"; sr.Close();

VB .NET
' questo codice utilizza le seguenti istruzioni Imports
' Imports System.IO
' Imports System.Text
' Imports System.Windows.Forms
' lettura del codice sorgente e costruzione dell'entry point scripter e del membro Main:
Dim sr As New StreamReader(fileName)
Dim Line, CodeExec As String
Dim Header As String = "public class scripter" & _ ControlChars.CrLf &  _
        ControlChars.CrLf & _ "public shared sub Main()" & ControlChars.CrLf
Dim SourceCode As New StringBuilder
Dim Import As New StringBuilder
Line = sr.ReadLine()
Do While Not (Line Is Nothing) 
    ' lettura della direttiva imports 
    If Line.ToLower().StartsWith("imports") Then
         Import.Append(Line)
    Else
         SourceCode.Append(Line & ControlChars.CrLf)
   End If
   Line = sr.ReadLine()
Loop
CodeExec = Import.ToString() & ControlChars.CrLf & Header _
        & ControlChars.CrLf & SourceCode.ToString() & _
        ControlChars.CrLf & "End Sub " & ControlChars.CrLf _
        & "End Class"
sr.Close()

Per compilare il codice è necessario invocare il metodo CompileAssemblyFromSource esposto dall'interfaccia ICodeCompiler passandogli l'oggetto CompParameters creato in precedenza e la stringa contenente il codice. Questo metodo restituisce un oggetto CompilerResults che permette di verificare se la compilazione ha prodotto errori o avvertimenti mediante la collection Errors, la quale espone proprietà per individuare la riga e la colonna contenente l'istruzione che ha prodotto l'errore, il numero dell'errore stesso e la sua descrizione, oltre alla proprietà HasErrors che consente immediatamente di verificare se è presente almeno un errore:

C#
// questo codice utilizza la seguente istruzione using
// using System.CodeDom.Compiler;
// compilazione del codice sorgente:
CompilerResults CompResults = Comp.CompileAssemblyFromSource(
                 CompParameters, CodeExec);
// verifica se ci sono errori di compilazione
if (CompResults.Errors.HasErrors )
   {
       foreach( CompilerError Err in CompResults.Errors)
             {
                 if (! Err.IsWarning)
                   {
                         Console.WriteLine("\nErrore riga {0} colonna {1} \n\t" + "Errore  
                                      numero:  {2} {3}", Err.Line.ToString(), Err.Column.ToString(), 
                                      Err.ErrorNumber.ToString(), Err.ErrorText);
                    }
              }
   }

VB .NET
' questo codice utilizza le seguenti istruzioni Imports
' Imports System.CodeDom.Compiler
' compilazione del codice sorgente:
Dim CompResults As CompilerResults
CompResults = Comp.CompileAssemblyFromSource(CompParameters, _ CodeExec)
' verifica se ci sono errori di compilazione
If CompResults.Errors.HasErrors Then
    For Each Err As CompilerError In CompResults.Errors
           If Not Err.IsWarning Then
               Console.WriteLine(ControlChars.CrLf & "Errore " & _ "riga {0} colonna {1}" _
                    & _ ControlChars.CrLf & ControlChars.Tab & _ "Errore numero: {2} _ 
                   {3}",Err.Line.ToString, Err.Column.ToString, _
                    Err.ErrorNumber.ToString, Err.ErrorText)
           End If
    Next
End If

Nel caso di compilazione terminata correttamente si effettua un ciclo sui tipi che appartengono all'assembly appena compilato e si ricava un riferimento al tipo "scripter" che costituisce l'entry point dell'assembly e, mediante l'uso di Reflection, si invoca dinamicamente il metodo Main:

C#
// questo codice utilizza la seguente istruzione using
// using System.CodeDom.Compiler;
// estrazione dell'entry point 'scripter'
Type t;
for (int i = 0; i<= CompResults.CompiledAssembly.GetTypes().Length -1; i++)
     { 
        if (CompResults.CompiledAssembly.GetTypes()[i].Name == "scripter")
            {
               t = CompResults.CompiledAssembly.GetTypes()[0];
               break;
             }
      }
// invocazione dinamica del membro Main
t.InvokeMember("Main", System.Reflection.BindingFlags.InvokeMethod, null , null , null);

VB .NET
' questo codice utilizza le seguenti istruzioni Imports
' Imports System.CodeDom.Compiler
' Estrazione dell'entry point "scripter"
For i As Integer = 0 To CompResults.CompiledAssembly.GetTypes().Length - 1
      If CompResults.CompiledAssembly.GetTypes()(i).Name = "scripter" Then
          t = CompResults.CompiledAssembly.GetTypes()(0)
          Exit For 
      End If
 Next
' invocazione dinamica del membro Main
t.InvokeMember("Main",  System.Reflection.BindingFlags.InvokeMethod,  _
        Nothing, Nothing, Nothing)
Infine, è possibile registrare nel sistema l'applicazione impostando una particolare estensione di file, ad esempio "cssc" per indicare uno script in linguaggio C#, oppure "vbsc" per uno script in Visual Basic .NET. In tal modo attraverso un semplice doppio click sul file con tale estensione sarà eseguito il codice in esso contenuto, in modo simile ad un file di script Visual Basic.
 
Autore E-Mail Web Site Data
Maurizio Tammacco maurizio@xplayn.org www.xplayn.org 27/05/2005
      ©Copyright 2005 - xplayn.org