Compilar código C# en tiempo de ejecución.
Hola Geeks!
Hoy vamos a ver una herramienta interesante, a la vez que molón. Se trata de poder ejecutar código en tiempo real. Mientras tu App esta ejecutándose, puedes mandar a que genere otra .exe/.dll. Se trata de CSharpCodeProvider,cual nos da una instancia al compilador y generador de código de C#. Esta herramienta es bastante útil pero en mi opinión es para App's simples. Cuales creo que podemos calificarlas en:
- Tutoriales web simples: podrías crear una sencilla App, donde puedes hacer copy/paste en un TextBox el código que quieres generar de un ejemplo de la web. No teniendo que abrir el Visual Studio.
- Funciones definidas por el usuario: Algunas funciones simples, donde podrías pasar parámetros también.
Empezamos con un HelloWorld App.
1. Creamos un nuevo proyecto de Consola por ejemplo con el nombre de: Demo.CSharpCodeProviderSample.
2. Ahora la parte más importante. Creamos nuestro código para ejecutarlo. Abrimos en NotePad++ o cualquier otro y copiamos este código. Finalmente guardamos como fichero .cs en el Escriorio con nombre HelloWorld por ejemplo.
using System; namespace Primer { public class Programa { public static void Main() { Console.WriteLine("Hola, Geeky Theory!"); Console.ReadKey(); } } }
3. Luego una simple comprobante en nuestro método Main de si el fichero esta localizado sino mostrará un mensaje de error.
static void Main(string[] args) { if (args.Length > 0) { // First parameter is the source file name. if (File.Exists(args[0])) { CompileExecutable(args[0]); } else { Console.WriteLine("Input source file not found - {0}", args[0]); } } else { Console.WriteLine("Input source file not specified on command line!"); } }
Siendo CompileExecutable un método que tiene toda la lógica para compilarlo en tiempo real. Vamos a ver ahora las partes más importantes que hay para crear un CSharpCodeProvider.
CodeDomProvider provider = null;CodeDomProvider, nos proporciona la libertad de poder elegir si lo queremos compilar en C# o VB con estas siguientes líneas: provider = CodeDomProvider.CreateProvider("CSharp"); ó provider = CodeDomProvider.CreateProvider("VisualBasic"); Ahora nos falta llamar al CompilerParameters quien se encarga de recopilar todos los parámetros que hace falta para compilar el código.CompilerParameters cp = new CompilerParameters(); // Generate an executable instead of // a class library. cp.GenerateExecutable = true; // Specify the assembly file name to generate. cp.OutputAssembly = exeName; // Save the assembly as a physical file. cp.GenerateInMemory = false; // Set whether to treat all warnings as errors. cp.TreatWarningsAsErrors = false;
- GenerateExecutable: Si es tue, genera un .exe. Si es false, generará una .dll.
- OutputAssembly: El nombre que se va a mostrar al .exe/.dll cuando es generado.
- GenerateInMemory: True -> generción en memoria, False -> genera el ficho en el bin/Debug directory.
- TreatWarningsAsErrors: Establecer si queremos que todas las advertencias las tomemos como error. En este caso, false.
CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceName); Invocamos al compilador con CompileAssemblyFromFile y se lo asignamos a CompilerResults quien recoge el resultado de la compilación. if (cr.Errors.Count > 0) { // Display compilation errors. Console.WriteLine("Errors building {0} into {1}", sourceName, cr.PathToAssembly); foreach (CompilerError ce in cr.Errors) { Console.WriteLine(" {0}", ce.ToString()); Console.WriteLine(); } } else { // Display a successful compilation message. Console.WriteLine("Source {0} built into {1} successfully.", sourceName, cr.PathToAssembly); } Y finalmente, miramos si hubo error o no para mostrar esa información.
4. El método CompileExecutable esta definida de la siguiente manera:
private static bool CompileExecutable(string sourceName) { FileInfo sourceFile = new FileInfo(sourceName); CodeDomProvider provider = null; bool compileOk = false; // Select the code provider based on the input file extension. if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".CS") { provider = CodeDomProvider.CreateProvider("CSharp"); } else if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".VB") { provider = CodeDomProvider.CreateProvider("VisualBasic"); } else { Console.WriteLine("Source file must have a .cs or .vb extension"); } if (provider != null) { // Format the executable file name. // Build the output assembly path using the current directory // and <source>_cs.exe or <source>_vb.exe. String exeName = String.Format(@"{0}\{1}.exe", System.Environment.CurrentDirectory, sourceFile.Name.Replace(".", "_")); CompilerParameters cp = new CompilerParameters(); // Generate an executable instead of // a class library. cp.GenerateExecutable = true; // Specify the assembly file name to generate. cp.OutputAssembly = exeName; // Save the assembly as a physical file. cp.GenerateInMemory = false; // Set whether to treat all warnings as errors. cp.TreatWarningsAsErrors = false; // Invoke compilation of the source file. CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceName); if (cr.Errors.Count > 0) { // Display compilation errors. Console.WriteLine("Errors building {0} into {1}", sourceName, cr.PathToAssembly); foreach (CompilerError ce in cr.Errors) { Console.WriteLine(" {0}", ce.ToString()); Console.WriteLine(); } } else { // Display a successful compilation message. Console.WriteLine("Source {0} built into {1} successfully.", sourceName, cr.PathToAssembly); } // Return the results of the compilation. if (cr.Errors.Count > 0) { compileOk = false; } else { compileOk = true; } } return compileOk; }
5. Generamos el código para que podamos buscar el .exe de nuestro Demo.CSharpCodeProviderSample. Ahora abrimos el Command Prompt y navegamos hacia este ejecutable. Una vez en el directorio donde el ejecutable se encuentra, que estará en ..\Demo.CSharpCodeProviderSample\Demo.CSharpCodeProviderSample\bin\Debug, llamamos al Demo.CSharpCodeProviderSample.exe con un parámetro que será donde esté localizado nuestro HelloWorld.cs. En mi caso esta de la siguiente forma:
..\Demo.CSharpCodeProviderSample\Demo.CSharpCodeProviderSample\bin\Debug\Demo.CSharpCodeProviderSample.exe ..\Desktop\HellorWorld.csSi os ha generado con éxito el código, lo podéis ir en busca a ..\Demo.CSharpCodeProviderSample\Demo.CSharpCodeProviderSample\bin\Debug y ahí estará localiza. Uno de los mayores incovenientes que veo yo es esta parte. Que no puedo especificar donde quiero que se me genere el fichero. Pero para ejemplos así sencillos viene bastante bien. Nota CompileAssemblyFromFile acepta como fileNames, en nuestro caso sourceName, varios ficheros donde leer código. Es un Array donde se le puedes pasar todos los ficheros que quieres generar. En este caso podrías en verde pasar el path donde exactamente se encontraba HelloWorld.cs a pasar su directorio. Ahí teniendo más ficheros. Y en el método CompileExecutable, podrías hacer un foreach, de todos los Directory.GetFiles(). Así tendrías la opción de compilar varios ficheros a la vez. Un saludo!