- Eval in .Net: compilare codice a run-time -
 
COSA SERVE PER QUESTO TUTORIAL
 Download | Chiedi sul FORUM | Glossario conoscenze basiche di VB .Net
Eseguire a run-time codice VB, C#, J# e JScript.Net in una stringa

EVAL IN .NET: COMPILARE A RUN-TIME
Colmare l'ultima differenza con i linguaggi di scripting tramite CodeDomProvider

La maggior parte dei linguaggi di scripting come PHP, Python e Javascript per via della loro versatilità permettono di eseguire stringhe di codice a run-time tramite la funzione comunemente chiamata eval. Ad esempio in Javascript:

eval("alert('Codice in una stringa!')");

Il fatto di essere interpretati permette di eseguire codice "al volo". I linguaggi .Net invece richiedono una compilazione e per questo il framework non mette a disposizione funzionalità come eval. Tuttavia la compilazione può anche essere eseguita a run-time e senza l'uso di tool da linea di comando (come cs.exe, vb.exe e così via). Esistono infatti per ogni linguaggio (o per lo meno per quelli forniti da Microsoft) una classe che eredita da CodeDomProvider (in System.CodeDom.Compiler) che mette a disposizione una serie di metodi per effettuare una compilazione a run-time senza complicazioni. Le classi per VB .Net e C# sono incluse in System.dll mentre per gli altri due linguaggi che qui tratteremo, JScript.Net e J#, è necessario aggiungere un riferimento rispettivamente a Microsoft.JScript.dll e VJSharpCodeProvider.dll. A questo punto sorge spontanea la domanda: perché non si parla di Managed C++ (chiamato anche C++.Net) e C++/CLI? Semplicemente perché pur esistendo la classe CodeDomProvider per questi linguaggi (in CppCodeProvider.dll) sia per Managed C++ (CppCodeProvider7) e C++/CLI (CppCodeProvider) i metodi per compilare da una stringa di testo non sono stati implementati.
Ricapitoliamo le classi che ci interessano:

  • Microsoft.CSharp.CSharpCodeProvider in System.dll per C#;
  • Microsoft.VisualBasic.VBCodeProvider in System.dll per Visual Basic .Net;
  • Microsoft.JScript.JScriptCodeProvider in Microsoft.JScript.dll per JScript .Net;
  • Microsoft.VJSharp.VJSharpCodeProvider in VJSharpCodeProvider.dll per J#;

Ricordiamo che J# non è normalmente incluso nel Framework .Net e per questo potrebbe essere necessario scaricarne le librerie.
Compilare codice è in sé molto semplice, sono sufficienti solo alcune di righe di codice:


Dim strAssemblyName As String = "EvalCode"
Dim cd As System.CodeDom.Compiler.CodeDomProvider
cd = New Microsoft.VisualBasic.VBCodeProvider()
Dim cmpParameters As New System.CodeDom.Compiler.CompilerParameters( _
    New String() {"System.dll"}, strAssemblyName & ".dll")
Dim cmrResults As System.CodeDom.Compiler.CompilerResults
cmrResults = cd.CompileAssemblyFromSource(cmpParameters, strCode)

Per prima cosa bisogna istanziare il CodeProvider corretto (tra quelli citati sopra), creare un oggetto CompilerParameters specificando una lista di librerie a cui fare riferimento e il percorso dove creare l'assembly (in questo caso EvalCode.dll), e infine richiamare il metodo CompileAssemblyFromSource passandogli l'oggetto CompilerParameters e il codice da compilare. Verrà restituito un oggetto CompilerResults con i risultati e gli errori della compilazione.
Si ricordi che eseguire codice inserito dall'utente è un'operazione molto rischiosa in quanto ha la possibilità di accedere in maniera completa al vostro sistema. Per proteggersi da questa evenienza è possibile limitare i permessi di esecuzione utilizzando un AppDomain con accessi ristretti, ma questa procedura trascende l'obiettivo dell'articolo.
Ovviamente il codice che gli si passa deve essere compilabile, ovvero non è possibile semplicemente compilare "Console.WriteLine(Now.ToString())", deve essere all'interno di un metodo a sua volta all'interno di una classe. Ecco una serie di contesti validi per i linguaggi presi in considerazione.
Per Visual Basic .Net:


Imports System
Namespace EvalCode
    Public Class Execute
        Private strOutput As String = [String].Empty
        Public Function Main() As String
            {0}
            Return strOutput
        End Function
        
        Public Sub o(str As String)
            strOutput &= str
        End Sub
    End Class
End Namespace

Per C#:


using System;
namespace EvalCode {
    public class Execute
    {
        private string strOutput = String.Empty;
        public string Main()
        {
            {0}
            return strOutput;
        }
        public void o(string str)
        {
            strOutput = strOutput + str;
        }
    }
}

Per JScript .Net:


import System;
package EvalCode {
    public class Execute
    {
        private var strOutput : String = String.Empty;
        public function Main() : String
        {
            {0}
            return strOutput;
        }
        public function o(str : String)
        {
            strOutput = strOutput + str;
        }
    }
}

Per J#:


package EvalCode;
import System.*;

public class Execute
{
    private String strOutput = String.Empty;
    public String Main()
    {
        {0}
        return strOutput;
    }
    public void o(String str)
    {
        strOutput = strOutput + str;
    }
}

Nota: "{0}" indica dove va inserito il codice; si noti inoltre che per ogni linguaggio è stato scritto, oltre a Main, un metodo o() che aggiunge una stringa all'output: esso può rivelarsi comodo da utilizzare.

A questo punto non resta che usare un po' di Reflection per caricare l'assembly in memoria e richiamare un metodo creato (nel nostro caso Main).


'Creiamo l'oggetto AssemblyName che ci servirà per identificare il nostro assembly
Dim asnName As New System.Reflection.AssemblyName(strAssemblyName)
asnName.CodeBase = strAssemblyName & ".dll"
asnName.Name = strAssemblyName

'Creiamo un'istanza dell'assembly
Dim asbInstance As Reflection.Assembly = Reflection.Assembly.Load(asnName)
'Namespace e nome della classe in cui si trova il codice da eseguire
Dim strClassName As String = "EvalCode.Execute"
'Creiamo un'istanza della classe sull'assemblt precedentemente richiamato
Dim objClassInstance As Object = asbInstance.CreateInstance(strClassName)
'Prendiamo il tipo della classe in questione
Dim typClass As Type = asbInstance.GetType(strClassName)

Dim strOutput As String
'Tramite il tipo richiamiamo il metodo Main (si tratta di una funzione dunque usiamo la binding-flag
'InvokeMethod) sull'istanza precedentemente creata (objClassInstance). Non passiamo parametri.
strOutput = CStr(typClass.InvokeMember("Main", _
    Reflection.BindingFlags.InvokeMethod, Nothing, objClassInstance, Nothing))
<< INDIETRO by VeNoM00