Sistema de plugins con C#. Parte I. Conceptos

Antes de empezar

Un sistema de plugins es un mecanismo complejo. Por un lado el traslado de dependencias hacia el tiempo de ejecución (como veremos más adelante) lo hace mucho más complicado de explicar, de comprender y de implementar. Hay muchas cuestiones a tener en cuenta, este artículo trata de abarcar las principales pero me temo que hay muchas cuestiones que quedarán fuera.

Por otro lado, aunque para entender el sistema no es necesario un conocimiento de .NET o C#, desde luego estos puntos ayudarán a comprenderlo mejor y con más profundidad.

Evidentemente el sistema es demasiado complejo para que explique aquí todas y cada una de las partes que componen el sistema. El código en si, que incluyo en el artículo, está bastante comentado y he procurado ilustrar en el artículo las partes principales del sistema pero no me cabe duda de que habrá zonas que queden sin comentar.

Por último, este es un artículo largo y complejo y aunque he intentado en la medida de lo posible que tanto el código como el artículo esten libre de errores puede que alguno se haya colado, si encontrais alguno o teneis algún comentario os ruego que me lo hagais saber, bien por comentarios o bien directamente contactando conmigo en el email de contacto.

La web del proyecto y su licencia

El código y otra información adicional está disponible en la página del proyecto Monet Plugins Library y tenéis disponible la última versión del código en el control de versiones de subversion http://svn.thealphasite.org/Monet.

Todo el proyecto responde a la licencia LGPL, lo cual implica, a grandes rasgos que el proyecto es esencialmente GPL (con todas las obligaciones que ello conlleva, como distribuir las modificaciones echas al código y mantener la misma licencia) pero permitiendo que el framework sea utilizado dese una aplicación o librería que no sea GPL. En general esta es una de las licencias que más "libre" me parece. Básicamente, si quieres modificarla y mejorarla tienes que "devolver" lo que has hecho y por tanto la librería va mejorando constantemente, pero si simplemente quieres usarla desde algún otro programa, entonces no encuentras restricciones.

¿Que entendemos por un sistema de plugins?

La palabra plugin viene del ingles y viene a significar algo así como enchufar. Seguro que alguna vez has visto algun programa al que podías añadirle complementos en forma de otros programas que se acoplan a el. Dichos complementos pueden realizar diversas funciones, desde cambiar el estilo visual del programa hasta cambiar el comportamiento o añadir funcionalidad a este.

Algunos ejemplos pueden ir desde el sistema de extensiones del Firefox que permite añadir pequeñas "capsulas" de funcionalidad al navegador hasta, por ejemplo, el sistema de MODs del Half-life que permite modificar el juego hsta tal punto que hay varios MODs con tanto o más prestigio que el juego original.

Beneficios de un sistema de plugins

Desde el punto de vista del desarrollo, un sistema de plugins tiene diversas ventajas:

  • Alta independencia entre modulos (también baja cohesión entre modulos). Puesto que cada modulo es, en principio, relativamente independiente de todos los demás es más facil desarrollar pruebas mediante stubs y drivers sin que se vean afectadas por el resto de componentes de la aplicacion
  • Facilidad para extender la aplicación sin necesidad de redistribuir un nuevo ejecutable.
  • Si los plugins que desarrollamos son los suficientemente genéricos pueden reutilizarse en otras aplicaciones con lo que el periodo de desarrollo es significativamente menor (el necesario para adaptar el plugin al nuevo sistema).
  • Facilidad para adaptarse a cambios en los requisitos añadiendo o modificando funcionalidad mediante la creación o modificación de un plugin adecuado.

Desventajas de un sistema de plugins

Pese a que un sistema de plugins tiene bastantes ventajas no está exento de desventajas:

  • Transferencia de dependencias a tiempo de ejecución. Debido a que los plugins son independientes unos de otros pero pueden apoyarse unos en otros pueden surgir problemas derivados de la interacción de dichos plugins.
  • Necesidad de un mayor control de errores. Puesto que los plugins son en principio desconocidos se hace especialmente necesario desarrollar un control adecuado sobre los parámetros de entrada así como los posibles errores durante la ejecución de un servicio que proporciona un plugin.
  • Posibles compromisos de seguridad. En principio, y salvo que proporcionemos un sistema de seguridad y permisos adecuados, cualquier plugin tiene acceso completo a la máquina con todos los permisos del usuario que ejecuta la aplicación.

El modelo en tres capas

Es posible que algunos de vosotros conozcáis la estructuración de aplicaciones en tres capas (presentación, negociado y datos) que consiste en diferenciar una aplicación en tres capas bien definidas, la capa de interfaz que presenta la información al usuario, la capa de negociado que se encarga de implementar las transformaciones en los datos y la capa de datos que se encarga de proporcionar los datos.

La lógica detrás de esta estructura es la agrupación de distintas funciones centralizando parte de ellas para conseguir un diseño relativamente estructurado. Si bien no estoy en principio en contra de dicho diseño para según que aplicaciones, la creación de una capa de datos es, en mi opinión habiendo hecho ya varias aplicaciones con esa estructura, aconsejable en una gran mayoría de los casos pero poco recomendable en otros, estando un sistema de plugins en este caso.

Crear una capa estructurada que centralice todos los datos (y accesos) de la aplicación es una estrategia que nos permite minimizar el impacto en la aplicación de los cambios en el modelo de datos ya que, al estar todos centralizado cuando cambia el modelo tan solo debemos tocar las rutinas que abstraen a la aplicación de dicho modelo, no tendremos que recorrer la aplicación cambiando cosas. Sin embargo dicho modelo incrementa a la vez el acoplamiento o dependencia entre módulos de forma que, muchos modulos tienen una dependencia fuerte lo cual introduce problemas a la hora de realizar cambios de funcionalidad. En muchos casos todo cambio que queramos hacer que requiera una nueva forma de acceder a los datos (por ejemplo mediante una query más especializada de una función ya existente) va a requerir en un cambio en la capa de acceso a datos. Esto que puede ser perfectamente válido e incluso recomendable en una aplicación estándar (en realidad se pueden hacer muchos analisis sobre el modelo de tres capas) puede resultar poco útil en una aplicación basada en plugins que queremos que pueda ser extendida sin necesidad de, por decirlo de alguna forma, tocar demasiado. Si cada vez que desarrollamos un plugin necesitamos cambiar la capa de acceso a datos dicha extensibilidad puede verse resentida.

En cualquier caso, como comentario final hay que recalcar que es posible desarrollar un sistema de plugins de cualquiera de las dos formas, con y sin capa de acceso a datos. En el primero de los casos la funcionalidad que podrán agregar los plugins se verá limitada por dicha capa aunque también tendremos más centralizado y controlado lo que los plugins pueden hacer en la base de datos mientras que en el segundo no tendremos esa limitación pero tendremos que idear un sistema para "controlar" lo que pueden hacer nuestros plugins (esto se tratará de forma breve al final del artículo junto con los aspectos de seguridad).

Prerequisitos

.NET Reflection y Assemblys

Nuestro sistema de plugins se va a apoyar en la librería System.Reflection de .NET. Explicar como funciona internamente el CLR (common language runtime) que es la base de la arquitectura .NET esta muy muy fuera de los ámbitos de este artículo, sin embargo un cierto conocimiento es necesario. Si no has mirado todavía la Introducción a .NET te recomendaría que lo hicieras ahora, antes de empezar.

Uso de atributos

.NET incopora el concepto de atributos al código. Los atributos son fragmentos de metainformación que podemos aplicar casi a cualquier parte del código, incluyendo funciones, clases, eventos, etc.

Para nuestro sistema de plugins vamos a utilizar dichos atributos para marcar los distintos puntos claves de nuestro sistema, es decir, que clase implementa un plugin, que servicios proporciona dicha clase, que hooks se implementan y que metodos pueden ser interceptados.

De esta forma (ya lo veremos más adelante) vamos a definir una serie de atributos como serán "Plugin", "Hook", "Hookable" que nos permitirán especificar esos datos, así como una cierta información y otra serie de atributos como RequiresPlugin, RequiresService, que nos permitirán indicar las dependencias de dichos plugins con otros plugins.

Para más información al respecto consultar el artículo sobre [/es/category/tags/attributes Atributos en .NET]

Arquitectura del sistema

Plugins y Plugin Manager

Vamos a dividir nuestro sistema en dos partes claramente diferenciadas y con objetivos completamente distintos.

Por un lado tenemos la biblioteca de plugins que proporcionará los interfaces y tipos base sobre los apoyaremos todo el sistema. En ella vamos a definir sobre todo clases ayudantes, definiciones de tipos y sobre todo la definición de los interfaces y atributos que van a constituir la base de toda la metainformación de nuestro sistema.

Por otro lado vamos a definir un plugin manager que será el que utilice toda la metainformación definida en la biblioteca de plugins y se encargue del proceso de carga y de resolución de conflictos que, como veremos más adelante constituye uno de los puntos más conflictivos. Así mismo el plugin manager se apoyará en gran parte de las clases que se definen en la librería de plugins.

Durante la primera parte del artículo vamos a explorar la metainformación y los recursos proporcionados por el sistema de plugins, fundamentalmente su significado. Después veremos como el plugin manager hace uso de toda esa metainformación para construir todo el sistema, uniendo, por así decirlo, cada parte en función de la información proporcionada.

Hooks y Servicios

En nuestro sistema de plugins nos vamos a apoyar en un sistema de Hooks y Servicios. Un determinado plugin puede proporcionar uno o más servicios, declarar uno o más hooks e interceptar uno o más hooks.

Un Servicio es, como su nombre indica, una funcionalidad que nos ofrece un plugin (un componente), es decir, de forma aplicada, un servicio proporcionado por un plugin. Un plugin puede ofrecer una serie de servicios que serán (o no) utilizados por el sistema principal o por otros plugins. Por ejemplo, un plugin de encriptación puede proporcionar un servicio que permita encriptar una cadena de texto o un fichero.

Un Hook es una acción que un plugin considera interceptable dentro de la su ejecución o dentro de la ejecución de algún servicio que proporciona, es decir, un plugin puede declarar que, cierta parte de su funcionalidad sea sustituida (o complementada) por código proporcionado por otro plugin. Por ejemplo, un componente que permita enviar un stream de texto a través de una conexión puede permitir preprocesar ese texto (mediante un Hook before_send_hook por ejemplo) que pueda ser interceptado por nuestro plugin de encriptación encriptando automáticamente el texto antes de mandarlo.

Un ejemplo de lo que conseguiremos

Supongamos que tenemos una aplicación normal para la que hemos creado un un plugin TCP que envía una cadena de texto a través de red.. el código de dicho plugin define algo como esto:

[Hookable]
public event BeforeSendDataDelegate beforeSend;

public SendText(string text)
{
    if (beforeSend != null)
      beforeSend(ref text);

  // Cogemos el texto y lo mandamos ...
}

Ahora, 4 meses después decido que quiero encriptar el texto que se manda para mejorar un poco la seguridad de la transmisión o porque el cliente se ha quejado de que le espían, entonces puedo crear otro plugin llamado Encrypt con el siguiente código

[Hooks("TCPPlugin", "beforeSend"]
public void HookSendText(ref string text)
{
   text = EncriptaTexto(text);
}

De esta forma yo no tengo que modificar el plugin de TCP (de hecho dicho plugin lo mismo ni lo he creado yo) pero puedo [b]extender[/b] su comportamiento y hacer que se comporte de otra forma, evidentemente el plugin de TCP deberá tener otro método hookable llamado AfterReciveText o similar para poder desencriptar la información.

Además podremos hookear interfaces para que la declaración anterior en realidad fuera..

[Hooks("IComProtocol", beforeSend)]

y se intercepten todos los beforeSend de cualquier plugin de "tipo" protocolo de comunicaciones.

Eso es todo, no tenemos que ocuparnos más que de declarar que métodos hookean a cuales y el framework se encarga de poner el pegamento que une todas las piezas para que, automáticamente, al activar el plugin Encrypt beforeSend haga referencia al método HookSendText de dicho plugin.

Librería de plugins

Para definir nuestro sistema de plugins una parte fundamental y obligada es definir lo que es un plugin. ¿Que tiene que proporcionar? ¿Que esperamos de el?

Hay muchas formas de crear un sistema de plugins y dentro de cada forma hay muchas maneras de definir que es un plugin. En nuestro caso un plugin viene definido por dos factores, por un lado la clase que encapsula el plugin debe implementar un determinado interfaz (IPlugin), por otro lado debe estar marcado con el atributo Plugin.

El interfaz del plugin

/// <summary>
/// Interfaz que describe un plugin del sistema.
/// </summary>
public interface IPlugin
{
  /// <summary>
  /// Realiza la carga del plugin.
  /// </summary>        
  /// <returns>0 si todo fue correcto. Un numero negativo en cualquier otro caso.</returns>
  int Load();

  /// <summary>
  /// Descarga el plugin de memoria
  /// </summary>
  /// <returns></returns>
  int UnLoad();

  /// <summary>
  /// Indica si un plugin debe ser cargado en un thread independiente
  /// </summary>        
  bool Threaded{ get; }
}

Nuestro interfaz de plugin es muy sencillo, básicamente consiste en 2 funciones, una para cargar el plugin y otra para descargar el plugin. La propiedad Threaded nos indicará si el plugin quiere ser lanzado desde un hilo independiente. Este último caso nos permitirá diseñar plugins que realizan alguna tarea en background y contienen su código de ejecución en la función Load. Para dichos plugins el sistema creará un hilo y lanzará la función Load desde dicho hilo liberando así al plugin de la necesidad de gestionar la creación y destrucción de hilos (aunque por supuesto cualquier plugin puede ocuparse por si mismo de eso simplemente declarando el código necesario en la función load).

El proceso de instalación

Puesto que estamos pensando en un sistma que queremos que sea lo más ampliable posible, debemos proporcionar un sistema para que los plugins realicen todas las operaciones que necesitan para ser funcionales como puede ser copiar una serie de archivos, crear entradas en el registro o incluso dar de alta datos o modificar la estructura de la base de datos (para por ejemplo crear las tablas que necesita para funcionar).

Definimos por tanto un nuevo interfaz para aquellos plugins que necesiten instalación.

/// <summary>
/// Describe el interfaz de un plugin que requiere instalación
/// </summary>
public interface IInstallPlugin : IPlugin
{
  /// <summary>
  /// Indica si el plugin está instalado o no
  /// </summary>
  /// <returns>True si el plugin está instalado, False si no</returns>
  bool IsInstalled();

  /// <summary>
  /// Se invoca cuando se realiza la instalacián del plugin.
  /// </summary>        
  /// <returns>0 si la instalación se realizó con Èxito. Un numero negativo en caso contrario</returns>
  int Install();

  /// <summary>
  /// Se invoca cuando se realiza la desintalación del plugin.
  /// </summary>
  /// <returns>0 si la desinstalación se realizó con écito. Un numero negativo en caso contrario</returns>
  int UnInstall();
}

Para esto existen tres funciones que permiten instalar, desinstalar y comprobar si el plugin esta instalado. Nuestro manejador de plugins, el modulo que se va a encargar de toda la gestión de los distintos plugins del sistema y que veremos más adelante, se encargará de realizar las llamadas apropiadas a estas funciones.

Atributos de plugin

Hay varias formas de marcar un plugin, así como de indicar los servicios que proporciona dicho plugin. Nosotros vamos a utilizar atributos para categorizar cada uno de los aspectos del plugin. Así, vamos a definir los siguientes atributos.

/// <summary>
/// Declara que una determinada clase es un plugin del sistema.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginAttribute : Attribute;

/// <summary>
/// Indica que una clase implementa un servicio
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ServiceAttribute : Attribute

Estos dos atributos nos permitirán indicar si una clase es un plugin y que servicios implementa. El atributo plugin solo puede aplicarse una vez pero una sola clase, un solo plugin, puede proporcionar varios servicios.

Para ir viendo el funcionamiento de la metainformación, vamos a suponer un ejemplo de plugin que iremos componiendo conforme va avanzando el artículo, así, puesto que es un plugin tendremos:

// Marcamos la clase como un plugin, proporcionano nombre, version, grupo al que
// pertenece el plugin y descripción del mismo. Además indicamos que va a proporcionar
// dos servicios Ejemplo1 y Ejemplo2, proporcionando nombre, tipo y descripción
[Plugin("Plugin de Ejemplo", "1.1.0.2","Ejemplos","Plugin de demostración del sistema")]
[Service("Ejemplo1", "Pruebas", "Servicio de ejemplo 1")]
[Service("Ejemplo2", "Pruebas", "Servicio de ejemplo 2")]
public class Ejemplo : IPlugin
{
   /// Aquí va la implementación del interfaz IPlugin
}

Dependencias y requisitos de plugin

El proceso de carga de plugins es uno de las situaciones más conflictivas y probablemente complejas del sistema. Si no has pensado todavía en ello vamos a pararnos a pensar en la dependencias que hay entre los distintos plugins...

¿Dependencias? ¿No quedamos en que un sistema de plugins aumenta la independencia entre modulos del sistema? Si, en gran medida lo hace pero no la elimina completamente. Si tenemos por ejemplo, un plugin de comunicaciones que permite enviar y recibir mensajes por un socket está claro que no tiene ningún tipo de dependencia con nuestro plugin de reproducción de sonido (en principio), sin embargo posiblemente todo nuestro plugin que implementa un sistema de mensajería instantánea se apoye en los servicios que proporciona el plugin de comunicaciones.

Que significa lo anterior, realmente no hay una dependencia con el plugin concreto de comunicaciones sino con el servicio que implementa ese plugin, es decir, dicho plugin puede ser sustituido (sin necesidad de recompilar el sistema además) por cualquier otro plugin que proporcione el mismo servicio y por tanto la dependencia es mucho menor, no obstante un cierto nivel de dependencia sigue ahí puesto que nuestro sistema de mensajería necesita que dicho plugin esté presente y además se haya cargado antes.

Existen varias formas de resolver el problema del orden de carga de los plugins (que descubrireis que es uno de los problemas a resolver en casi todos los sistemas de plugins).

Para nuestro sistema vamos a apoyarnos de nuevo en el uso de atributos. Dichos atributos indicarán que plugins requiere un determinado plugin y/o que servicios necesita.

/// <summary>
/// Declara un plugin que este plugin requiere
/// para funcionar y sin los cuales el plugin no debe
/// cargarse
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequiresPluginAttribute : Attribute

/// <summary>
/// Define los servicios que un determinado plugin requiere
/// para funcionar y sin los cuales el plugin no debe
/// cargarse
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequieresServiceAttribute : Attribute

El subsistema de servicios

Para implementar nuestro sistema de hooks definimos una clase estática en la biblioteca de plugins llamada modules que va a permitir realizar las distintas operaciones con los servicios, dichas operaciones serán:

  • Registrar un servicio. Nos permite registrar un servicio en el sistema, tan solo aquellos servicios registrados pueden usarse.
  • Desregistrar un servicio. Nos permite desregistrar un servicio previamente registrado en el sistema. De este paso y del anterior se encargará el Plugin Manager.
  • Obtener el objeto que implementa un servicio. Nos permite obtener el objeto que implementa un servicio dando su nombre o su tipo (o ambos).
  • Cargar un plugin de tipo runnable (ver la sección final del artículo)

El mecanismo de hooks

El sistema de hooks se va a basar, al igual que prácticamente todo el resto del sistema en el uso de una serie de atributos que nos van a permitir marcar que métodos serán hookables (interceptables) y cuales se comportarán como hooks de otros plugins interceptando su funcionamiento.

Para ello debemos definir dos nuevos atributos en el sistema:

/// <summary>
/// Define los metodos de un plugin que pueden ser
/// hookados.
/// </summary>
[AttributeUsage(AttributeTargets.Event, AllowMultiple = false)]
public class HookableAttribute : Attribute
{
}

/// <summary>
/// Declara un metodo como hook de algún otro metodo definido.
/// </summary>
/// <remarks>
/// Un hook puede interceptar tanto el metodo concreto de un
/// plugin en una versión, como un metodo de un interfaz.
/// </remarks>
/// <example>
/// <code>
/// [Hooks("TCP","BeforeSendText","1.0")]
/// InterceptSendText(ref string text);
/// </code>
/// Intercepta el metodo BeforeSendText del plugin llamado TCP en
/// todas aquellas versiones cuya versión sea 1.0 (1.0, 1.0.3, 1.0.9)
/// <code>
/// [Hooks("IProtocolo","BeforeSendText")]
/// InterceptSendText(ref string text);
/// </code>
/// Intercepta el metodo BeforeSendText de todos aquellos plugins
/// que implementen el interfaz IProtocolo. De esta forma podemos
/// definir un hook (por ejemplo de encriptado) que intercepte todos
/// los envios de texto de cualquier protocolo de comunicaciones.
/// </example>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class HooksAttribute : Attribute

El primer atributos Hookable, permite indicar sobre un evento que puede ser hookado, es decir, que queremos que dicho método sea interceptado. Esto quiere decir que los hooks se definen sobre eventos no sobre métodos. La razón de que esto sea así es que la idea es que el uso de hooks sea exactamente igual al de eventos en .NET, y tan transparente para el programador como lo son estos. Esto significa que nuestro evento, por ejemplo, OnDataReceived puede ser igualmente interceptado por parte de nuestro código como por un plugin externo.

El segundo atributo permite definir la otra parte de la ecuación, es decir, definir el método que se suscribirá al evento. En la definición del atributo tenemos dos opciones, asociarlo al nombre de un plugin completo o bien asociarlo a un determinado interfaz genérico de forma que el método intercepte siempre dicho método del interfaz (siempre que este marcado como Hookable) no importa quien lo implemente.

El plugin manager

El plugin manager es el encargado de gestionar los plugins del sistema realizando operaciones como la carga de un plugin, el descubrimiento e instalación de nuevos plugins o la invocación del servicio de configuración del mismo mediante la llamada a la función Configure.

Para llevar a cabo su misión el plugin manager se encargará de recorrer el directorio en el que están almacenados los plugins (y los subdirectorios de forma recursiva) e ir leyendo mediante reflection la información que, en forma de atributos, hemos definido dentro de ellos. También se encargará de analizar esa información para determinar el orden correcto de carga de los plugins y las dependencias que dichos plugins tienen, evitando su carga si no se cumplen.

Lo interesante o elegante del sistema es que el plugin manager es, en si mismo, un plugin, aunque un tipo especial de plugin.

Arquitectura de un sistema de plugins

EjPlugins.png

Existen muchas formas de organizar una arquitectura de sistema sobre un sistema de plugins. Esta es solo una pero existen muchas más.

El sistema de plugins constituye la capa básica en la que se apoya todo el sistema. Apoyandose en el existen dos modulos que conforman la base de la aplicación el nucleo del sistema y el gestor de actualicaciones.

El nucleo del sistema es el encargado de cargar el plugin manager y, una vez cargado este, cargar todos los plugins imprescindibles para el funcionamiento del sistema, es decir, aquellos plugins sin los cuales el sistema no puede funcionar, en este caso, el interaz de usuario y la capa de acceso a la base de datos. La idea es que el nucleo se asegure de que existe una base válida para que la aplicación pueda arrancar y funcionar.

La tarea del gestor de actualicaciones consiste en centralizar todas las actualicaciones del sistema. Cuando existen actualizaciones disponibles (incluidas actualizaciones del nucleo) el nucleo cierra la aplicación y la inicia en modo actualización, en la cual no se cargan los modulos del nucleo sino que se carga solamente el gestor de actualizaciones, que se encarga de actualizar todos aquellos plugins que requieran actualización.

Por encima de los plugins que conforma el nucleo existen otros plugins que se encargan de añadir funcionalidad a la aplicación, en este caso, por ejemplo, un sistema de autenticación de usuarios que se apoya en los plugins de interfaz y de acceso a la base de datos así como un sistema de comunicaciones. A su vez existe un plugin de mensajería que se apoya en el interfaz de usuario y en el plugin de comunicaciones.

Nota final

Este artículo se ha retrasado tanto que he decidido sacar parte del código antes de haber acabado todo lo que quería hacer. Debería servir para iniciar a cualquiera en el proceso, incluso para montar una aplicación basada en plugins completamente, sin embargo no puedo garantizar que este libre de fallos (de hecho muy probablemente no lo esté) así que revisarlo antes de usarlo para cualquier aplicación en producción. El proposito del artículo es enseñar como hacer un sistema de plugins no proporcionar uno totalmente listo para producción.

Entre las ideas que faltan por implementar están un sistema de pesos para los hooks que permita definir que el orden en el que se aplicarán los hooks, así como un sistema que permita lanzar los plugins en su propio hilo y simplificar así la implementación de sistemas multihilo (por ejemplo cuando deseamos implementar un plugin de comunicaciones que debe funcionar en su propio hilo).

9
Average: 9 (17 votes)
Your rating: None