none
Como usar .dll en codeDom? RRS feed

  • Pregunta

  • Buenas tardes!!

    En mi editor de código c# tengo el siguiente código:

    using System;
    
    // la libreria que quiero usar:
    
    using plugin; 
    
    namespace CSprueba
    {
        public class Source
        { 
            public static void Main()
            {
         Console.WriteLine(Convert.ToString(plugin.Operaciones.resta(9, 7)));
                Console.ReadKey();
            }
        }
    }

    El código de la librería "plugin.dll" es:

    using System;
    using System.Text;
    
    namespace plugin
    {
        public class Operaciones
        {
            public static int resta(int x, int y)
            {
                return x - y;
            }
            public static int suma(int x, int y)
            {
                return x + y;
            }
        }
    }

    Lo que hago en mi editor usando codeDom es crear el .exe del código mediante la siguiente función:                                                   

    public void codedom()
    {
               CodeDomProvider  codeProvider = CodeDomProvider.CreateProvider("CSharp");
    
                CompilerParameters parameters = new CompilerParameters();
                parameters.GenerateExecutable = true;
                parameters.GenerateInMemory = false;
                parameters.IncludeDebugInformation = false;
                parameters.OutputAssembly = path_base + output;
    
                parameters.ReferencedAssemblies.Add("plugin.dll");   //<------------ librería que quiero usar
                parameters.ReferencedAssemblies.Add("mscorlib.dll");
                parameters.ReferencedAssemblies.Add("System.dll");
                parameters.ReferencedAssemblies.Add("System.Core.dll");
                parameters.ReferencedAssemblies.Add("System.Data.Linq.dll");
                parameters.ReferencedAssemblies.Add("System.Data.Entity.dll");
    
                var providerOptions = new Dictionary<string, string>();
                providerOptions.Add("CompilerVersion", "v4.0");
    
                CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ActiveEditor.Document.TextContent);
    
                if (results.Errors.Count > 0)
                {
    
                 }
                else
                {
    
                }
    }

    Me general el .exe sin arrojar ningún error, pero al ejecutar dicho exe me dice que no encuentra el archivo plugin.dll:

    Excepción no controlada: System.IO.FileNotFoundException: No se puede cargar el archivo o ensamblado 'plugin.dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' ni una de sus dependencias. El sistema no puede encontrar el archivo especificado.
       en CSprueba.Source.Main()

    En definitiva, no se como usar una librería compilada en dicho editor, no se como referenciar esa .dll al codigo.

    Gracias, espero respuesta.


    lunes, 6 de febrero de 2017 16:00

Respuestas

  • Para usar el FUSLOGVW: Abre el "Developer Command Prompt" de Visual Studio (para que tenga la ruta de las herramientas en el PATH). Lánzalo con "ejecutar como Administrador", porque sino el FUSLOGVW te sale con casi todas las opciones desactivadas. Después, desde esa ventana teclea "FUSLOGVW" para lanzar el programa.

    Pincha en "Settings" y selecciona la opción "Log Bind failures to disk". Esto activa el Log de fusión, de forma que cada vez que un ejecutable de .NET dé un error al buscar una librería, los detalles del error se quedan grabados en el Log.

    Lanza el programa que te falla hasta que dé el error. Vete de vuelta a la ventana del FUSLOGVW y pulsa "Refrescar". Debería aparecerte en la ventana el error capturado. Haz doble-click sobre el error, y te abrirá una ventana más grande con toda la secuencia de qué buscó y dónde lo buscó. Eso debería darte alguna pista de por qué no está funcionado como debería.

    En cuanto a lo de añadir la DLL ya compilada al fuente del .EXE para que la use el CodeDom: No, no puede ser si está ya compilada. Necesitas el código fuente de esa DLL. Si lo tienes, sí que lo puedes añadir al proyecto, y una de dos: o añadírselo al fuente que compilas con el CodeDom, o compilarlo junto con el .exe y que el programa que generas tenga una referencia al propio .exe.

    martes, 7 de febrero de 2017 19:00
  • usar una clase [...] Como tendría que hacer referencia a esa clase o a ese archivo .cs??

    Lo mas sencillo es que se la pases al mismo metodo CompileAssemblyFromSource que ya estas usando. El ultimo parametro es un "params string[]", por lo que le puedes pasar varios archivos fuente para que los compile juntos. Si la clase no la tienes en un string en memoria sino que la tienes en un .cs, sencillamente usa File.ReadAllText para pasar el contenido del archivo a un string, y pasale el string al "Compile...":

     CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ActiveEditor.Document.TextContent, laCadenaQueContieneLaClase);

    miércoles, 8 de febrero de 2017 11:48
  • Primero, en la parte donde configuras los CompilerParameters, le pones esto:

    parameters.GenerateInMemory = true;

    Después recuperas los CompilerResults en la forma en la que lo estabas haciendo:

    CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ...);

    Y a partir de esos results, obtienes el assembly, y desde el assembly ya usas Reflection en la forma habitual en la que lo usarías si fuera un código compilado desde Visual Studio con normalidad:

    Assembly assembly = results.CompiledAssembly;
    Type t = assembly.GetType("plugin.Operaciones", BindingFlags.Static | BindingFlags.Public);
    MethodInfo mi = t.GetMethod("resta");
    int resultado = (int)mi.Invoke(null, new int[]{x,y});

    jueves, 9 de febrero de 2017 20:42

Todas las respuestas

  • El criterio que se sigue para encontrar una DLL es distinto en tiempo de compilacion y en tiempo de ejecucion.

    En tiempo de compilacion, la DLL que le indicas al CodeProvider usa una ruta relativa al CurrentDirectory. Si te compila bien sin dar errores es porque la tenias ubicada en la misma carpeta que casualmete resultaba ser el CurrentDirectory en el momento de compilar.

    En cambio, en tiempo de ejecucion, se sigue un complejo proceso para buscar la DLL, llamado "fusion". Se busca en varias ubicaciones entre las que se incluyen el GAC y la carpeta del ejecutable, pero NO el CurrentDirectory. Por eso cuando compilas un proyecto desde Visual Studio, de forma predeterminada copia todas las Referencias que no esten en el GAC a la carpeta del ejecutable, para que el ejecutable las encuentre al ejecutarlo. Pero esto lo hace el propio Visual Studio, no es algo inherente al compilador de C#. Tendras que ocuparte tu de copiar la dll a algun sitio donde la pueda encontrar el ensamblado que has compilado (lo mas sencillo es copiarla a la carpeta en la que lo generaste).

    lunes, 6 de febrero de 2017 16:16
  • Hola Alberto, si en efecto tengo el archivo plugin.dll en la misma carpeta donde intento ejecutar ese .exe y me da ese error. Me deja generar ese .exe porque en la función codedom tengo agregado el archivo plugin.dll como indico arriba y tengo en la misma carpeta donde se ejecuta mi editor tambien ese plugin.dll. Sin hacer cualquiera de esas dos cosas no me deja ni generar el .exe, pero una vez generado y teniendo ese exe en la misma carpeta junto al plugin.dll no me deja ejecutarlo. No se como hacer para que ese .exe encuentre esa librería.

    Muchas gracias de todos modos.

    lunes, 6 de febrero de 2017 16:24
  • Si la DLL está en la misma carpeta que el .exe, en teoría debería encontrarla. Se me ocurren un par de cosas que pueden ocurrir: una es que la dll esté compilada para una versión del Framework más moderna que la versión para la que has compilado el .exe. Otra es que esté compilada para 32 bits y el ejecutable esté rodando en 64 bits o viceversa. Si la dll tuviera un Strong Name (que no es el caso, a juzgar por el error que sale), también podría fallar por culpa de una diferencia en el número de versión o en la clave del SN.

    Si no es ninguna de las cosas anteriores, utiliza el visor del log de fusión (FUSLOGVW.EXE). Activa el Log, ejecuta el programa, y cuando falle, examina en el FUSLOGVW el error que ha salido. Ahi verás qué es lo que ha buscado, en qué carpetas lo ha buscado, y por que razón no le ha servido lo que encontró. Con esto deberías poder resolver el problema.

    lunes, 6 de febrero de 2017 19:31
  • Buenas compañero!

    Si, ya comprobé lo de las versiones y el exe utiliza providerOptions.Add("CompilerVersion", "v4.0"); y la librería también la 4.0. Me podrías explicar como hacer lo del FUSLOGVW?. Y una última consulta, no sería posible usar el contenido de la librería ya compilada "plugin.dll", en una clase añadida al source del .exe y copilar el programa con dicha clase?. Supongo que si se podrá, siendo así, me podrías explicar como hacer esa referencia de esa clase en codeDom, ya que lo que tengo es un editor de código para visualizar y modificar el programa de consola (dicho exe), pues querría añadir una clase a ese source visualizado en el editor, mediante codeDom. Un ejemplo siempre ayuda a entender mejor las cosas, por si me puedes pasar alguno sencillo. 

    De nuevo mil gracias por tu tiempo.

    martes, 7 de febrero de 2017 11:12
  • Para usar el FUSLOGVW: Abre el "Developer Command Prompt" de Visual Studio (para que tenga la ruta de las herramientas en el PATH). Lánzalo con "ejecutar como Administrador", porque sino el FUSLOGVW te sale con casi todas las opciones desactivadas. Después, desde esa ventana teclea "FUSLOGVW" para lanzar el programa.

    Pincha en "Settings" y selecciona la opción "Log Bind failures to disk". Esto activa el Log de fusión, de forma que cada vez que un ejecutable de .NET dé un error al buscar una librería, los detalles del error se quedan grabados en el Log.

    Lanza el programa que te falla hasta que dé el error. Vete de vuelta a la ventana del FUSLOGVW y pulsa "Refrescar". Debería aparecerte en la ventana el error capturado. Haz doble-click sobre el error, y te abrirá una ventana más grande con toda la secuencia de qué buscó y dónde lo buscó. Eso debería darte alguna pista de por qué no está funcionado como debería.

    En cuanto a lo de añadir la DLL ya compilada al fuente del .EXE para que la use el CodeDom: No, no puede ser si está ya compilada. Necesitas el código fuente de esa DLL. Si lo tienes, sí que lo puedes añadir al proyecto, y una de dos: o añadírselo al fuente que compilas con el CodeDom, o compilarlo junto con el .exe y que el programa que generas tenga una referencia al propio .exe.

    martes, 7 de febrero de 2017 19:00
  • Ya puedo ejecutar el prorama sin que me de el problema de que no encuentra el archivo .dll, no se muy bien a que se debía pero creo que era algo de mi función que compilaba el código con codeDom. Muchas gracias por la explicación del uso de FUSLOGVW pues sin duda me servirá de gran ayuda en otros problemas futuros. 

    En cuanto a mi programa, lo que hago es primero compilar la dll con codeDom y guardarla en la misma carpeta donde se va a generar seguidamente mi .exe también con codeDom. Pero y si lo que quiero es: en vez de usar una dll, usar una clase sin que tenga que compilarla en una dll externa al programa y que me compile en codeDom el programa con la clase que usa incorporada dentro de él. Como tendría que hacer referencia a esa clase o a ese archivo .cs??

    Como siempre muchas gracias.

    miércoles, 8 de febrero de 2017 10:36
  • usar una clase [...] Como tendría que hacer referencia a esa clase o a ese archivo .cs??

    Lo mas sencillo es que se la pases al mismo metodo CompileAssemblyFromSource que ya estas usando. El ultimo parametro es un "params string[]", por lo que le puedes pasar varios archivos fuente para que los compile juntos. Si la clase no la tienes en un string en memoria sino que la tienes en un .cs, sencillamente usa File.ReadAllText para pasar el contenido del archivo a un string, y pasale el string al "Compile...":

     CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ActiveEditor.Document.TextContent, laCadenaQueContieneLaClase);

    miércoles, 8 de febrero de 2017 11:48
  • Perfecto, pues tema solucionado. Muchas gracias por la atención.

    Por último me gustaría saber como podría recoger el valor devuelto por el código compilado con codeDom.

    Suponiendo la función resta del archivo plugin.dll del inicio de este post:

    Console.WriteLine(Convert.ToString(plugin.Operaciones.resta(9, 7)));

    el Console.WriteLine lo tengo que sustituir por Debug.Write para guardar el resultado en NativeCompilerReturn:

    CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ActiveEditor.Document.TextContent, clase);

    var output = results.NativeCompilerReturnValue.ToString(); 

    para mostrar el resultado de mi programa sin que me tenga que generar una aplicación de consola y tener que ejecutarla para ver el resultado?

    Básicamente lo que querría sería poder recuperar el resultado que me retorne mi programa sin tener que crear el .exe y ejecutarlo para ver el resultado, haciendo un output o consola de salida.

    Perdón si no he abierto otro hilo para esta duda, pero he aprovechado que he contestado a la solución de antes para plantear esta cuestión. Si no es adecuada en ese hilo moverla a uno nuevo o a donde corresponda. Gracias por tomarte el tiempo para contestar mis dudas, quedo a la espera.



    miércoles, 8 de febrero de 2017 21:45
  • Hay varias opciones. Si estás compilando para llamar al ejecutable de forma inmediata, entonces en lugar de generarlo en disco podrías generarlo en memoria (con la opción CompileInMemory=true) y luego llamar directamente a una función que tengas en el código compilado.

    Si no quieres compilar en memoria, sino en disco, otra opción es compilar a una DLL, y luego cuando la quieras ejecutar cargarla con Assembly.LoadFrom, y después llamar a sus funciones mediante reflexión o mediante una interfaz si generaste una clase que implemente dicha interfaz.

    jueves, 9 de febrero de 2017 7:43
  • Usando CompileInemory como llamar directamente, por ejemplo, a la función de resta de arriba usando la primera opción de las dos anteriores?? como recuperar en una variable el valor devuelto de dicha función resta?

    Gracias!!

    jueves, 9 de febrero de 2017 11:26
  • Primero, en la parte donde configuras los CompilerParameters, le pones esto:

    parameters.GenerateInMemory = true;

    Después recuperas los CompilerResults en la forma en la que lo estabas haciendo:

    CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, ...);

    Y a partir de esos results, obtienes el assembly, y desde el assembly ya usas Reflection en la forma habitual en la que lo usarías si fuera un código compilado desde Visual Studio con normalidad:

    Assembly assembly = results.CompiledAssembly;
    Type t = assembly.GetType("plugin.Operaciones", BindingFlags.Static | BindingFlags.Public);
    MethodInfo mi = t.GetMethod("resta");
    int resultado = (int)mi.Invoke(null, new int[]{x,y});

    jueves, 9 de febrero de 2017 20:42
  • Genial, pero si me puedes indicar como recuperar los parámetros que le pase al método "resta" cuando lo escribo en el editor y lo compilo, tendría solucionado el problema. Porque esa variable "resultado" de arriba la dejamos en función de los parámetros x e y, pero ya compile el código asignándole un valor a dichos parámetros. Me gustaría saber como puedo pasarle los valores de esos parámetros con los que compile el código. 

    Porque si en el editor tengo la función ...resta(9, 7); y en la función para compilar el codigo tengo: ...Invoke(null, new object[] {5, 3}); no me serviría, pues tendría que ser: ..Invoke(null, new object[] {9, 7}); y me daría el resultado correcto. Pero si modifico el código en el editor y pongo: ...resta(12, 6); y compilo me seguirá retornando la diferencia de 5 - 3. No se como establecer a esa x e y los valores originales con los que compilo.

    Mil gracias de todas formas por la ayuda, pues me esta permitiendo acercarme mucho al final del programa.

    jueves, 9 de febrero de 2017 22:20
  • Aquí sí que me he perdido. No sé a qué te refieres con eso del "editor". Se supone que a la función quieres pasarle como parámetros unas variables que tienes en tu programa, que son las que yo he llamado x e y. Por supuesto esas variables las cargarás desde donde tengas los datos, por ejemplo, si tienes en pantalla dos textboxes donde el usuario teclea los dos valores, entonces harías, por ejemplo

    ... mi.Invoke(null, new int[]{ int.Parse(textBox1.Text),  int.Parse(textBox2.Text) });

    Si no tienes los valores por separado, sino que el usuario teclea la instrucción entera, tal como "resta(12,6)", entonces tienes dos opciones: una es escribir un "parser" que lea ese texto y extraiga los valores (se podría hacer con una expresión regular), y otra es que ese texto lo hagas formar parte del código que compilas (en cuyo caso no haría falta pasar ningún parámetro al llamarlo, ya estarían compilados los parámetros en el interior).

    viernes, 10 de febrero de 2017 7:14
  • Este es mi editor:


    Assembly assembly = results.CompiledAssembly;
    Type t = assembly.GetType("CSprueba.Source");
    MethodInfo mi = t.GetMethod("Main");
    int resultado = (int)mi.Invoke(null, null);

    Con esto ya lo tengo solucionado.

    Muchas gracias por la ayuda prestada. 

    Hay alguna manera de revisar los error del lenguaje que sea de forma que no compile y sea mas rápida? y estoy pensando en implementar el Intellesense, así que si me puedes dar alguna idea me vendría bien, porque no tengo ni idea de como lo haré. Sin otro particular, gracias y hasta la próxima.

    viernes, 10 de febrero de 2017 11:24
  • [...] manera de revisar los error del lenguaje que sea de forma que no compile y sea mas rápida? y estoy pensando en implementar el Intellesense [...]

    Tendrias que usar Roslyn. Pero aqui ya no te puedo guiar, yo no lo he usado nunca.

    https://roslyn.codeplex.com/

    viernes, 10 de febrero de 2017 12:02
  • Vale gracias!!
    viernes, 10 de febrero de 2017 12:10