Carga de archivos DLL en tiempo de ejecución en C #

91

Estoy tratando de averiguar cómo podría importar y usar un .dll en tiempo de ejecución dentro de una aplicación C #. Usando Assembly.LoadFile () he logrado que mi programa cargue el dll (esta parte definitivamente está funcionando ya que puedo obtener el nombre de la clase con ToString ()), sin embargo, no puedo usar la 'Salida' método desde el interior de mi aplicación de consola. Estoy compilando el .dll y luego lo muevo al proyecto de mi consola. ¿Hay un paso adicional entre CreateInstance y luego poder usar los métodos?

Esta es la clase en mi DLL:

namespace DLL
{
    using System;

    public class Class1
    {
        public void Output(string s)
        {
            Console.WriteLine(s);
        }
    }
}

y aquí está la aplicación que quiero cargar la DLL

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
danbroooks
fuente

Respuestas:

128

Los miembros deben poder resolverse en tiempo de compilación para ser llamados directamente desde C #. De lo contrario, debe utilizar reflexión u objetos dinámicos.

Reflexión

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                var c = Activator.CreateInstance(type);
                type.InvokeMember("Output", BindingFlags.InvokeMethod, null, c, new object[] {@"Hello"});
            }

            Console.ReadLine();
        }
    }
}

Dinámico (.NET 4.0)

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

            foreach(Type type in DLL.GetExportedTypes())
            {
                dynamic c = Activator.CreateInstance(type);
                c.Output(@"Hello");
            }

            Console.ReadLine();
        }
    }
}
Halcón oscuro
fuente
12
Tenga en cuenta que esto intenta llamar Outputa todos los tipos en el ensamblado, que probablemente se lanzarán antes de que se encuentre la clase "correcta" ...
Reed Copsey
1
@ReedCopsey, de acuerdo, pero para su ejemplo simple, su tipo es el único visible. "Los únicos tipos visibles fuera de un ensamblado son los tipos públicos y los tipos públicos anidados dentro de otros tipos públicos". Para un ejemplo no trivial, obviamente esto será un problema ...
Dark Falcon
1
¡Genial con los dos ejemplos! :)
Niels Abildgaard
22
Esta es la razón por la que las interfaces se utilizan a menudo y puede realizar una detección de características como IDog dog = someInstance as IDog;y probar si no es nula. Coloque sus interfaces en una DLL común compartida por los clientes, y cualquier complemento que se cargue dinámicamente debe implementar esa interfaz. Esto le permitirá codificar su cliente contra la interfaz IDog y tener intellisense + verificación de tipo fuerte en tiempo de compilación en lugar de usar dinámica.
AaronLS
1
@ Tarek.Mh: Eso requeriría una dependencia en tiempo de compilación de Class1. En ese momento, puede usar new Class1(). El autor de la pregunta especificó explícitamente una dependencia de tiempo de ejecución. dynamicpermite que el programa no requiera una dependencia en tiempo de compilación de Class1.
Dark Falcon
39

En este momento, está creando una instancia de cada tipo definido en el ensamblaje . Solo necesita crear una única instancia de Class1para llamar al método:

class Program
{
    static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var theType = DLL.GetType("DLL.Class1");
        var c = Activator.CreateInstance(theType);
        var method = theType.GetMethod("Output");
        method.Invoke(c, new object[]{@"Hello"});

        Console.ReadLine();
    }
}
Reed Copsey
fuente
19

Debe crear una instancia del tipo que exponga el Outputmétodo:

static void Main(string[] args)
    {
        var DLL = Assembly.LoadFile(@"C:\visual studio 2012\Projects\ConsoleApplication1\ConsoleApplication1\DLL.dll");

        var class1Type = DLL.GetType("DLL.Class1");

        //Now you can use reflection or dynamic to call the method. I will show you the dynamic way

        dynamic c = Activator.CreateInstance(class1Type);
        c.Output(@"Hello");

        Console.ReadLine();
     }
Alberto
fuente
Muchas gracias, esto es justo lo que estoy buscando. No puedo creer que esto no tenga una calificación más alta que las otras respuestas, ya que muestra el uso de la palabra clave dinámica.
skiphoppy
Ah, ahora veo que también estaba en la respuesta de DarkFalcon. Sin embargo, el tuyo era más corto y lo hacía más fácil de ver. :)
skiphoppy
0

Activator.CreateInstance() devuelve un objeto, que no tiene un método de salida.

¿Parece que vienes de lenguajes de programación dinámicos? C # definitivamente no es eso, y lo que está tratando de hacer será difícil.

Dado que está cargando un dll específico desde una ubicación específica, ¿tal vez solo desee agregarlo como referencia a su aplicación de consola?

Si absolutamente desea cargar el ensamblaje a través de Assembly.Load, tendrá que ir a través de la reflexión para llamar a los miembros enc

Algo como type.GetMethod("Output").Invoke(c, null);debería hacerlo.

Fredrik
fuente
0
foreach (var f in Directory.GetFiles(".", "*.dll"))
            Assembly.LoadFrom(f);

Eso carga todas las DLL presentes en la carpeta de su ejecutable.

En mi caso, estaba tratando de usar Reflectionpara encontrar todas las subclases de una clase, incluso en otras DLL. Esto funcionó, pero no estoy seguro de si es la mejor manera de hacerlo.

EDITAR: Lo cronometré, y solo parece cargarlos la primera vez.

Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < 4; i++)
{
    stopwatch.Restart();
    foreach (var f in Directory.GetFiles(".", "*.dll"))
        Assembly.LoadFrom(f);
    stopwatch.Stop();
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
}

Salida: 34 0 0 0

Por lo tanto, uno podría ejecutar ese código antes de cualquier búsqueda de Reflection por si acaso.

Samuel Cabrera
fuente
-1

No es tan dificil.

Puede inspeccionar las funciones disponibles del objeto cargado y, si encuentra la que está buscando por su nombre, busque sus parámetros esperados, si los hay. Si es la llamada que está tratando de encontrar, llámela usando el método Invoke del objeto MethodInfo.

Otra opción es simplemente construir sus objetos externos en una interfaz y convertir el objeto cargado en esa interfaz. Si tiene éxito, llame a la función de forma nativa.

Esto es algo bastante simple.

ChrisH
fuente
Vaya, no estoy seguro de por qué los votos negativos. Tengo una aplicación de producción que hace exactamente esto durante los últimos 12 años. * encogerse de hombros * Cualquiera necesita un código para hacer esto, envíame un mensaje. Empacaré partes de mi código de producción y lo enviaré.
ChrisH
10
Sospecho que los votos negativos tendrían que ver con la falta de ejemplos y el tono de condensación ... Sin embargo, parece que tiene la base para una respuesta completa, así que no tenga miedo de editar con más detalles :)
Shadow
1
Es un poco grosero decir "esto es algo bastante simple", y es por eso que obtuviste los votos negativos.
ABPerson
1
No estaba siendo grosero ni condescendiente ... hace 6 años. El tono no se transmite en el texto, claramente. Realmente estaba destinado a ser bastante alegre ... También realmente siento que tuve un enlace a una muestra de código allí todos esos años, y no tengo idea de a dónde fue (suponiendo que realmente estuviera allí como si estuviera recordando ). : \
ChrisH
No sé cómo funciona MethodInfo, pero parece valioso. Yo diría que su respuesta tiene el potencial de ser mejor que la aceptada actualmente, pero debería ser completada. Si alguna vez lo hace, se lo agradecería. Si es así, no enlace a una muestra de código. Estos pueden romperse en el futuro. Es mejor proporcionar la muestra usted mismo, posiblemente con un enlace a una fuente o información adicional para continuar leyendo.
SpaghettiCook