2012-04-24 8 views
8

Estoy creando una aplicación basada en .NET y me gustaría permitir un diseño más extensible y conectable.Permitir que los complementos de C# se registren en los ganchos de la aplicación

En aras de la simplicidad, la aplicación expone un conjunto de operaciones y eventos:

  • DoSomething()
  • DoOtherThing()
  • OnError
  • onSuccess

I le gustaría ofrecer "complementos" para cargar y enganchar en algunas de estas operaciones (algo así como: cuando se dispara el evento 1, ejecute el plugin1).

Por ejemplo RUN - plugin1.HandleError() cuando el OnError incendios de evento.

Esto se puede hacer fácilmente con la suscripción de eventos:

this.OnError += new Plugin1().HandleError(); 

El problema es que:

  1. Mi aplicación no sabe del tipo "Plugin1" (que es un plug-in, mi aplicación no hace referencia directamente).
  2. Al hacerlo creará el plugin antes de tiempo, algo que no quiero hacer .

En los modelos de plugins "tradicionales", la aplicación ("cliente" de complementos) carga y ejecuta el código de complemento en ciertos puntos clave.Por ejemplo, una aplicación de procesamiento de imágenes, cuando se realiza una determinada operación).

La aplicación cliente conoce el control de cuándo crear una instancia del código del complemento y cuándo ejecutarlo.

En mi aplicación, el plugin en sí es para decidir cuándo se debe ejecutar ("El complemento debe registrarse en el evento OnError").

Mantener el código de "ejecución" del complemento junto con el código de "registro" plantea un problema que la DLL del complemento se cargará en la memoria en el momento del registro, algo que deseo evitar.

Por ejemplo, si agrego un método Register() en el plugin DLL, el plugin DLL tendrá que cargarse en la memoria para que se llame al método Register.

¿Qué podría ser una buena solución de diseño para este problema en particular?

  • Lazily cargando (u ofreciendo carga lenta/con ganas) de archivos DLL de complementos.
  • Permitir que los complementos controlen en qué partes del sistema/aplicación se conectan.

Respuesta

4

Lo que debe hacer es encontrar la ruta de acceso del dll y luego crear un objeto de ensamblaje a partir de ella.A partir de ahí, se tendrá que obtener las clases que desea recuperar (por ejemplo, cualquier cosa que implementa la interfaz):

var assembly = Assembly.Load(AssemblyName.GetAssemblyName(fileFullName)); 
foreach (Type t in assembly.GetTypes()) 
{ 
    if (!typeof(IMyPluginInterface).IsAssignableFrom(t)) continue; 
    var instance = Activator.CreateInstance(t) as IMyPluginInterface; 
    //Voila, you have lazy loaded the plugin dll and hooked the plugin class to your code 
} 

Por supuesto, desde aquí se es libre de hacer lo que quieran, métodos de uso, suscriben eventos, etc.

+1

el inconveniente de esta estrategia es muy lento al cargar el conjunto. Este es un problema común para la reflexión en .Net –

+0

Gracias ya sé cómo hacer esto. Lo que me gustaría diseñar es una forma de permitir que el complemento se registre en ciertos eventos, sin cargarlo de antemano. –

+0

No creo que sea posible que el complemento dll haga * cualquier cosa * hasta que esté realmente cargado. Una solución es poner una clase de definición de complemento en cada ensamblado de complemento que configura el complemento/le dice al host sobre sus capacidades. Cuando se inicia la aplicación, puede encontrar todas las clases de definición y tomar la acción adecuada (tal vez simplemente llamar a un método de "Configurar" en cada una). –

2

Para cargar ensamblajes de complemento Tiendo a apoyarme en mi contenedor IoC para cargar los ensamblajes desde un directorio (uso StructureMap), aunque puede cargarlos manualmente de acuerdo con la respuesta de @ Oskar.

Si desea soportar la carga de complementos mientras se ejecuta su aplicación, StructureMap se puede "reconfigurar", por lo que puede elegir cualquier complemento nuevo.

Para sus enganches de aplicación puede enviar eventos a un bus de eventos. El ejemplo siguiente se utiliza StructureMap para encontrar todos los controladores de eventos registrados, pero se puede utilizar la reflexión a secas u otro contenedor IoC:

public interface IEvent { } 
public interface IHandle<TEvent> where TEvent : IEvent { 
    void Handle(TEvent e); 
} 

public static class EventBus { 
    public static void RaiseEvent(TEvent e) where TEvent : IEvent { 
     foreach (var handler in ObjectFactory.GetAllInstances<IHandle<TEvent>>()) 
      handler.Handle(e); 
    } 
} 

Luego, puede provocar un evento de este modo:

public class Foo { 
    public Foo() { 
     EventBus.RaiseEvent(new FooCreatedEvent { Created = DateTime.UtcNow }); 
    } 
} 

public class FooCreatedEvent : IEvent { 
    public DateTime Created {get;set;} 
} 

Y manejarlo (en su plugin, por ejemplo), así:

public class FooCreatedEventHandler : IHandle<FooCreatedEvent> { 
    public void Handle(FooCreatedEvent e) { 
     Logger.Log("Foo created on " + e.Created.ToString()); 
    } 
} 

sin duda recomendaría this post por Shannon Deminick que cubre muchos de los problemas con el desarrollo de pl aplicaciones uggable. Es lo que utilizamos como base para nuestro propio "administrador de complementos".

Personalmente, evitaría cargar los conjuntos bajo demanda. IMO es mejor tener un tiempo de inicio un poco más largo (incluso menos problema en una aplicación web) que los usuarios de la aplicación que ejecuta que tiene que esperar a que se carguen los complementos necesarios.

+0

Interesante. El inconveniente (al menos para mí) es la sobrecarga de crear una nueva clase para cada evento, y cada vez que me gustaría exponer un nuevo evento en mi sistema. –

+0

El tiempo que lleva crear estos eventos y manejadores ligeros es probablemente el mismo que se necesita para conectar un evento y controlador tradicional. No puedo ver que haya una manera más rápida de publicar nuevos eventos. –

+0

Además, yo (más o menos) pierdo la capacidad de definir .NET 'events' mediante + = suscripción a ellos. El envío del evento se realiza según su ejemplo utilizando una llamada a un método + dejando que el contenedor resuelva las clases 'suscritas'. –

5

Está tratando de resolver un problema inexistente. La imagen mental de todos los tipos que se cargan cuando su código llama a Assembly.LoadFrom() es incorrecta. .NET Framework aprovecha al máximo que Windows es un sistema operativo de memoria virtual con paginación demandada. Y algo más.

La bola se activa cuando llama a LoadFrom(). Hace que el CLR cree un archivo de memoria mapeada, una abstracción del sistema operativo principal. Se actualiza un poco de estado interno para realizar un seguimiento de un conjunto que ahora es residente en el dominio de la aplicación, es muy poco importante. El archivo MMF configura la asignación de memoria hacia arriba, creando páginas de memoria virtual que mapean el contenido del archivo. Solo un pequeño descriptor en el TLB del procesador. Nada realmente se lee del archivo de ensamblaje.

A continuación, utilizará la reflexión para intentar descubrir un tipo que implemente una interfaz. Esto hace que CLR lea algunos de los metadatos del ensamblaje. En este punto, las fallas de página hacen que el procesador mapee el contenido de algunas de las páginas que cubren la sección de metadatos del ensamblaje en la memoria RAM. Un puñado de kilobytes, posiblemente más si el conjunto contiene muchos tipos.

A continuación, el compilador just-in-time entra en acción para generar código para el constructor. Eso hace que el procesador falle la página que contiene el constructor IL en la RAM.

Etcétera. La idea principal es que el contenido del ensamblaje siempre se lee de forma lenta, solo cuando necesita.Este mecanismo es no diferente para complementos, funcionan igual que los ensamblajes normales en su solución. Con la única diferencia de que el orden es ligeramente diferente. Cargue primero el ensamblaje e inmediatamente llame al constructor. A diferencia de llamar al constructor de un tipo en su código y el CLR, inmediatamente cargando el ensamblado. Toma el mismo tiempo.

+0

Mi pregunta es sobre cambiar la responsabilidad de decidir cuándo invocar el complemento desde el código del cliente al plugin mismo. ¿Cómo dejo que el complemento diga qué evento debe cargarse y ejecutarse, sin cargarse demasiado pronto? Un poco de situación de pollo y huevo. –

+1

Creo que lo que Hans intenta decir es que su método RegisterForOperation1Hooks no carga el ensamblaje completo en la memoria, por lo que es una buena solución para su problema :) – cwap

+0

No estoy seguro de cómo crear un tipo incluido en mi complemento. dll e invocar un método Register() en él no hará que el dll se cargue en la memoria. Mi motivo es, por supuesto, descargar la DLL más adelante (se cargará en su propio Dominio de Aplicación), esto no será posible con esta solución. –

Cuestiones relacionadas