CodeDom
De la forma dinámica de hacer Código Dinámico
Por Willy Marroquín
Descargar ejemplos de este artículo (39 KB).
Contenido
Introducción
Un poco de historia ...
El siguiente ejemplo sirve para darte cierta idea de lo que hace:
Inside CodeDom (Code Document Object Model)
El NameSpace System.CodeDom
El código
Introducción
En algunas ocasiones, si se observa con detenimiento la manera como se ha construido software desde su inicio, ha evolucionado poco (por no decir casi nada) a la programación por automatización; es decir se define una algoritmia para un problema, se codifica, se compila y si aparece una nueva característica se codifica la misma y de nuevo se recompila, y así en lo sucesivo.
Dentro de las múltiples características existentes en el Framework, no podía faltar un NameSpace que permitiera tomar las riendas del sistema de compilación propiamente dicho, con el fin de generar y compilar código de manera dinámica. Si se ve con detenimiento esta postura, bien puede ser un primer paso hacia una forma de construcción distinta de aplicaciones. En las siguientes líneas, daremos un vistazo a este maquinar. Comentaremos un poco de CodeDom con VB .NET.
Un poco de historia ...
En versiones previas de Visual Basic, lo más cercano a la generación dinámica de código estaba representado por el Objeto VBIDE.VBE, que permitía leer, contar y escribir líneas de código de un componente en tiempo de ejecución. Esta técnica, bastante útil por cierto, se basaba en EOM (Extensibility Object Model).
El siguiente ejemplo sirve para darte cierta idea de lo que hace:
Esto asumiendo la existencia de dos cajas de texto llamadas TxT_Prj y TxT_Component 'en un proyecto VB 6.0 para Winforms que permitiera leer la cantidad de líneas de 'un componente, como una reflexión primitiva que además permitiría compilar. Option Explicit On Public VBInstance As VBIDE.VBE Private Sub cmdCountLines_Click() Dim objVBComponent As VBComponent objVBComponent = _ VBInstance.VBProjects.Item(Txt_Prj.Text).VBComponents.Item(TxT_Component.Text) MsgBox(objVBComponent.CodeModule.CountOfLines) End Sub
Figura 1: Arbol de grado 2. Volver al texto.
Inside CodeDom (Code Document Object Model)
Dejando de lado la nostalgia, y como indicábamos al comienzo, CodeDom toma al compilador "por el mango”.
CodeDom, utiliza una ya conocida fórmula que es la de basarse en árboles de grado dos. Los árboles de grado 2 (o binarios, tal como se les conoce de forma común; ver Figura 1) se definen como un conjunto finito de elementos (nodos) que bien está vacío o bien está formado por una raíz con dos árboles binarios disjuntos, llamados subárbol izquierdo y subárbol derecho de la raíz.
A. Representación típica de árbol.
B. Este se compone de Nodos que son piezas conceptuales para almacenamiento.
C. Nodo Padre o Nodo de Jerarquía Uno.
D. Representación de Nodo Raíz como parte de un árbol de grado 2 extensible.
E. Para localizar datos en un árbol este debe ser Cruzado. Al ser Cruzado se aplica algún algoritmo específico para la ubicación de datos en los Nodos de árbol.
Resulta importante entender estos fundamentos de árboles para poder entender de paso el NameSpace de CodeDom, pues justamente éste representa un tradicional árbol nivel 2 en memoria como los que alguna vez nos enseñaron en la Universidad ... Hace muchas líneas de código.
El NameSpace System.CodeDom
Para representar el código fuente, los elementos de System.CodeDom se vinculan entre sí formando una estructura de datos conocida como CodeDomGraph, que modela la estructura de parte del código fuente del cual se hará rendering. Adicionalmente, cada árbol existente de código a procesar se conoce como una Unidad de Compilación (CompileUnit).
Para tener una perspectiva clara del NameSpace, puedes referirte a ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.3082/cpref/html/frlrfsystemcodedomhierarchy.htm en la ayuda de Visual Studio 2003.
Pensemos que se quiere hacer Code Rendering a las siguientes líneas:
Namespace Samples Imports System Public Class Hello Public Say Sub New() Console.WriteLine("Hello World!") End Sub End Class End Namespace
Esto representa el CodeGraph mostrado en la Figura 2.
Figura 2: Captura de pantalla de Vedex. Volver al texto.
Esto demuestra que CodeDom es análogo a los árboles de nivel 2. Las unidades de compilación (CompileUnit) son trabajadas por tres interfaces que serán las que hagan el trabajo duro.
ICodeParser: Esta interfaz se encarga de analizar el código recibido desde una unidad de compilación (CompileUnit) para colocarlo en Memoria.
ICodeGenerator: Esta interfaz lee las salidas generadas por ICodeParser y es quien genera el código fuente en el lenguaje deseado.
ICodeCompiler: Esta interfaz finalmente genera a nivel físico el ensamblado. Se suele combinar con System.CodeDom.Compiler.CompilerParameters, que asigna parámetros de compilación, de la misma forma que se reciben los parámetros de compilación por línea de comandos.
Más acción (compilación de ensamblados on-the-fly)
Pues ya basta de hablar. Vamos al código. En esta parte lo que haremos será tocar el primer nivel de utilización de CodeDom en la emisión dinámica de ensamblados y su ejecución de métodos. El proceso se divide en 4 partes.
Para demostrarlo crearemos un proyecto Winforms llamado ICodeCompiler y dentro del subdirectorio Bin de éste colocaremos un archivo llamado “CodigoaLeer.Txt” que contendrá el código a ejecutar (Ver Figura 3 y Figura 4).
Figura 3: . Volver al texto.
Figura 4: . Volver al texto.
El código
El siguiente es el código que utilizaremos para el render y está comentado por bloques:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load '************************************************************************************** 'Paso A : Creación de Proveedor, Interfaz de Compilación, parámetros, ' Ensamblado, y String '************************************************************************************** Dim VBP As New VBCodeProvider Dim ICC As System.CodeDom.Compiler.IcodeCompiler Dim Parametro As New System.CodeDom.Compiler.CompilerParameters Dim Ensamblado As System.Reflection.Assembly Dim SB As New System.Text.StringBuilder -------------------------------------------------------------------------------- '************************************************************************************** 'Paso B : Asignación de parámetro de Compilación, GenerateInMemory, GenerateExecutable, ' OutputAssembly, IncludeDebugInformation '************************************************************************************** With Parametro .GenerateInMemory = True '.GenerateExecutable = True .MainClass = "Prueba" .OutputAssembly = "ElResultado.Dll" .IncludeDebugInformation = False End With '************************************************************************************** 'Paso C : Se leen las referencias de los ensamblados de los dominios de aplicación ' y se agrega a la colección de referencias de ensamblados al objeto parámetro '************************************************************************************** For Each Ensamblado In AppDomain.CurrentDomain.GetAssemblies Parametro.ReferencedAssemblies.Add(Ensamblado.Location) Next Try 'Llamar al archivo de ejemplo y con el codigo a Compilar Dim Read As System.IO.TextReader Read = New System.IO.StreamReader(Application.StartupPath & "\CodigoaLeer.Txt") SB.Append(Read.ReadToEnd) Read.Close() Catch err As Exception 'Por si algo sale mal =) MessageBox.Show(err.Message & " " & err.StackTrace) End Try '************************************************************************************** 'Paso D :Generación del Ensamblado '************************************************************************************** 'La interfaz se puebla desde el compilador del provider del lenguaje ICC = VBP.CreateCompiler Dim Results As System.CodeDom.Compiler.CompilerResults Results = ICC.CompileAssemblyFromSource(Parametro, SB.ToString) 'El objeto Run lo utilizaremos para atrapar el resultado del objeto de Compilación 'y no tener que hacer el llamado posterior con Reflexion, sino de forma directa Dim RunObj As New Object Dim vArgs() As Object RunObj = Results.CompiledAssembly.CreateInstance("elnamespace.prueba", _ False, Reflection.BindingFlags.CreateInstance, Nothing, vArgs, Nothing, Nothing) If Not RunObj Is Nothing Then 'llamar el metodo RunObj.MostarSaludo() Else MessageBox.Show("Fallo la compilación y el objeto results quedo vacío") End If End Sub
Tras ejecutar este código se llama al único método existente que vino desde el archivo de texto, tal como se muestra en la Figura 5 y Figura 6 .
Figura 5: Llamado del método MostrarSaludo(). Volver al texto.
Figura 6: El resultado es el ensamblado llamado ElResultado.dll, asignado en el Paso A del listado general del ejercicio. Volver al texto.
Bien pueden imaginar que este es sólo el comienzo de esta propuesta. En el próximo artículo hablaremos de la generación dinámica de código fuente para que éste a su vez sea compilado y ejecutado, o dicho de otra forma, subiremos un peldaño más ...
Willy Alexánder Marroquín ha trabajado 8 años en la construcción de software de manera profesional y está especializado en el desarrollo sobre la plataforma .net. Ha participado en varios proyectos que han sido galardonados a nivel internacional por Microsoft. Cuenta con las certificaciones MCP, MCSD, MCT y MCAD. Su otro frente son las Ciencias Humanas; está especializado en filosofía Nietzscheana y es estudiante de Psicología Social en la Universidad Nacional de Colombia. |