miércoles, 13 de junio de 2012

Carga dinámica de paquetes y reflexión (Una agradable sorpresa)


Fuente Original: Desarrollo de Software 

Introducción

Hacia años que no tenia que meterme con las características de reflexión de delphi. Había trabajado bastante en .Net pero en Delphi poco y nada. Sabia que en la version 2010 de delphi se había evolucionado mucho en este sentido pero no sabia cuanto y a priori imagina las restricciones obvias de un lenguaje compilado.
La reflexión seria como la capacidad que tiene nuestro compilador de analizar y accionar sus elementos en tiempo de ejecución.
En lo que nos toca en esta entrada apuntamos a analizar una clase totalmente desconocida para investigar sus método y eventualmente ejecutarlos.

¿Para que puede servir?

Bueno, seguramente habrá mas casos el aqui expuesto, lo que aquí intentamos es simplemente cargar una serie de bpls, investigar sobre las clases y métodos que las mismas contienen y exponerlos. ¿Para que puede servir esto?, en mi caso particular lo que quiero es que se carguen las bpls a modo de funcionalidad agregada, luego quien configura el sistema determina que método se ejecuta según condiciones, pero los métodos disponibles se cargan dinamicamente de las bpls con las que se compra el sistema. Para mejor, se puede ir agregando bpls con mas funcionalidad sin afectar el core del producto.
Al decidir el configurador/analista que se ejecuta y cuando yo tendré que buscar y ejecutar un método mediante su nombre y para eso necesito las capacidades de reflexión que encuentro mas que aceptables en la versión actual de Delphi.
Los ejemplos de abajo han sido pelados de todas las cuestiones de bases de datos que tiene que ver con guardar los métodos disponibles y recuperar el método a ejecutar. Solo nos concentramos en lo importante: Como bucear en las bpls y como instanciar una clase y ejecutar un método por sus nombre.

¿Como cargar las bpls?

El siguiente código revisa una carpeta determinada y bpl que encuentra bpl que carga por medio del LoadPackage.


 DynamicPackageNames :=  TStringList.Create;
 sda := TDirectory.GetFiles(ExtractFilePath( Application.ExeName)+'PlugIns\', '*.bpl');

  for s in sda do begin
      hndl  := LoadPackage(s);
      DynamicPackageNames.Add(s);
  end;


Ahora lo bueno, analizo las clases, pero solo lo hago para los librerias cargadas de las bpls.

....

c : TRttiContext;
m : TRttiMethod;
c : TRttiContext;
t : TRttiType;
p:TRttiPackage;
...
for p in c.GetPackages do begin
    //Solo las cargadas de las bpls...
    if DynamicPackageNames.Find(p.Name, Index) then begin
        //Recorro las clases
         for t in p.GetTypes do begin
           //Recorro los metodos
           for m in t.GetMethods do begin
              //Esto elimina metodos de clases anteriores, como por ejemplo de la
              //Classe TComponent
              if t.Name=m.Parent.ToString then begin
                //Recorro los parametros de los metodos
                for param in m.GetParameters do begin
                end;
              end;
            end;
          end;
        end;
      end;
    end;  


Con el código anterior, se puede bucear hasta el nivel deseado en las clases presentes en la bpls cargadas dinamicamente.

Cuando encuentro lo que busco, ¿Como lo ejecuto?

 c := TRttiContext.Create;
 ty := c.FindType('MiClase') as TRttiInstanceType;
 if Assigned(ty) then begin
   //Encontro la clase que buscaba
   m := ty.GetMethod('MiMetodo');
   if Assigned(m) then begin
      //Encontro el metodo que buscaba
      //Construyo una instancia de la clase en cuestion
      SL := ty.GetMethod('Create').Invoke(ty.MetaclassType,[Self]);
      //Ejecuto el metodo
      m.Invoke(SL,  [param1, param2, paramx]);
      //Libero el objeto creado
      SL.AsObject.Free;
   end;
end;


Espero que el código aquí expuesto sea de utilidad ya que no se encuentra con facilidad documentación sobre estos temas. Me parece mas importante el código expuesto que explicar todos los casos donde se puede utilizar la reflexión.