11 abril 2008

Cazando errores con EurekaLog

Cuando uno encuentra herramientas como esta, a veces le dan ganas de darse cabezazos contra la pared después de haber sufrido los castigos del debugger de Delphi. EurekaLog es un experto que se acopla perfectamente a cualquier versión de Delphi y que permite capturar excepciones, access violation y pérdidas de memoria todo ello en una sóla herramienta (y además sin modificar para nada las opciones de Delphi o de nuestro proyecto).

Funciona incluso con las últimas versiones de Delphi (Delphi for Win32 y RadStudio 2007). Para versiones muy antiguas de Delphi, como por ejemplo Delphi 5 a veces requiere tener instalado algún Update que otro.

Aunque sea un programa comercial podemos bajar la demo de su página oficial:

http://www.eurekalog.com/downloads.php

La última versión a fecha de este artículo es la 6.0.12, teniendo el archivo de instalación un tamaño de 12.4 MB. Antes de ejecutar la instalación es conveniente tener todos los Delphi cerrados. La instalación no tiene muchas complicaciones:

Vamos pulsado el botón Next hasta que finalice la instalación. En uno de estos pasos reconoce las distintas versiones de Delphi que tenemos instaladas, permitiendo seleccionar las que nos interesen:

Cuando termina la instalación nos da la posibilidad de visualizar un videotutorial online sobre cómo funciona EurekaLog, lo cual facilita mucho las cosas.

Ahora arrancamos Delphi y abrimos un proyecto cualquiera para probarlo. Para activar EurekaLog en nuestro proyecto hay que ejecutar las opciones Proyect -> Eureka Options:

En la ventana que aparece debemos activar la opción Activate EurekaLog. Esto hará que intercepte los errores en tiempo de ejecución sin necesidad de tener activada la opción Integrated Debugging del propio Delphi. Si también queremos interceptar los errores de pérdida de memoria debemos irnos a la sección llamada Avanced Options y seleccionar la opción Catch Memory Leaks:

Después pulsamos el botón Ok y ya tenemos EurekaLog listo para cazar bichos. Vamos a verlo con unos ejemplos.

CAPTURANDO ACCESS VIOLATIONS

Uno de los errores que más solemos cometer es el utilizar objetos que no hemos creado. Por ejemplo, voy a crear un formulario con un botón en el cual si hago clic intentaré añadir un elemento a un objeto StringList sin haberlo creado anteriormente:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S.Add( 'añadiendo elemento' );
end;

Veamos como se comporta EurekaLog en este caso. Al hacer clic sobre el botón nos aparece esta ventana en vez de la típica de Access Violation:

Si pulsamos la opción click here nos mostrará esta otra ventana:

En la primera pestaña (General) nos informa de la excepción que ha provocado la aplicación así como todas las características del equipo donde se está ejecutando. Esta información es muy interesante para averiguar en que máquina esta corriendo nuestra aplicación.

En la segunda pestaña (Call Stack) podemos ver en que línea de código se ha producido el error:

Y ahora viene lo mejor de todo. Si hacemos doble clic sobre la línea azul saltará directamente a Delphi y se irá a la línea de código donde se ha producido el error. Vosotros os preguntareis ahora, ¿y que tiene de especial si el depurador de Delphi ya lo hace? Pues porque EurekaLog caza errores donde Delphi no llega. ¿Cuántos access violations os ha llevado Delphi al depurador en ensamblador? ¿Cuántas veces os ha explotado Delphi sin tener ni idea donde se ha producido el error? A mi por lo menos se me ha quedado el puntero del depurador en Application.Run y se ha quedado como Dios.

Con EurekaLog he llegado a cazar el 95% de errores de Delphi donde el debugger no llegaba. Además me sitúa el cursor exactamente en la línea de código donde se ha producido el error.

En la tercera pestaña (Modules) podemos ver nuestro ejecutable y cuantas librerías DLL hay cargadas en memoria:

En la cuarta pestaña (Processes) se ve los ejecutables que estaban corriendo en el sistema en el momento del error:

En la quinta pestaña (Assembler) nos muestra el punto de ejecución en ensamblador donde se ha quedado el procesador:

Y en la última pestaña (CPU) tenemos un volcado de los registros del procesador, la pila y la memoria del segmento actual:

Si pulsamos el botón Ok, la aplicación seguirá su curso y devolverá el control a Delphi.

AVERIGUANDO DONDE SE PRODUCEN LAS PERDIDAS DE MEMORIA

Este es otro de los agujeros negros que sufrimos los programadores de Delphi, las pérdidas de memoria (Memory Leaks). Yo hasta ahora había utilizado la unidad MemCheck.pas para cazar los errores, pero es muy incómoda su instalación y seguimiento. En este caso EurekaLog también es insuperable, cazando las perdidas de memoria al vuelo. Veamos otro ejemplo.

Voy a crear un StringList y a salirme del programa sin hacer Free del objeto:

procedure TForm1.Button1Click( Sender: TObject );
var S: TStringList;
begin
S := TStringList.Create;
S.Add( 'añadiendo elemento' );
end;

Al finalizar el programa saltará la siguiente ventana de EurekaLog:

Cuando pulsemos sobre la opción click here y nos vayamos a la pestaña Call Stack nos dirá exactamente que línea ha creado algo en memoria y no lo ha liberado:

Haciendo doble clic sobre la misma saltará a la línea de código responsable del problema:

S := TStringList.Create;

Esto deja a Delphi 2007 en ridículo en cuestión de cazar pérdidas de memoria, ya que este último IDE de CodeGear no dice ni en que unidad ni en que línea se ha producido la perdida de memoria, sólo nos dice que objetos han quedado sin liberar. Yo no se vosotros, pero para mi, eso y nada es lo mismo.

ENVIANDO LOS ERRORES POR CORREO ELECTRONICO

¿Cuántas veces os ha ocurrido que a un cliente vuestro le salta un Access Violation y a vosotros os funciona bien con la misma base de datos? Eso a mi me ha pasado un día si y otro también. Pues en ese caso enviamos el ejecutable de nuestra aplicación a nuestros clientes con EurekaLog activado y compilado, y en momento que se produzca la explosión a ellos también les aparecerá la misma ventana de EurekaLog con el error, con lo cual podrán decirnos en que unidad y línea de código se ha producido el error.

Pero no sólo eso, si no queremos complicarnos la vida, EurekaLog permite enviar por correo electrónico toda la información perteneciente al error que se ha producido. Para ello volvemos a la ventana de opciones de EurekaLog y nos situamos en la primera sección (Email & WebSend):

Seleccionamos la opción SMTP Client dentro del campo Send Mode y rellenamos el resto de campos con la cuenta de correo que vamos a utilizar para enviar los errores. Esa cuenta se puede utilizar para enviarse errores así misma, gastando pocos recursos de nuestro servidor SMTP.

Cuando a nuestro cliente le ocurra un error con nuestra aplicación le aparecerá esta ventana:

Cuando pulse el botón Send Error Report comenzará a enviar el error por correo electrónico con los datos de la cuenta que le hemos dicho (si os fijáis en la imagen se puede enviar a otro correo opcional):

Junto con el mensaje de correo llevará adjunto un archivo comprimido con zip llamado BugReport.zip. Este a su vez tiene dos archivos dentro:

Screenshot.png que contiene una imagen capturada de todo el escritorio de Windows cuando se produjo el error.

ProbandoEureka.elf (Igual que el nombre de nuestro ejecutable pero con extensión .elf) que al doble clic sobre el mismo y nos abrirá un visor con el error:

Es decir, nos manda la pantalla capturada (para saber que estaba haciendo el usuario antes de saltar el error) y el archivo de depuración con la línea de código que provocó el error.

Aunque EurekaLog sea una herramienta comercial (cuesta 99 €) sin duda merece la pena comprarla si eso va a mejorar la productividad y ahorrarnos muchos dolores de cabeza. En resumen, otra herramienta imprescindible para Delphi.

Pruebas realizadas en Delphi 7.

04 abril 2008

El componente ValueListEditor

Hace bastante tiempo mostré como guardar valores dobles (nombre=valor) dentro de una lista (StringList), concrétamente fue en este artículo:

El Objeto StringList (I)

Lo que vamos a ver ahora es cómo almacenar esos mismos valores pero utilizando el componente visual de la clase TValueListEditor, el cual está alojado en la pestaña Additional:

Al insertar este componente en un formulario se muestra de este modo:


La columna Key contendrá el nombre de los valores que deseamos insertar y la columna Value el valor asociado al nombre. Supongamos que quiero que el usuario pueda modificar los datos principales de un cliente. Lo primero que vamos a hacer el cambiar el título de las columnas a través de la propiedad TitleCaptions. Vamos a sustituir Key y Value por Campo y Valor, de manera que así quedaría:

Lo siguiente que toca es introducir los campos del cliente. Para ello utilizamos la propiedad Strings del componente e introducimos los nombres de los campos en la columna Key:

Para crear más de una línea tenemos que pulsar en el teclado la tecla del cursor hacia abajo (la verdad es que este editor de valores es algo chapucero). Una vez introducidos los valores y pulsado el botón Ok dejaría este resultado:

Al ejecutar el programa se puede apreciar que este componente funciona de manera similar al inspector de objetos de Delphi (Object inspector). La primera columna se halla en modo lectura dejando que el usuario sólo pueda modificar los valores. Pero tenemos que ir un paso más allá: hay que conseguir que dependiendo del campo, el usuario sólo pueda meter valores de cierto tipo.

ESTABLECIENDO MASCARAS PARA CADA VALOR

El problema radica en que no se puede establecer el tipo de valor que puede introducirse en un campo en tiempo de diseño utilizando el inspector de objetos. Hay que hacerlo en el evento OnCreate del formulario mediante código. Por ejemplo, vamos a hacer que en el campo ID sólo se puedan introducir valores numéricos:

procedure TFPrincipal.FormCreate( Sender: TObject );
var
PropNumerica: TItemProp;
begin
PropNumerica := TItemProp.Create( ValueListEditor );
PropNumerica.EditMask := '###0';
ValueListEditor.ItemProps[0] := PropNumerica;
end;

Lo que hemos hecho es crear el objeto PropNumerica de la clase TItemProp y posteriormente hemos establecido la regla de edición asignándola al primer elemento de la lista (el campo ID).

También podemos crear otro tipo de propiedad que permita escoger el valor sólo entre una lista predeterminada. Por ejemplo, voy a hacer que el campo ESTADO sólo pueda contener los estados PENDIENTE o PAGADO:

procedure TFPrincipal.FormCreate( Sender: TObject );
var
PropNumerica, PropEstado: TItemProp;
begin
PropNumerica := TItemProp.Create( ValueListEditor );
PropNumerica.EditMask := '###0';
ValueListEditor.ItemProps[0] := PropNumerica;

PropEstado := TItemProp.Create( ValueListEditor );
PropEstado.PickList.Add( 'PENDIENTE' );
PropEstado.PickList.Add( 'PAGADO' );
ValueListEditor.ItemProps[5] := PropEstado;
end;

Al ejecutar el programa el campo ESTADO quedaría del siguiente modo:

También podemos modificar la forma en cómo se guardan los valores dentro de la rejilla a través de la propiedad KeyOptions en el inspector de objetos:

Aunque todas estas opciones hacen que este componente sea bastante útil, se echan en falta algunas características tales como valores para selección de color, máscaras para valores numéricos en coma flotante y la selección de fuentes de texto.

Pruebas realizadas en Delphi 7.

28 marzo 2008

El objeto Application

Todas las aplicaciones Win32 que se crean con Delphi tienen definido un objeto global accesible desde cualquier formulario o unidad llamado Application, de la clase TApplication, el cual esta definido dentro de la unidad Forms. Este objeto se encarga de encapsular todo lo referente a nuestra aplicación, evitando el tener que procesar el envío y recepción de mensajes como ocurre cuando se programa en C/C++ mediante las funciones PeekMessage y SendMessage.

De hecho, si miramos la unidad DPR de cualquier proyecto de Delphi podemos ver como el objeto Application se incializa, después llama al formulario principal y posteriormente comienza la ejecución de la aplicación mediante el método Run:

program Project1;

uses
Forms,
Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

Este código fuente cambia dependiendo de las preferencias que tengamos a través del menú Project -> Options (pestaña Forms). Todos los formularios que pongamos en la sección Auto-create forms serán los que se creen después de Application.Initialize.

Windows funciona internamente como un buzón donde van llegando mensajes y los va procesando en el orden de llegada, consiguiendo así el efecto de multitarea, aunque internamente sólo puede procesar un mensaje a la vez (menos en los últimos sistemas con procesador de dos o más núcleos).

El objeto Application nos abstrae de toda esta complejidad interna y se encarga de controlar todos los eventos que se producen en nuestra aplicación. Si queremos modificar el comportamiento de nuestra aplicación para ciertos eventos, tenemos en la pestaña Additional el componente ApplicationEvents que podemos colocar en el formulario principal de nuestra aplicación, controlando así eventos tan comunes como OnActivate, OnRestore, OnMessage, etc.

Por ejemplo, se puede utilizar el evento OnIdle para que nuestro programa realice cálculos complejos en segundo plano cuando el procesador esté desocupado, aprovechando al máximo el rendimiento de nuestro PC. También se puede utilizar el evento OnMessage para interceptar mensajes de Windows que incluso Delphi no tiene definidos en el objeto Application.

MOSTRANDO MENSAJES

Esta es una de las funciones que más se suelen utilizar del objeto Application:

function MessageBox( const Text, Caption: PChar; Flags: Longint = MB_OK ): Integer;

Esta función muestra al usuario un mensaje dentro de un cuadro de dialogo. Para mostrar un mensaje informativo sería así:

Application.MessageBox( 'Mensaje informativo.', 'Atención',
MB_ICONINFORMATION );

se vería así:


Otro mensaje sería para informar al usuario que se ha interrumpido un proceso:

Application.MessageBox( 'No ha rellenado el nombre del proveedor.',
'Acceso denegado', MB_ICONSTOP );

este sería el resultado:


También se puede hacer una pregunta al usuario por si desea realizar algo:

if Application.MessageBox( '¿Desea continuar?', 'Guardando factura',
MB_ICONQUESTION OR MB_YESNO ) = ID_YES then
....

este cuadro de diálogo tendría dos botones:

El parámetro flags de esta función permite que aparezcan los siguientes botones:

MB_ABORTRETRYIGNORE Anular, Reintentar y Omitir.
MB_OK Aceptar (por defecto).
MB_OKCANCEL Aceptar y Cancelar.
MB_RETRYCANCEL Reintentar y Cancelar.
MB_YESNO Si y No.
MB_YESNOCANCEL Si, No y Cancelar.

Entre los posibles mensajes que devuelve esta función (según lo que hagamos con flags) tenemos:

Constante Valor numérico Resultado
--------- -------------- ---------
IDOK 1 El usuario ha pulsado Aceptar.
IDCANCEL 2 El usuario ha pulsado Cancelar.
IDABORT 3 El usuario ha pulsado Abortar.
IDRETRY 4 El usuario ha pulsado Reintentar.
IDIGNORE 5 El usuario ha pulsado Ignorar.
IDYES 6 El usuario ha pulsado Si.
IDNO 7 El usuario ha pulsado No.

CONTROLANDO EL ESTADO DE LA APLICACIÓN

Tenemos los siguientes procedimientos disponibles para controlar el estado global de la aplicación:

procedure Minimize;

Este procedimiento minimiza la aplicación en la barra de tareas. Es importante tener bien claro cual es el formulario principal de la aplicación, ya que si minimizamos otra ventana provocará un efecto no deseado: se minimiza en la esquina inferior izquierda del escritorio. Para evitar esto siempre tenemos que procurar que el formulario que se pueda minimizar sea siempre el formulario principal. Si no fuera así, entonces recomiendo que toda la aplicación sea de tipo MDI.

procedure Restore;

Vuelve a mostrar la ventana principal de la aplicación si ha sido minimizada por el usuario o por el propio programa con el método Minimize.

procedure BringToFront;

Este procedimiento enfoca de nuevo nuestra aplicación superponiendo la ventana principal de nuestra aplicación a otras aplicaciones que se estén visualizando en este momento en Windows. Puede ser interesante cuando tenemos que informar al usuario de algún evento que se ha producido en nuestra aplicación (como hacen los antivirus).

procedure CreateForm( FormClass: TFormClass; var Reference );

Este es otro de los procedimientos más utilizados por el objeto Application. Crea el formulario que le pasemos como parámetro. El primer parámetro es la variable de la instancia que se va a crear y el segundo es la clase de la variable. Por ejemplo:

Application.CreateForm( FCliente, TFCliente );

procedure Terminate;

Hay que estar muy seguro para llamar a este procedimiento, ya que cierra toda la aplicación por la fuerza. Si tenemos algún formulario que haya instanciado algunos objetos que no ha liberado de memoria se podría quedar memoria desperdiciada en todo Windows.

procedure ProcessMessages;

Es importante llamar a este procedimiento cuando estamos haciendo un proceso largo que gasta bastante procesador, ya que si no lo hacemos así el usuario tiene la sensación de que la aplicación se ha colgado. Hay que llamar a este procedimiento en cada iteración del bucle.

LEYENDO DATOS DE NUESTRA APLICACION

El objeto Application también es muy útil para obtener información. Veamos que funciones pueden utilizarse para esto:

property Active: Boolean;

Esta propiedad nos dice si la aplicación esta activa y enfocada.

property ExeName: string;

Nos devuelve la ruta y nombre completo de donde se está ejecutando nuestra aplicación. Si nos interesa saber sólo la ruta se podría hacer esto:

ExtractFilePath( Application.ExeName );

property MainForm: TForm;

Nos devuelve la referencia al formulario principal de la aplicación. Si el formulario principal cambiara, no haría falta modificar el código de nuevo.

property ShowMainForm: Boolean;

Si desactivamos esta propiedad, cuando arranque el programa por primera vez no mostrará la ventana principal. Esto puede ser útil para mostrar la típica ventana de presentación antes de mostrar la pantalla principal. También puede utilizarse si vamos a crear un control de usuarios con clave de acceso y necesitamos que no muestre la ventana principal antes de que se haya validado el usuario.

property Title: string;

Modificando esta propiedad podemos cambiar en tiempo de ejecución el título de nuestra aplicación según su estado (se ve sobre todo cuando está minimizada en la barra de tareas).

property Handle: HWND;

Un handle es un identificador único que Windows da a todas las aplicaciones y ventanas que crea. Este handle (que en realidad es un valor numérico) es muy utilizado por funciones de la API de Windows. Por ello, esta propiedad es muy interesante para leer el handle de nuestra aplicación (cada vez que se ejecuta una aplicación Windows le da un handle distinto).

Aunque el objeto Application tiene más funciones y propiedades creo que hemos cubierto aquí las más importantes.


Pruebas realizadas en Delphi 7.

21 marzo 2008

El componente ActionMaganer

Uno de los problemas que suele presentarse a un programador de aplicaciones es cuando tiene que rediseñar de nuevo toda la interfaz del programa. Si tenemos el código vinculado directamente a botones, menús genéricos o menús contextuales nos toca de nuevo trasladar el código que había en los eventos de los componentes viejos a los componentes nuevos (haciendo copy-paste a base de bien).

Supongamos hemos programado un pequeño bloc de notas:




Para cada opción de menú tenemos definido su código correspondiente:

procedure TForm1.NuevoClick( Sender: TObject );
begin
Memo.Lines.Clear;
end;

procedure TForm1.AbrirClick( Sender: TObject );
begin
if OpenDialog.Execute then
Memo.Lines.LoadFromFile( OpenDialog.FileName );
end;

procedure TForm1.GuardarComoClick( Sender: TObject );
begin
if SaveDialog.Execute then
Memo.Lines.SaveToFile( SaveDialog.FileName );
end;

.....

Supongamos que quiero eliminar el componente de la clase TMainMenu y crear el su lugar un componente nuevo. El código que hemos visto anteriormente quedaría abandonado y habría que cortar el código de cada opción para llevárselo al nuevo componente.

Para evitar estos problemas tenemos el componente ActionMaganer que esta situado dentro de la paleta de componentes Aditional. Este componente esta diseñado específicamente para contener acciones de menús o botones pero sin estar vinculados a ningún componentes visual en concreto.

De manera que vamos a centralizar todas las acciones de nuestro formulario dentro de este componente para desvincularlo de la interfaz. Sería algo así como una programación visual dividida en dos capas: capa lógica (la cual contiene el código fuente relacionado con las acciones del usuario) y la capa visual (los componentes con los que va a interactuar el usuario).

Veamos cuales son los pasos para realizar esta tarea:

1. Insertamos un componente ActionManager en nuestro formulario:

2. Hacemos doble clic sobre dicho componente y nos aparecerá esta ventana:

Es esta ventana vamos a definir las acciones genéricas del formulario.

3. Pulsamos el botón New Action:
4. Nos aparecera en la lista Action1. Hacemos doble clic sobre el mismo y guardamos el código asociado al menú (Archivo -> Nuevo):

procedure TForm1.Action1Execute( Sender: TObject );
begin
Memo.Lines.Clear;
end;

Igualmente vamos a crear dos acciones más para guardar los eventos Archivo -> Abrir y Archivo -> Guardar como...

procedure TForm1.Action2Execute( Sender: TObject );
begin
if OpenDialog.Execute then
Memo.Lines.LoadFromFile( OpenDialog.FileName );
end;

procedure TForm1.Action3Execute( Sender: TObject );
begin
if SaveDialog.Execute then
Memo.Lines.SaveToFile( SaveDialog.FileName );
end;

5. Ahora vamos a añadir a nuestro formulario una barra de menús de la clase TActionMainMenuBar situada en la pestaña de componentes Additional:


Esto creará una barra vacía en la parte superior de nuestro formulario:


Para añadir opciones a esta barra vamos a hacer lo siguiente:

6. Hacemos doble clic sobre el componente ActionMaganer.

7. Arrastramos el objeto de la lista Action1 hacia la barra vacía que hemos insertado en el formulario:


8. Copiamos también las acciones 2 y 3.

9. Si queremos cambiar el nombre de las acciones hacemos doble clic en el objeto ActionMaganer, pulsamos Action1 y en el inspector de objetos modificamos la propiedad Caption y ponemos Nuevo. Igual hacemos para las acciones 2 y 3 que vamos a llamarlas Abrir y Guardar como. Como podemos apreciar no sólo cambian los nombres de las acciones en la lista del objeto ActionManager sino que también las situadas en la barra superior del formulario:


10. Por último y no menos importante, sería conveniente cambiar también la propiedad Name de cada una de las acciones, de modo que en el código fuente los procedimientos pasen a llamarse:

procedure TForm1.NuevoExecute( Sender: TObject );
procedure TForm1.AbrirExecute( Sender: TObject );
procedure TForm1.GuardarComoExecute( Sender: TObject );

en vez de:

procedure TForm1.Action1Execute( Sender: TObject );
procedure TForm1.Action2Execute( Sender: TObject );
procedure TForm1.Action3Execute( Sender: TObject );

De este modo tenemos separadas las acciones internas del formulario y su parte visual. Si eliminamos por error la barra superior del formulario seguimos teniendo todos nuestros eventos vinculados al objeto ActionMaganer. Así nuestro programa queda más flexible y elegante.

La mayor parte del código fuente de este artículo puede sustituirse mediante acciones predefinidas que lleva el componente ActionMaganer. Cuando creamos una acción, en vez de pulsar botón New Action tenemos que pulsar la flecha hacia abajo que hay a su derecha y seleccionar New Standar Action. En la ventana que aparece tenemos todas las típicas acciones que suelen realizarse en un bloc de notas, tales como copiar, cortar, abrir archivos, etc. También incorpora algunas funciones para recorrer los registros de las bases de datos, acelerando de este modo la implementación del programa.

Otra ventaja de utilizar este método es que mientras un programador puede dedicarse a la parte visual del formulario (creando menús, botones, iconos, etc), otro programador puede dedicarse a crear el código asociado al formulario distribuyendo de esa manera el trabajo en equipo.

Pruebas realizadas en Delphi 7.

14 marzo 2008

Creando aplicaciones multicapa (y VII)

Voy a finalizar este artículo hablando sobre la creación de aplicaciones multicapa utilizando servicios web a través del protocolo SOAP.

Para ello voy a utilizar el servidor Apache para alojar el ejecutable de mi servidor de aplicaciones.

CREANDO EL SERVIDOR DE APLICACIONES

El servidor de aplicaciones va a ser un ejecutable CGI que vamos a situar en el directorio cgi-bin de Apache. Los pasos para su creación son los siguientes:

1. Selecciona File -> New -> Other. Seleccionamos la pestaña WebServices, elegimos SOAP Server application y pulsamos Ok:


Al pulsar Ok nos preguntará el tipo de servidor que vamos a crear:


2. Como quiero que mi servidor web sea un ejecutable independiente voy a seleccionar la segunda opción: CGI Stand-alone executable. Al pulsar Ok nos creará un proyecto nuevo que incluye el siguiente módulo de datos web:

3. Nos preguntará si deseamos crear una interfaz para el módulo SOAP. Pulsamos No.

4. Cuando pulsemos el botón Save All para guardar todo el proyecto, vamos a poner como nombre el objeto TWebModule1 como UServidorSOAP.pas y el nombre del proyecto como ServidorSoap.dpr. El proyecto creado sólo se compone de dos partes; por un lado el archivo DPR del proyecto:


Y una unidad de la clase TWebModule que es la encargada de recoger las peticiones de los clientes:


5. Ahora vamos a añadir un módulo de datos remoto: File -> New -> Other, pestaña WebServices, seleccionamos SOAP Server Data Module y pulsamos Ok.

6. Nos preguntará el nombre del módulo: ModuloSOAP y pulsamos Ok. Lo guardamos también como UModuloSOAP.pas.

7. En este módulo de datos insertamos los componentes de acceso a nuestra base de datos Firebird, al igual que hicimos en artículos anteriores:


8. Compilamos el proyecto y nos generará el archivo ServidorSoap.exe. Si ejecutamos ese archivo nos devuelve una página web con la descripción de nuestros servicios web disponibles. Puede verse abriendo una ventana de comandos y ejecutando:

ServidorSoap.exe > servicios.html

9. Copiamos el ejecutable ServidorSoap.exe al directorio cgi-bin donde tengamos instalado el servidor web Apache. Para comprobar que nuestro servidor de aplicaciones esta operativo, abrimos un navegador web y escribimos la siguiente dirección:

http://127.0.0.1/cgi-bin/ServidorSoap.exe

Nos mostrará la misma página web que hemos guardado en servicios.html:


Con esto ya tenemos creado nuestro servidor de aplicaciones.

CREANDO LA APLICACION CLIENTE

La aplicación cliente va a ser similar a todas las que hemos creado hasta ahora:


Como puede verse en la imagen, he incluido un componente de la clase TSoapConnection de la pestaña de componentes WebServices. Al componente lo he llamado ClienteSoap y he modificado las siguientes propiedades:

URL: http://127.0.0.1/cgi-bin/ServidorSoap.exe/SOAP/
SOAPServeIID: IAppServerSOAP - {C99F4735-D6D2-495C-8CA2-E53E5A439E61}
Connected: True

Y en el componente TClientes (ClientDataSet) modificamos:

RemoteServer: ClienteSOAP
ProviderName: DSPCliente
Active: True

Haciendo esto debe verse la lista de clientes.

Este es el tipo de aplicación multicapa que más me gusta, ya que al trabajar a través del puerto 80 y mediante XML-SOAP no es necesario abrir puertos en los routers ni en los cortafuegos de los antivirus.

Como hemos visto anteriormente al crear el servidor de aplicaciones, también pueden crearse servidores que estén dentro de una librería DLL ya sea para el servidor apache o para el servidor Internet Information Server (IIS) de Microsoft.

Pruebas realizadas con Delphi 7.0, Firebird 2.0 y Apache Server 2.2.8.

07 marzo 2008

Creando aplicaciones multicapa (VI)

Hoy vamos a ver como crear las aplicaciones cliente y servidor utilizando el protocolo HTTP para establecer la comunicación. Para que funcione este método es necesario tener instalado el servidor Internet Information Server de Microsoft.

Si teneis Windows XP profesional se puede instalar de la siguiente manera:

1. Inicio -> Configuración -> Panel de control.

2. Hacemos doble clic sobre el icono Agregar o quitar programas.

3. Seleccionamos Agregar o quitar componentes de Windows.

4. Activamos Servicios de Internet Information Server (IIS).

5. Pulsamos Siguiente y nos pedirá el CD de Windows XP. Lo introducimos y pulsamos Siguiente.

6. Cuando termine la instalación pulsamos el botón Finalizar.

Con esto quedará instalado y ejecutado el servidor web de Microsoft.

CREANDO EL SERVIDOR DE APLICACIONES

La creación del servidor de aplicaciones es idéntica a la creada en el artículo anterior mediante sockets. Queda resumido en estos pasos:

1. Creamos un nuevo proyecto y lo guardamos.

2. Creamos un módulo de datos remoto: File -> New -> Other, pestaña Multitier, seleccionamos Remote Data Module y pulsamos Ok.

3. En la nueva ventana que aparece escribimos en el campo CoClass Name: ServidorHTTP.

4. Guardamos el módulo de datos remoto con el nombre UServidorWeb.pas.

5. Insertamos en el módulo de datos remoto los componentes encargados de conectar con nuestra base de datos. No voy a volver a explicarlo de nuevo ya que son los mismos componentes que he explicado en artículos anteriores:


Prácticamente no cambia nada respecto a hacerlo con sockets o con DCOM. En los tres casos lo que hace el módulo de datos es quedar registrado en Windows mediante un número de interfaz.

CREANDO LA APLICACION CLIENTE

Para crear la aplicación cliente rápidamente, podemos crear un proyecto nuevo y luego nos traemos del artículo anterior el formulario que utilizamos para crear la aplicación cliente, menos el componente de la clase TSocketConnection.

A dicho formulario le añadimos el componente WebConnection (pestaña DataSnap) y le ponemos de nombre ClienteWeb. Quedaría del siguiente modo:


Y aquí es donde empiezan las complicaciones. En primer lugar hay que copiar el archivo httpsrvr.dll que se encuentra en la carpeta bin de Delphi al directorio scripts de donde esté instalado Internet Information Server. Eso en teoria (según la documentación de Borland), porque yo no he encontrado dicho directorio (por lo menos en mi Windows XP Profesional SP2). Lo que vamos a hacer es copiar esa DLL donde esté nuestro servidor de aplicaciones HTTP.

Lo que he hecho en su lugar es ejecutar el programa que administra IIS del siguiente modo:

1. Inicio -> Configuración -> Panel de control.

2. Hacemos doble clic sobre Herramientas Administrativas.

3. Hacemos doble clic sobre Servicios de Internet Information Server.

4. Vamos pulsando el icono [+] del arbol de tenemos a la izquierda hasta acceder al icono Sitio Web Predeterminado. Se puede ver la carpeta scripts donde se pueden añadir directorios virtuales. He probado a añadir la carpeta donde está mi servidor HTTP pero ni caso.

5. Pulsamos con el botón derecho del ratón sobre el icono Sito Web Predeterminado y seleccionamos la opción Nuevo -> Directorio Virtual...


6. Pulsamos el botón Siguiente.

7. En Alias escribimos ServidorHTTP y pulsamos Siguiente.


8. Seleccionamos el directorio donde se encuentra nuestro proyecto del Servidor HTTP y el archivo httpsrvr.dll.

9. Activamos la opción Ejecutar (por ejemplo, aplicaciones ISAPI o CGI) y pulsamos Siguiente.


10. Pulsamos Finalizar.

11. Nos vamos a nuestro proyecto Cliente HTTP de Delphi 7 y seleccionamos el componente WebConnection.

12. En la propiedad ServerName seleccionamos ServidorHTTP.ServidorWeb. Se rellenará automáticamente el campo ServerGUID.

Y cuando parece que va a ir todo perfecto y activamos la opción Connected aparece este mensaje:


Y aquí es donde me he quedado. Por mucho que he dado permisos en IIS y por muchas vueltas que le he dado a los proyectos cliente y servidor no hay manera de evitar dicho mensaje. Y no he encontrado ninguna información útil por la red que solucione el problema (ni en castellano ni en inglés), bueno sí, en ruso, pero...

También he probado a copiar el archivo httpsrvr.dll dentro del directorio cgi-bin del servidor Apache y aunque llega a conectar con el mismo, me devuelve un error 500 que no lo conoce ni su padre.

Por lo tanto, aunque consiga solucionar este problema, no me gusta tener que depender de IIS para crear aplicaciones mediante HTTP. Prefiero utilizar sockets o DCOM.

Si alguien ha solucionado el problema que me lo cuente, porque yo no lo he logrado de ningún modo.

En la siguiente parte del artículo veremos como realizar lo mismo mediante servicios Web utilizando SOAP.

Pruebas realizadas en Delphi 7 y Firebird 2.0.

Publicidad