Archivo

Archivo para la categoría ‘OOP’

JSON Data Binding Wizard (Delphi 12)

martes, 19 de diciembre de 2023 2 comentarios

Hace poco que ya está disponible la última versión de RAD Studio.

La versión 12 Athens trae bastantes novedades; Podéis ver la lista completa y explicada en la propia web de embarcadero:

En esta entrada me voy a centrar en el nuevo asistente «JSON Data Binding Wizard».
Anteriormente en otras entradas ya he realizado ejemplos para trabajar con ficheros JSON. A continuación os adjunto algunos links de entradas donde por diferentes necesidades he trabajado con archivos de este tipo:

Es junto al XML el formato más utilizado para intercambio de datos en la web y mayoritariamennte usado cuando descargamos información desde servidores REST mediante API, como se hace en las entradas anteriores.
Lo habitual en versiones antiguas de Delphi, es utilizar una librería externa ya que Delphi no la trae integrada (lkJSON, SuperObject,…) y en las versiones nuevas de Delphi ya se puede utilizar la que trae el propio Delphi (System.Json, REST.Json).

Lo que he necesitado hacer en esos ejemplos, es leer la estructura de datos y navegar por esa estructura jerárquica del JSON (similar al XML) e ir saltando por diferentes nodos hasta encontrar la información que necesitamos. Si el archivo es muy grande y la estructura compleja con muchos niveles, esta navegación (y su implementación puede ser más o menos compleja). Para escribir debemos completar los diferentes nodos de la estructura para finalmente generar el JSON.

Leer más…

Categories: Código, Delphi, JSON, OOP Tags: , ,

Convertir Test de DUnit a DUnitX (Entrega 3)

lunes, 30 de abril de 2018 Sin comentarios

Con este artículo pensaba completar esta serie referente a pruebas unitarias y a los Frameworks más utilizados en Delphi para realizarlas, pero el tema de conversión y migración de test entre DUnit y DUnitX se ha alargado más de lo esperado, así que para no generar una entrada muy larga y mezclar temas he decidido «partirla» en dos».

De esta forma dejo para la siguiente el Framework DUnitX en profundidad y en esta me he centrado en explicaros cómo migrar los test existentes que tengamos desarrollados en DUnit.

En las últimas versiones de Delphi, DUnitX es el framework que se recomienda usar desde Embarcadero. Veremos que es más flexible y parametrizable que DUnit, aunque si hemos trabajado con DUnit, DUnitX nos será muy familiar en su mayoría de aspectos (es una evolución de DUnit, NUnit y algunos otros…).

Si ya hemos trabajado con DUnit antes que con DUnitX (como hemos hecho en las entradas del blog) podemos «migrar» nuestros test de forma sencilla, ya que existen determinadas Units ya creadas para adaptar nuestros test existentes.

Leer más…

Categories: Delphi, OOP, TDD, Test Tags: , , , ,

Multi-Threading for TBitmap, TCanvas, and TContext3D

viernes, 7 de abril de 2017 8 comentarios

Mucho se ha hablado de las características de Tokyo en cuanto al soporte de nuevas plataformas. Está claro que la irrupción de Linux entre las plataformas destino de nuestras aplicaciones, es el gran atractivo de esta nueva versión. Otros compañeros de la comunidad han hablado del tema y han publicado vídeos al respecto.

En mi caso voy a hablar de otra de las novedades de la versión Tokyo. Se trata del soporte de multithread para las clases TBitmap, TCanvas y TContext3D en Firemonkey para poder trabajar con un único elemento desde diferentes threads.

Tal y como se explica en el enlace de la wiki, internamente las clases realizan la sincronización de forma automátca, así que aunque no ganemos en rendimiento, si podemos ganar en organización y en claridad a la hora de escribir nuestro código y clases. Imaginad que tenemos que dibujar diferentes objetos o elementos en un TCanvas. Ahora podamos organizar el trabajo en diferentes clases (Threads) que se encarguen de dibujar cada uno de ellos. De otra forma tendríamos que tener un único código o clase donde se dibujaran todos ellos (por lo tanto menos estructurado y organizado).

Leer más…

Hablando del tiempo… (OpenWeatherMap) 1/2

viernes, 4 de diciembre de 2015 13 comentarios

clima_gdeHace unos días pensaba en un tema para una nueva entrada del blog.
No tenía ideas, así que como solemos hacer aquí cuando no tenemos de qué hablar, se me ocurrió “hablar del tiempo”… (gracias a Javi desde aquí, que me fue realmente el que me puso en la pista del tema ;-) ).

Pues ya está, decidido, hablemos del tiempo.

Tenía en mente escribir sobre algunas de las novedades de las últimas versiones de RAD Studio y utilizaremos el tema para realizar algunas pruebas y demostrar cómo funcionan. Para ellos y para hablar del tiempo, vamos a utilizar en la entrada OpenWeatherMap.

¿Qué es OpenWeatherMap?

Según la web se describe como, “un servicio que provee de datos del tiempo de más de 200.000 ciudades y de todas las ubicaciones geográficas que se encuentra disponible en la web OpenWeatherMap.org y también a través de API. La idea del servicio está inspirada en OpenStreetMap y Wikipedia que ofrecen la información gratuita y disponible para todo el mundo.”

 APY Key

OpenWeatherMap es un servicuio gratuito y para poder trabajar con él, deberemos crear una cuenta y así acceder a una API Key, que es obligatoria a la hora de realizar peticiones a la web.

Para esta entrada y los ejemplos de código he dado de alta una cuenta gratuíta y he obtenido la siguiente API Key:

278857e8dee51f914026df21d0d40c19

_________________________________________________________________
NOTA (21/01/2021):
Algún usuario del blog ha utilizado esta clave, en lugar de para hacer pruebas, para otros temas, de manera que desde la página de OpenWeatherMap me han informado de un tráfico excesivamente alto y de que se bloqueará esta APIKey cuando lo exceda. Siento que para los que accedéis al blog esta deje de funcionar (a ratos). Si queréis hacer pruebas debréis crear vuestra propia clave.
Desgraciadamente a veces intento facilitar las cosas a los que accedéis a estas entradas, pero hay quien prefiere no seguir las reglas y usar el camino fácil.
_________________________________________________________________

AVISO: Para seguir esta entrada y probar los ejemplos, podéis usar esta clave, pero si váis a programar aplicaciones o vuestros propios ejemplos, os aconsejo que déis de alta vuestra propia cuenta y obtengáis vuestra clave, pues en cualquier momento ésta puede dejar de estar activa.

La explicación completa de cómo utilizar el valor de la «API KEY» lo tenéis en este link.

Leer más…

Categories: Aplicación, Delphi, OOP, REST Tags:

Persistencia de una estructura de clases.

viernes, 8 de marzo de 2013 9 comentarios

core_data_image_1Esta entrada nace de una necesidad, la que he intentado explicar en el título, pero que tal vez, por la falta de espacio ha quedado «parca» y poco clara. Se trata de una estructura de clases almacenada en memoria y que utilizo en una de mis aplicaciones. Llegado a este punto tengo la necesidad de «respaldar» esta estructura  en disco, para posteriormente, desde este mismo programa o desde otro, poder recuperarla. Lo que comúnmente podemos llamar como un backup/restore.

Se trata de una estructura jerárquica de clases, en la que unas incluyen a otras y en la que además podemos encontrar listas de elementos. En una clase podemos encontrar propiedades de tipos simples (string, cadena), propiedades con objetos de otras clases y listas que almacenan objetos de otras clases.

Leer más…

Categories: Delphi, OOP, RTTI Tags: , ,

VCL y FireMonkey

martes, 4 de octubre de 2011 10 comentarios

imagen1

Está claro que FireMonkey (FM) es una tecnología súmamente atractiva y desde el mismo día que llegó (y vimos los primeros vídeos), la mayoría de nosotros está deseando poder integrarla en nuestras aplicaciones.

En el momento en que nos planteamos desarrollar aplicaciones nuevas, podemos tomar la decisión de escoger FireMonkey, si nuestras necesidades están cubiertas por esta librería (componentes y funcionalidad),  pero la pregunta que nos hacemos muchos de nosotros es:

¿Y qué hay con las aplicaciones que ya tenemos?

Está claro que no podemos “mezclar” en un proyecto formularios de la VCL y de FireMonkey. La ayuda nos dice lo siguiente (docwiki):

______________________________________________________________________
Caution:
FireMonkey (FMX) and the Visual Component Library (VCL) are not compatible and cannot be used in the same project or application. That is, an application must be exclusively one or the other, either FireMonkey or VCL. The incompatibility is caused by framework differences between FireMonkey (FMX) and VCL.
______________________________________________________________________

¿O si? (bueno luego hablaremos de esto…)

Sólo basta con intentarlo en el IDE; Creamos un proyecto/aplicación VCL e intentamos añadir un formulario «tipo FireMonkey»; Instantáneamente obtendremos un mensaje como este:

Imagen907

El mensaje es claro. No podemos añadir a un “proyecto VCL” un formulario FireMonkey.   ;-(
Bueno, no desesperemos; Delphi es mucho Delphi y ofrece “otras” posibilidades… (Packages)

Me he planteado este problema, mi cabeza ha empezado a dar vueltas y he comenzado a “trastear”…  ;-D

NOTA: Estoy pensando en aplicaciones que tenemos funcionando en Windows y que queremos que sigan así (descartando características de Multiplatarforma –al menos a priori-).

NOTA2: Como en las últimas entradas, he cogido Delphi XE2 y he empezado desde cero.

PRIMERA CUESTIÓN

Crear un package con un formulario FireMonkey.

Pues me he puesto manos a la obra…
He abierto un nuevo package y he añadido un formulario de FireMonkey; Un par de controles, un Timer y una vez compilado, se ha generado el package correspondiente (sin aparentes problemas).

Imagen908

Como veis a la izquierda la estructura del formulario es sencilla y es lo único que posee el package.

SEGUNDA CUESTIÓN:

¿Podré cargar este package desde una aplicación Delphi (VCL)?

Bueno, si el package se ha generado sin problemas, cargarlo de forma dinámica desde una aplicación no tendría porque fallar.

Creo un proyecto VCL estándar y utilizo el procedimiento LoadLibrary para cargar una BPL de disco; La BPL en la que he añadido el formulario FireMonkey. El código de la carga es el mismo que para cualquier otro package.

 

var
  H: Thandle;
  fName:String;
begin
  // Nombre del package
  fName := Edit1.Text;
  // existe el fichero?
  if not FileExists(fName) then begin
    Memo1.Lines.Add('Error al cargar el package; Fichero inexistente: ' +
      fName);
    Exit;
  end;
 
  Memo1.Lines.Add('Se va a cargar el package...');
  // Cargando
  H := LoadPackage(Edit1.Text);
  // Cargado correctamente?
  if (H > 0) then begin
    Memo1.Lines.Add('El package se ha cargado correctamente (' +
       IntToStr(h) + ')');
  end
  else begin
    Memo1.Lines.Add('Error, no se ha cargado el packae');
  end;

 

En este caso el nombre del package lo “pasamos” utilizando un componente de edición. Además he añadido al packages un par de procedimientos de INITIALIZATION y FINALIZATION; El de inicialización se encarga de crear una instancia del formulario y mostrarla por pantalla, mientras que el de finalización lo libera. El código que añadiríamos al final del .PAS sería este:

procedure Start_Pack();
begin
  // Crear el formulario
  FormMainFM := TFormMainFM.Create(nil);
  FormMainFM.ShowModal;
end;
 
procedure Finish_Pack();
begin
  // Liberar el form
  FormMainFM.Free;
end;
 
//=========================================================================
// I N I T I A L I Z A T I O N
//=========================================================================
initialization
  Start_Pack();
  RegisterClass(TFormMainFM);
 
//=========================================================================
// F I N A L I Z A T I O N
//=========================================================================
finalization
  Finish_Pack();
  UnregisterClass(TFormMainFM);

Si ejecutamos el programa podemos ver que el Package se carga correctamente y que en ese momento se ejecuta el procedimiento de inicialización, con lo que se crea el formulario (FMX). He añadido estilos a ambos formularios para que se aprecie la diferencia; Al formulario de la VCL con los Visual Styles y al de FireMonkey con el componente TStyleBook. El resultado es el que véis aquí:

Imagen909

Llegados a este punto. Pues he de decir que yo personalmente me he llevado una sorpresa. Ya se lo que dice la ayuda (lo he vuelto a leer), pero la aplicación se ha ejecutado y aunque es sencilla, aparentemente no parece tener problemas (he realizado la prueba en XP, en Windows Server y en Windows 7) .

Resumiendo… Lo que he hecho hasta ahora es generar un proyecto en Delphi (VCL) compilarlo sin runtime packages y cargar una BPL de forma dinámica (como si fuera una DLL) que a su vez muestra un formulario “FireMonkey” cuando se Inicializa. No está mal, aunque podemos ir un poco más allá.

¿Me pregunto qué pasará cuando intente acceder por RTTI a la información del package?

TERCERA CUESTIÓN:

Acceder por RTTI desde una aplicación “VCL” a los métodos de un form “FireMonkey” almacenado en una BPL.

Para ello lo primero que debemos hacer algunos cambios:

  1. Nuestra aplicación  VCL, ahora debe compilar con “runtime packages” para poder compartir la información RTTI con los packages dinámicos.
  2. En nuestro package que contiene el formulario “FireMonkey” vamos a Registrar la clase del formulario, para poder acceder a ella posteriormente con GetClass. Para ello utilizaremos los procedimientos de Inicialización y Finalización vistos anteriormente.
    //=========================================================================
    // I N I T I A L I Z A T I O N
    //=========================================================================
    initialization
      //-- Start_Pack();  // Lo crearemos “manualmente”
      RegisterClass(TFormMainFM);
     
    //=========================================================================
    // F I N A L I Z A T I O N
    //=========================================================================
    finalization
      //-- Finish_Pack();
      UnregisterClass(TFormMainFM);
  3. A nuestro formulario vamos a añadir en la parte published, varios métodos de clase (aunque esto último no es necesario) a los que accederemos vía RTTI. Un Timer y un par de procedimientos para controlar una animación. Además de un procedimiento que creará el formulario (ExecForm).
  4. Por último vamos a eliminar la creación del formulario en la carga del package, para hacerlo nosotros vía RTTI. Si os fijáis en el código superior, he comentado los procedimientos Start_Pack y Finish_Packque se encargaban de la creación y destrucción.
      // en la parte published del formualrio
      published
        // Método de clase para crear y visualizar
        class procedure ExecForm();
        class procedure Start();
        class procedure Stop();

     

      // Y la implementación (asumimos que el formulario ya estará creado)
      //...// Actiar el Timer para iniciar la animación
    class procedure TFormMainFM.Start;
    begin
      FormMainFM.TimerRot.Enabled := True;
    end;
     
    // Para el Timer y detener la animación
    class procedure TFormMainFM.Stop;
    begin
      FormMainFM.TimerRot.Enabled := False;
    end;
     
    // Crear el formulario y visualizarlo
    class procedure TFormMainFM.ExecForm();
    begin 
      FormMainFM := TFormMainFM.Create(nil); 
      FormMainFM.Show;
    end;

Hecho esto, nuestro método de carga ahora es un poco más complejo. Una vez cargado el package de forma dinámica, busco mediante RTTI la clase del formulario e intento crearlo. El código es el siguiente:

var
  H: Thandle;
  res:integer;
  fName:string;
  AClass:TPersistentClass;
begin
 
  fName := Edit1.Text;
 
  // existe el fichero?
  if not FileExists(fName) then begin
    Memo1.Lines.Add('Error al cargar el package; Fichero inexistente: ' +
       fName);
    Exit;
  end;
 
  Memo1.Lines.Add('El fichero de package existe en disco');
  Memo1.Lines.Add('Se va a cargar el package...');
 
  // Cargando
  H := LoadPackage(Edit1.Text);
 
  // Cargado correctamente?
  if (H > 0) then begin
    Memo1.Lines.Add('El package se ha cargado correctamente (' +
         IntToStr(h) + ')');
 
    // Acceder a la clase del form
    AClass := GetClass('TFormMainFM');
    // encontrada
    if Assigned(AClass) then begin
      Memo1.Lines.Add('AClass<>nil; Encontrada la referencia' +
          ' al formulario');
      // Crear el form
      F := TFormClass(AClass).Create(nil);
      Memo1.Lines.Add('Creado el form correctamente; Accediendo ' +
        ' a los métodos');
      // Acceder al método
      Routine.Data := Pointer(F);
      // Ejecutar al código
      Routine.Code := (F).MethodAddress('ExecForm');
 
      // No ha encontrado el código de la rutina...
      if (Routine.Code <> nil) then begin
        Memo1.Lines.Add('Se ha encontrado el punto de entrada del método ');
        Memo1.Lines.Add('Se va a ejecutar el método...');
        // Ejecutarlo
        TExecuteExecForm(Routine);
        Memo1.Lines.Add('Ejecutado OK. Form visible');
 
        btnStart.Visible := True;
        btnStop.Visible := True;
        Button1.Enabled := False;
      end
      else  begin
        Memo1.Lines.Add('No se ha encontrado el punto de entrada del método ');
      end;
    end
    else begin
      Memo1.Lines.Add('Error, no se ha encontrado la referencia a la ' +
        'clase en el package ' + fName);
    end;
  end
  else begin
    Memo1.Lines.Add('Error, no se ha cargado el package');
  end;
end;

 

He utilizado un Memo en el formulario principal para ir mostrando los diferentes mensajes (los pasos que se van realizando). Una vez que ejecutamos el código, podemos ver que la salida de LOG es la esperada y el formulario se visualiza correctamente.

 

Imagen910

 

De la misma manera, si ejecutamos los métodos Start y Stop (vía RTTI) funcionan de forma correcta.

CONCLUSIÓN:

La primera conclusión que extraigo de todo esto (contando que las pruebas que he realizado son bastante sencillas), es que utilizando packages (creo que es la mejor forma), tenemos la posibilidad de utilizar FireMonkey en una aplicación «VCL». Está claro que esto invalida la posibilidad de utilizar la «multiplataforma» con esa aplicación, pero tal y como os he comentado, no era ese el objetivo de estas pruebas.

¿Para qué puede servir esto?

Se me ocurren a priori 2 situaciones donde esto podría ser «utilizable».

La primera es que tengamos aplicaciones antiguas desarrolladas en Delphi y a las que queramos «añadir» alguna característica donde FireMonkey pueda aplicar su potencial. Por ejemplo, un módulo donde tengamos que realizar animaciones o visualizaciones 3D; Incluso sacar partido de la facilidad que FireMonkey tiene para trabajar con modelos importador de herramientas de modelado (3DS).

La segunda, es la de poder «migrar» de forma paulatina aplicaciones que tengamos funcionando; Si cumplen unos determinados requisitos, puede ser que la migración se pueda hacer de forma escalonada. En este caso ya deberían trabajar con packages y facilitaría mucho si la lógica de negocio la tenemos «independiente» a la parte de Interficie.

Todo esto con la RESERVA de ver qué otras implicaciones más profundas puede tener este diseño. El hecho de que Embarcadero diga que es incompatible utilizarlos en el mismo proyecto, debe ser por algo, aunque en este momento no se porqué.  ¿?¿?¿?

 

BONUS TRACK:

Por último se me ha ocurrido hacer una prueba más. Probar, por probar,…    ;-)

Crear un proyecto «VCL», y añadir directamente un formulario FMX al proyecto. Aunque en IDE avisa con un Warninig, deja hacerlo.
Compilas, ejecutas y ¡Voilà! ¡Sorpresa!

Se ejecuta perfectamente.

En este punto he buscado por Internet si a alguien más se le ha ocurrido hacer esto mismo y he encontrado algunas referencias. La más interesante en StackOverFlow (y con esto completo la entrada) es que apenas con 3 líneas, podemos conseguir, no sólo abrir un formulario FireMonkey como os he mostrado, sino «incrustarlo» dentro de uno «VCL».

procedure TForm3.Button1Click(Sender: TObject);
begin
  FormMainFM := TFormMainFM.Create(nil);
  FormMainFM.Show;
  Panel1.Form := FormMainFM;
  FormMainFM.Start;
end;

 

     

Aquí os enseño las imágenes en diseño y en ejecución.

Vuelvo a decir, que todo esto no creo que se pueda tomar como base para diseño de aplicaciones, entre otras cosas porque las pruebas son bastantes  «simples» y porque desde Embarcadero lo desaconsejan, pero se puede tener en cuenta como alternativa puntual.

Os adjunto los enlaces a los proyectos con código y a los ejecutable compilados por si queréis verlos funcionando. Los segundos están comprimidos con UPX para reducir su tamaño.

Aunque ya he comentado el enlace, os animo a revisar la entrada en Stackoverflow, » Delphi XE2: Possible to instantiate a FireMonkey Form in VCL application?»

Os añado también un enlace al blog de RemObjects, donde aparece una referenca a esta cuestión; «Hydra and FireMonkey – Best Friends Forever».

Como siempre, comentarios, quejas, críticas, sugerencias,…  serán bienvenidas.

Un saludo.

Categories: FireMonkey, OOP, XE2 Tags: , ,

Utilizar ADO con Threads

viernes, 14 de enero de 2011 1 comentario

Los threads (o hilos de ejecución), en general, se ven como un tema peliaudo; Son unas «cosas» que están ahí, son buenas (eso dice todo el mundo), pero cuando más se tarde en tener que tacarlos, mejor…  ;-D

Recuerdo que cuando empecé en esto de la programación, los threads o la «programación con múltiples hilos de ejecución» sonaba algo así como «muy difícil». A medida que va pasando el tiempo, uno se da cuenta que que no es así, y cuando has hecho unos cuantos ejemplos te das cuenta de la potencia que aportan y de que realmente sólo hay que tener algunas «precauciones» a la hora de utilizarlos.

En este misma página podéis encontrar algunos ejemplos («ping usando threads», Ejemplo visual y Ejemplo visual ampliado) de programación de hilos de ejecución con Delphi. Ejemplos bastantes sencillos y bastante inútiles, porqué no decirlo, exceptuando la utilidad de aprender cómo funcionan y poder ver un código simple de utilización.

La idea de esta entrada es ir un poco más allá. A veces nos encontramos en una aplicación, con determinadas consultas que tardan mucho tiempo y que no es imprescindible esperar a su finalización para poder continuar con la ejecución normal del programa. Las más comunes serías típicas consultas de Listados, estadísticas o determinadas operaciones que podríamos hacer en 2º plano. Esas consultas serían las candidatas ideales para poder lanzarlas en un Thread independiente del hilo principal del programa.

Para los ejemplos voy a utilizar los componentes ADO y accederemos a la Base de Datos dbdemos.mdb que viene con Delphi.

Para trabajar con ADO utilizando threads, o para lanzar consultas dentro de threads, la única condición es que la conexión (TADOConnection) se cree dentro del mismo thread. Utilizaremos para ello una «cadena de conexión» como propiedad del Thread.

La estructura de la clase podría ser algo así:

TADOSQLThread = class(TThread)
  private
    FADOQ: TADOQuery;
    FSQL: string;
    FTotalTime:string;
  public
    constructor Create(CreateSuspended:Boolean; AConnString:String;
                       ASQL:string);
    destructor Destroy; override;
    procedure Execute(); override;
    property SQL:string read FSQL write FSQL;
    property ADOQ:TADOQuery read FADOQ write FADOQ;
    property TotalTime:string read FTotalTime;
end;

Para lanzar desde una aplicación una consulta utilizando nuestra clase TADOSQLThread , debería bastar con asignar la conexión, la cadena SQL, lanzar nuestro thread y esperar a que acabe. El código podría ser este:

  //crear el Thread; Pasamos los parámetros de conexión y SQL
  th := TADOSQLThread.Create(True, AConnection, ASQL);
  // Evento finalizacion; Al finalizar el control me llegará hasta este evento.
  th.OnTerminate := TerminateThread;
  // Ejecutarlo (ponerlo en marcha)
  th.Resume;

El código operativo del thread es sencillo, se encuentra en el método Execute y lo único que hace es ejecutar la consulta; En el constructor cremos la nueva Query (con una nueva conexión) y asignamos la SQL.

constructor TADOSQLThread.Create(CreateSuspended:Boolean;
           AConnString:String; ASQL:string);
begin
  // Creamos el thread inicialmente suspendido (para asignarle las props.)
  inherited Create(CreateSuspended);
  // No liberar automáticamente
  Self.FreeOnTerminate := False;
  //crea el query
  FADOQ := TAdoquery.Create(nil);
  FADOQ.ConnectionString := AConnString;
  FADOQ.SQL.Add(ASQL);
  Self.FSQL:= ASQL;
end;
 
procedure TADOSQLThread.Execute();
begin
  inherited;
  // Ejecutar la consulta
  Self.FADOQ.Open;
end;

Ahora haría falta probar si en la ejecución de una serie de sentencia SQL con y sin threads se aprecian diferencias visibles. Hay que tener en cuenta que el utilizar o no threads también implica otras cosas.

No todo en este escenario son ventajas, hay que tenerlo en cuenta y entender el funcionamiento para sopesar si en cada caso concreto es beneficioso utilizar threads. Hay 2 grandes inconvenientes que a priori se detectan fácilmente cuando se ejecuta y se prueba un ejemplo como el que vamos a realizar.

Gasto de conexiones: En una ejecución normal, las consultas que se lanzan utilizan todas la misma conexión (ADOConnection); Una premisa que hemos marcado para trabajar con threads, es que cada thread debe funcionar con su conexión propia. Esto implica que en un caso estamos utilizando una única conexión y en el otro X conexiones concurrentes. Esto puede ser un problema en segun qué sistemas.

Sobrecarga de tiempo: El segundo problema (derivado en cierta manera del primero) es la sobrecarga de tiempo que la creación y activación de las nuevas conexiones provoca. Crear, activar (sobre todo este) y liberar las conexiones de cada thread es un tiempo añadido que hay que tener en cuenta.

Estos 2 problemas no se puede solucionar (del todo), pero sí mitigar utilizando un «pool de conexiones«; No es un tema para desarrollar ahora (tal vez más adelante), pero la idea explicada de forma sencilla, es que podemos utilizar un número máximo de conexiones (no tantas como threads). De esta forma, se asigna una conexión libre a un thread cuando la necesita, mientras haya conexiones libres; Cuando ya no quedan libres, el thread debe esperar a que una finalice para que le sea asignada. De esta forma podemos fijar el número máximo de conexiones que se utilizan y además optimizar tiempo, ya que esas conexiones se pueden «reaprovechar» de forma que no exista la necesidad de crear/activar/destruir cada una de ellas.

¿Cuando usar threads y cuando no?

La regla sencilla sería: «Cuanto más grandes y pesadas sean las consultas, más a cuenta sale utilizar threads».

Si lanzamos 20 consultas que tardan muy poco tiempo, el retraso en crear/activar las conexiones de cada una de ellas puede hacer que el tiempo de preparación sea mayor que el de la propia consulta; En ese caso estaremos «gastando» mas tiempo en «preparar» que en «consultar. Por el contrario si esas 20 consultas tardan 30 segundos cada una, el tiempo de extra de conectar para cada una de ellas puede pasar desapercibido (cuando mayor sea el tiempo de consulta, más eficiente este sistema).

Resultado de las pruebas

En las pruebas he lanzado una serie de consultas de forma secuencial. Hay que notar que el tiempo total (para consultas grandes) es sensiblemente menos cuando utilizamos threads; Pero no sólo hay que tener en cuenta el tiempo total, sino el intervalo en que tenemos acceso al resultado de cada consulta.

De forma secuencial, si la primera consulta tarda 10, la segunda 5 y la tercera 7; El tiempo total es de 22, pero los tiempos de acceso a los resultados son 10, 15 y 22 segundos respectívamente; En cambio si esto se hiciera con threads, aun suponiendo que el tiempo total fuera el mismo, los tiempo de acceso a los resultados serían 10, 5 y 7 segundos.

Select * from CustomerSelect * from Employee
Select * from CountrySelect * from items
Select * from Parts

Select * from VendorsSELECT employee.* FROM employee ORDER BY Salary,
LastName DESC , FirstName DESC , HireDate DESC

SELECT employee.* FROM employee ORDER BY Salary DESC

SELECT customer.*, orders.*, items.*, parts.*, vendors.*,
vendors.State, items.Discount, orders.SaleDate, *
FROM vendors INNER JOIN (parts INNER JOIN ((customer INNER JOIN orders
ON customer.CustNo = orders.CustNo)
INNER JOIN items ON orders.OrderNo = items.OrderNo)
ON parts.PartNo = items.PartNo)
ON vendors.VendorNo = parts.VendorNo
ORDER BY vendors.State, items.Discount DESC , orders.SaleDate

La primera prueba consta de una serie de consultas que tardan muy poco tiempo, con la Base de Datos de Access DBDEMOS.MDB (que se adjunta con Delphi). En este caso se puede ver que los tiempos de las consultas individuales son sensíblemente más bajos sin threads que con threads, debido a que las consultas con threads incluyen el tiempo de conexión. Finalmente aunque los tiempos individuales son mayores (con threads) el tiempo total queda bastante igualado (se compensa la ejecución con threads con la pérdida en las conexiones).

DATOS SIN THREADS.

DATOS CON THREADS

¿Qué pasaría si lanzáramos algunas consultas que tarden más tiempo?

Para el ejemplo he utilizado datos propios conectando a SQL Server, ya que los de la Base de Datos DBDEMOS sólo nos sirven para realizar pequeñas pruebas. Os animo a que cambieis la conexión ADO que viene en el ejemplo y configuréis vuestra propia conexión y vuestras propias consultas para realizar las pruebas.

Para la conexión basta con pulsar el botón que aparece en la parte derecha de la conexión:

Y para las consultas,  basta con tener la precaución de colocar el caracter @ al inicio de cada una de las SQL (sólo cuando empieza la consulta, no en el salto de línea).

En este caso, vemos que los resultados sí cambian sensiblemente; Lo primero que nos llama la atención, es la diferencia de tiempo total de la serie de consultas (con y sin threads); He realizado unas cuantas ejecuciones, alternando primero unas y luegos las otras y los resultados de tiempos son estos; Los primeros son las consultas normales y los segundos con threads:

Sin threads:

·············································
Tiempo total(todo): 01:19:359
Tiempo total(todo): 01:18:516
Tiempo total(todo): 01:04:500
Tiempo total(todo): 01:08:969
Tiempo total(todo): 01:09:718
·············································

Con threads:

·············································
Tiempo total con threads(todo): 01:00:000
Tiempo total con threads(todo): 00:46:800
Tiempo total con threads(todo): 00:45:484
Tiempo total con threads(todo): 00:53:078
·············································

Posteriormente he lanzado, para variar, 4 ejecuciones concurrentes de ejemplo; 2 con threads y 2 sin threads y el resultado ha sido similar (en cuanto a la diferencia):

·············································
Tiempo total(todo): 01:48:984
Tiempo total(todo): 01:50:860
·············································
·············································
Tiempo total con threads(todo): 01:27:593
Tiempo total con threads(todo): 01:32:860
·············································

CONCLUSIÓN: Aunque el ejemplo es bastante sencillo, y la clase que implementa los threads tiene poca «chicha» yo creo que se ven las posibilidades de utilizar esta opción. También debe quedar claro que no es algo para usar «siempre»; Hemos visto que dependiendo del escenario donde se utiliza puede resultar inútil e incluso contraproducente, ya que gasta más recursos que la técnica sin threads. Como ventaja tenemos que la utilización de threads, en general, nos aporta paralelismo y mayor control en la ejecución del programa (ya que evitamos el «bloqueo» en el caso de una consulta muy costosa).

El código fuente del ejemplo, el binario podeís descargarlo desde aquí.

<DESCARGAR CODIGO FUENTE>

Un saludo.

 
Categories: Artículos, Delphi, OOP Tags: , ,

Póster de pared…

martes, 5 de octubre de 2010 1 comentario

Si no os acaba de gustar o no es vuestro estilo, un póster como el de Raquel Welch, que cuelga Andrew Dufresne (Tim Robbins) en la película «Cadena perpetua», o simplemente sois un poco más «frikis» y/o  fans de Delphi, os adjunto este que hoy he visto en «TheRoadToDelphi» y que me ha traído gratos recuerdos.  ;-)

VCL Object Hierarchy

Un saludo.

Categories: Delphi, Humor, OOP Tags:

DLL’s, BPL’s, Carga dinámica/Estática y «Packages en Runtime»

lunes, 19 de julio de 2010 9 comentarios

Cuando uno empieza a conocer Delphi (al menos a mi me pasó) y dependiendo de si «viene» de otros lenguajes, se hace un lío con las diferentes formas en que podemos generar un programa; Con la forma de trabajar con packages o sin packages, con los ficheros a distribuir,… Y le empiezan a surgir diferentes preguntas:

¿Porqué mi ejecutabe ocupa tanto si sólo he puesto un botón?
¿Qué son los packages de liberías? ¿Los debo copiar con mi aplicación?
¿Porque si marco «Build with runtime packages» mi programa ocupa tampoco?
¿Porque si marco «Build with runtime packages» mi programa no funciona en otras máquinas?
¿Como trabajar con DLL’s? ¿Y con BPL’s? ¿Cual es mejor? ¿Es lo mismo?

Y muchas otras relacionadas con los elementos que aparecen en el título de esta entrada.

Mi idea en este caso es, intentar aclarar algunos de estos términos y las diferentes formas de generar un programa desde Delphi (utilizando DLL’s y BPL’s -packages-). Otra cosa, es que lo consiga…  ;-D

Antes de comenzar, debemos aclarar algunos términos o elementos que debemos tener claros antes de continuar.

——————————————————————————————————————————————-

BWRP = «Build with runtime packages»

Esta opción se encuentra en las «Opciones de proyecto/Packages» y con ella conseguimos:

  • ACTIVADA: El programa no incluye las librerías (normalmente la VCL y componentes de terceros) en el EXE; El ejecutable es más pequeño, pero necesita de los packages (BPL’s) de la VCL; Al distribuir la aplicación se deben copiar también esos ficheros (los necesarios -NOTA1-) y los packages que hayamos creado nosotros.
  • DESACTIVADA: El programa incluye en el EXE los packages de librerías y componentes de terceros. En un único fichero va todo incluído. El EXE tiene mayor tamaño (lógico porque todo va incluído).

NOTA1: Se puede saber qué librerías (DLL’s o BPL’s) necesita un ejecutable utilizando herramientas como «Dependency Walker», GExperts o CnWizards.

DLL = «Dinamic Link Libray / Librería de enlace dinámico»

Son ficheros con código ejecutable, que se utilizan en sistemas Windows, y que forma parte de otras aplicaciones o del propieo sistema operativo. Permiten hacer que los programas sean más pequeños y que el código que contienen pueda ser usado por diferentes aplicaciones.

BPL = «Borland Package Library»

En pocas palabras podemos decir que una BPL es una DLL sólo para Delphi. Las BPL’s son más potentes que las DLL’s. Permiten almacenar más información fFunciones, procedimientos, Units, pero también Clases, Objetos y Componentes). La mayor potencia de estos ficheros se «paga» con la exclusividad de poderlos utilizar únicamente en Delphi.

Carga estática de librerías

Tanto para DLL’s como para BPL’s, cuando hablamos de carga estática, nos referimos a que al ejecutarse el programa, se carga automáticamente esa librería. Aunque en ese momento no se vaya a utilizar el código que hay dentro de la librerías, esta se carga al iniciar. Si la librerías no existe en disco el programa falla durante la carga y no es posible ejecutarlo.

Carga dinámica de librerías

La carga dinámica de packages, tiene como contrapartida que es más compleja (programáticamente) que la carga estática. La carga se debe hacer de forma expresa por el programador cuando se dese. Esto significa que sólo se cargará esa librería cuando se necesite. El programa puede funcionar aunque no esté (el programador deberá comprobarlo antes de cargarla). Este sistema hace que este tipo de carga sea ideal para sistemas con plugins/Addins.

——————————————————————————————————————————————-

Una vez revisado esto, voy a intentar explicar las diferencias al crear un proyecto, segun si utilizamos DLL’s o BPL’s y carga estática/dinámica.

EXE + DLL con Carga estática(El EXE puede ser con o sin BWRP)

Tal y como hemos dicho, la DLL debe existir en disco cuando se ejecuta la aplicación, de otra forma esta no funcionaría y fallaría al arrancar.
Hay que definir dónde se encuentra la DLL (en el que la carga -el EXE-)

function Sumar(x,y:integer):Integer; stdcall; external 'sumas.dll';

Y con eso ya se puede hacer la llamada:

Res := Sumar(4,5);

EXE + DLL con Carga dinámica(El EXE puede ser con o sin BWRP)

En este caso el programa puede funcionar aunque la DLL no exista; En el momento de intentar cargarla se debe comprobar si está o no en disco. Se puede cargar cuando se necesita y descargarla cuando ya no es útil. La carga es un poco más compleja que el caso anterior.

Se utiliza LoadLibrary para cargar la DLL en el momento en que se necesita:

H := LoadLibrary('SUMAS.DLL');

Para acceder a las funciones de la DLL se usa GetProcAddress:

@sum := GetProcAddress(H, 'Sumar2');

En el programa que hace la llamada (el EXE) debe estar predefinido el tipo de la función a la que vamos a realizar la llamada:

Sum: function(x,y:Integer):integer; stdcall;

EXE + BPL con Carga estática(El EXE puede ser con o sin BWRP)

En este caso no hace falta definir nada referente a la función que se encuentra en el package, simplemente hacer referencia a ella en diseño (añadiendo la unit al USES).

Si el programa principal (EXE) se compila con «BRWP Desactivado» todo va dentro del EXE (componentes de terceros y código de la VCL); Si se compila con «BWRP activado» la VCL y los packages de terceros van aparte; Estos packages se deberán distribuir junto al EXE.

Utilizando carga estática es obligatorio que exista el package en disco y que esté accesible al arrancar la aplicación; Si la BPL no existe el programa no puede funcionar.

EXE + BPL con Carga dinámica + RTTI(El EXE debe ser con BWRP)

Es el caso más potente que tenemos. Cargar el package (hemos dicho que las BPL’s son más potentes que las DLL’s) de forma dinámica. Para ello hacemos uso de RTTI (Runtime Type Information) que es lo que le da la potencia extra a las BPL’s. Es obligatorio en este caso que «BWRP esté activado»; Es decir, obligatoriamente debemos distribuir nuestra aplicación con packages.

Los packages se cargan de forma dinámica con LoadPackage (de forma similar a cómo se hace con LoadLibrary):

H := LoadPackage('RESTAS.BPL');

Podemos acceder a las funciones de la misma forma que lo hacemos con las DLL’s, utilizando GetProcAddress:

@resta := GetProcAddress(H, 'Resta');

Podemos utilizar RTTI para acceder a classes que previamente se han registrado (con RegisterClass) utilizando funciones de RTTI como GetClass:

AClass := GetClass('TFormMain');

Y podemos acceder a métodos de las clases utilizando también RTTI, utilizando MethodAddress:

// Acceder al método
Routine.Data := Pointer((F as FormClass));
// Ejecutar al código
Routine.Code := (F as FormClass).MethodAddress('ExecForm');

No es necesario definir nada en el programa principal, puesto que podemos obtener todo la información de las clases definidas en el package utilizando RTTI.

Quedaría una última combinación, posible, pero un poco extraña, que sería EXE sin «runtime packages» y con carga dinámica. Y digo que es un poco rara, porque es un poco contracorriente. Utilizar BPL’s tiene la ventaja que le confiere utilizar RTTI, sin ella los BPL’s pasan a ser DLL’s normales. Esta opción haría justo eso, utilizar BPL’s, pero sin RTTI.

EXE + BPL con Carga dinámica sin RTTI(El EXE sin BWRP)

Esta opción (un poco rara) sería la de utilizar una aplicación con «BRWP Desactivado» (es decir TODO en un único EXE) y cargar en él package de forma dinámica. Esto es posible, pero en este caso perdemos la opción de utilizar RTTI; El EXE (fichero único) podría funcionar sin que exista la BPL que vamos a cargar. Al no poder utilizar RTTI, las «mejoras» que tienen las BPL’s se pierden y pasan a usarse como DLL’s.

Se pueden cargar las BPL’s de forma dinámica con LoadPackage:

H := LoadPackage('RESTAS.BPL');

Y para acceder a las funciones usamos GetProcAddress (como en las DLL’s):

@resta := GetProcAddress(H, 'Resta');

Por supuesto si intentamos acceder a información de clases (GetClass) el programa falla.
Más o menos hasta aquí las diferentes opciones de compilar el proyecto y algunas referencias a cómo hacer las llamadas y las definiciones.

Todo ello está implementado en los ejemplos que ajunto al final de esta entrada. Hay un grupo de proyectos (compilado con Delphi 6) que incluye los siguientes ficheros:

  • sumas.dpr: Proyecto que genera un DLL con una función de suma
  • Call_suma_estat.dpr: Proyecto para llamar a una DLL con carga estática.
  • Call_suma_dinam.dpr: Proyecto para llamar a una DLL con carga dinámica.
  • restas.dpk: Proyecto para generar una BPL con una función de resta y un formulario.
  • Call_resta_estat.dpr: Proyecto para llamar a la BPL con carga estática.
  • Call_suma_dinam.dpr: Proyecto para llamar a la BPL con carga dinámica (uso de RTTI y proyecto compilado con Runtime Packages).
  • Call_resta_dinam_sin_pack.dpr: Proyecto para llamar a la BPL con carga dinámica, pero sin compilación con packages (sin uso de RTTI).

El Grupo de proyectos completo se puede descargar desde aquí.

[Link descarga código fuente]

AÑADIDO: El otro día me dejé sin poner algunas referencias que tengo sobre el tama y que me parecen interesante, así que ahí van:

Categories: OOP Tags: , ,