Main logo La Página de DriverOp

Tutorial Delphi - Estructura de un programa

Temas.

Estructura del código fuente.

En Pascal existen dos tipos de código fuente: el programa principal, que en nuestro caso es el que se almacena en el archivo .dpr y que a su vez toma el nombre del ejecutable que estamos creando; y luego tenemos los programas de biblioteca[1] que son anexos al programa principal, que Delphi los nombra con la extensión .pas. Estos últimos no son impresindibles, quiero decir, puede existir un .dpr que no tenga .pas asociados. En efecto, con Delphi es posible crear ejecutables Windows que no tengan ninguna ventana asociada, o sea que simplemente trabajen con la interface de línea de comandos (el famoso Simbolo de Sistema).

Para el caso de los .dpr la estructura interna de su código fuente es como sigue:

Todos comienzan con la palabra reservada program seguida del nombre del programa, aunque no es obligatoria conviene usarla para tener un control de qué programa se trata.

A continuación tenemos la palabra reservada uses luego de la cual se listan los nombres de los programas de biblioteca .pas, si no vamos a usar ninguna no es necesario usar esta cláusula aunque como veremos más adelante es extremadamente raro que presidamos de, al menos, una biblioteca.

Le sigue la palabra reservada const luego de la cual se declaran las constantes absolutas del programa, no es obligaroria

Luego tenemos la cláusula type, donde se declaran los tipos definidos por el usuario, tampoco es obligatoria.

A continuación la cláusula var luego de la cual se declaran las variables globales, es decir, las que son visibles a todo el programa.

Luego se declaran los procedimientos (procedure) y funciones (function) en el orden que se deseen.

Finalmente tenemos el bloque del programa principal comenzando por la palabra reservada begin y terminado con la palabra reservada end. (presta atención al punto). Ambos son obligatorios.

Entonces la estructura general es:

program nombre_del_programa;
uses Unit1, Unit2, Unit3... etc...

const

type

var

procedure ejemplo1;
begin

end;

function ejemplo2: tipo_de_retorno;
begin

end;

begin
{ desarrollo del programa }
end.

Tomemos por ejemplo nuestro .dpr de la aplicación Hola Mundo! visto en el capítulo anterior de este tutorial, el cual es este:

program Project1;
uses
  Forms,
  Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

Como verás aquí faltan algunas de las cláusulas enumeradas antes pero eso es perfectamente válido pues no todas son obligatorias, lo que aquí vemos es lo mínimo necesario para formar una aplicación Windows de una sola ventana.

Hay varias cosas para explicar. Primero salta a la vista la presencia de la declaración Forms en la cláusula uses, Forms es una biblioteca que ya está predefinida en Delphi por lo que no tiene un .pas correspondiente en la carpeta de nuestro proyecto (en realidad el código fuente de la unidad Forms reside en otro lugar que está compartido a todos los proyectos creados con Delphi), luego tenemos esto: Unit1 in 'Unit1.pas' {Form1}; Unit1 no es más que el .pas que hemos modificado pero en este caso Delphi aclara que es el que está en Unit1.pas en la carpeta actual, lo que sigue entre llaves es un comentario que agrega el IDE de Delphi para hacernos saber que en esa unidad reside la declaración de la ventana Form1.

Luego notarás este comentario entre llaves {$R *.res} este no es un comentario normal, es una directiva de compilación como se denota por la combinación de los caracteres {$ seguida de una letra, en este caso la R, esta directiva de compilación le indica al compilador que tome en cuenta un archivo .res, los archivos .res se usan para almacenar recursos visuales o de audio para nuestra aplicación que luego copiará dentro del .exe final. En nuestro ejemplo no usamos ninguna de estas cosas, no hemos creado ningún archivo de recursos explícitamente, el IDE de Delphi lo ha hecho por nosotros (aunque podemos modificarlo para agregarle cosas este tema no será tratado en este tutorial), específicamente allí ha almacenado el ícono por omisión de nuestra aplicación. Más adelante explicaré cómo cambiar el ícono por omisión. En la directiva de compilación está indicado *.res, ese asterico no significa lo que normalmente significa en Windows (es decir "todos los archivos .res") sino que quiere decir que el archivo de recursos tiene el mismo nombre que el .dpr; ¿por qué no poner el mismo nombre entonces?, porque si cambiamos el nombre al .dpr externamente o cuando salvamos el proyecto desde el IDE, deberíamos tomarnos la molestia de cambiar el mismo en la directiva de compilación.

Ahora concentrémonos en lo que está entre begin y end. del .dpr

El objeto Application es un objeto predefinido en todos los programas Delphi el cual hace referencia a la propia aplicación (nuestro programa Hola Mundo!), el método Initialize le indica al compilador que allí deben ejecutarse todos los procedimientos de inicialización requeridos por Windows para una aplicación. Luego sigue la llamada a la creación de la ventana, nuestra ventana. Y luego el método Run es la indicación de que debe ponerse en espera de eventos, lo que en Windows equivale a que la aplicación se está ejecutando. Estos tres pasos son los mínimos requeridos para una aplicación de al menos una ventana.

Si tenemos más de una ventana en nuestra aplicación, el IDE de Delphi agregará una llamada Application.CreateForm(); por cada una de ellas, pero es importante recordar que la primera llamada a CreateForm establece que esa ventana es la principal de la aplicación, esto significa que cuando se cierra (con Alt+F4 o haciendo click en el botón X de la esquina superior derecha) la ventana principal, se abandona el método Run y concluye la aplicación.

La estructura de una unidad Pascal.

Pasemos ahora al código fuente de las bibliotecas, desde ahora las llamaré unidades.

La estructura general es como sigue:

unit Unit1;

interface
uses { Lista de unidades }
type
   { Lista de tipos definidos por el usuario }

{ Luego encabezado de los procedimientos y funciones visibles desde afuera }

var
{ variables visibles desde afuera }
implementation
uses { lista de unidades usadas localmente }
var
{ variables visibles solo localmente }
   
{ Luego implementación de los procedimientos y funciones declaradas en interface más otras }
   
initialization
   { código de inicialización }
finalization
   { código de finalización }
end.

Igual que en el caso de los .dpr no todas las cláusulas son obligatorias, en especial initialization y finalization que sirve para colocar código que se ejecutará antes de que se cargue la unidad y luego cuando esta termine de usarse.

Las que sí son obligatorias son unit seguido del nombre de la unidad que DEBE SER IGUAL AL NOMBRE FÍSICO DEL ARCHIVO .pas, interface, implementation y end. El resto de las cláusulas no son obligatorias.

Como verás una unidad tiene sus propias cláusulas uses, esto significa que una unidad puede usar a su vez otras unidades, ya sean predefinidas por Delphi o hechas por nosotros.

Todo lo que está declarado a continuación de la cláusulas interface hasta implementation es lo que puede "verse" desde otras partes de la aplicación. Para entender esto veamos el código fuente de la unidad Unit1 de nuestra aplicación Hola Mundo1! del capítulo anterior la cual, con nuestras reformas incluidas, debería verse más o menos así:

unit Unit1;
interface
uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls;
type
   TForm1 = class(TForm)
   Button1: TButton;
   procedure Button1Click(Sender: TObject);
   private
   { Private declarations }
   public
   { Public declarations }
   end
var
   Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
   ShowMessage('Hola Mundo!');
end;
end.

Lo que haremos será mover las dos líneas "var Form1: TForm1;" y ponerlas justo debajo de la palabra reservada implementation, de modo que esa parte del código quede como sigue:

implementation
var
   Form1: TForm1;
{$R *.dfm}

Ahora compilaremos de nuevo el programa, pero solo lo compilaremos sin ejecutarlo, esto se consigue presionando las teclas Ctrl+F9. Inmediatamente obtendermos un error de compilación, la siguiente figura lo muestra:

Error de compilación

Fig. 1 - Error de compilación.

El IDE de Delphi nos trae en la ventana Editor de Código el código de nuestro .dpr resaltando la línea donde encontró el error y debajo aparece una nueva ventana con el mensaje de error, el cual es: [Error] Project1.dpr(11): Undeclared identifier: 'Form1', el cual quiere decir que la variable Form1 no está declarada o no está visible para el .dpr al compilar la línea 11 ¿por qué?, porque en la unidad Unit1 nosotros dejamos de hacer visible desde fuera de la unidad la variable Form1. Para reparar esto solo es necesario volver a hacer visible esa variable trayéndola de nuevo a la sección interface de la unidad Unit1. Restaura las lineas que movimos a su lugar y vuelve a compilar el proyecto con Ctrl+F9.

Esto me lleva a decir que las unidades Pascal sirven para no redundar código, es decir, no declarar más de una vez una misma variable o procedimiento, el uso de las unidades sirve para concentrar el código en común a toda la aplicación en un solo código fuente, bastando solamente compartir lo que nos interesa que sea visible desde otros lugares de la aplicación. Pero también para hacer uso de ella es necesario agregar el nombre de la unidad donde reside tal declaración a la cláusula uses de la unidad donde se necesite.

Esto último es evidente tanto en el .dpr de nuestro proyecto como en la propia unidad Unit1. Si miras ambas cláusulas uses verás que están listadas un montón de unidades, en especial la cláusula uses de la unidad Unit1 contiene 10 llamadas a unidades externas, ninguna de las cuales las hemos hecho nosotros mismos sino que están predefinidas por Delphi. Esas unidades se llaman unidades estandar, en ellas están, entre otras cosas, declarado el procedimiento ShowMessage que usamos en el evento OnClick del Button, concretamente en la unidad Dialogs. Podemos hacer este experimento. Encierra con llaves a modo de comentario la llamada a Dialogs en la lista de uses de la unidad Unit1 de modo que toda la declaración quede asi:

uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   { Dialogs, } StdCtrls;

Compila con Ctrl+F9 y verás que obtienes un error en la llamada a ShowMessage, esto se debe a casi lo opuesto del error que vimos antes, aquí lo que el compilador no encuentra es dónde está declarado el procedimiento ShowMessage entonces asume, como en el caso anterior que se trata de un identificador desconocido.

Exportando variables, tipos, procedimientos y funciones.

Los usos de las unidades de biblioteca (units) de Pascal sirven para hacer visibles variables globales, tipos definidos por el usuario, procedimientos y funciones. Me voy a concentrar en estos dos últimos ya que me parece que el tema de las variables globales quedará más claro de esta forma mientras que de los tipos nos ocuparemos más adelante en este tutorial.

Los procedimientos son pequeños algoritmos dentro del gran algoritmo que es una unidad Pascal. Es la implementación de la filosofía de programación top-down, también llamada programación descendente en donde el problema mayor se divide en problemas menores que se resuelven por separado. Cada procedimiento o función es un programa en sí mismo, más o menos independiente del resto y que a su vez puede llamar a otros procedimientos y funciones dentro de la misma unidad o en otra unidad como ya hemos visto.

La estructura general de un procedimiento Pascal es como sigue:

procedure Nombre_del_procedimiento(parametro: tipo);
const { lista de constantes }

var { lista de variables locales al procedimiento }

begin
{ desarrollo del procedimiento }
end;

Como verás se parece bastante al código que está en el .dpr. Solo son obligatorias las palabras reservadas procedure, begin y end, var y const se usan solo si se necesitan.

En el caso de usar var la declaración de variables solo tienen ámbito en ese procedimiento, es decir, esas variables son de caracter local al procedimiento. Pueden ser de un tipo declarado en la type de la unidad. Los procedimientos pueden hacer uso de otros procedimientos dentro de la misma unidad o los que estén exportados en las unidades de la cláusula uses de esa unidad. La única forma de comunicar resultados fuera del procedimieto es atravez de sus parámetros o bién mediante el uso de variables globales pero esto no es recomendable como veremos más adelante.

La declaración general de una función es como sigue:

function Nombre_de_la_funcion(parametro: tipo): tipo;
const { lista de constantes }

var { lista de variables locales a la función }

begin
{ desarrollo de la función }
end;

Es muy parecida la procedure pero difiere en su comportamiento, las functions SIEMPRE devuelven un valor que será del tipo indicado al final de su encabezado. En Object Pascal (y por ende en Delphi) todas las functions tienen una variable implícitamente definida llamada Result que sirve para enviar el valor de retorno al lugar desde donde es llamada la función. En algun lugar dentro de begin ... end esta variable se le debe asignar un valor y puede ser usado como si fuera una variable local más.

Veremos con más detalles algunos aspectos particulares de los procedimientos y funciones más adelante en este tutorial.

Llamada circular y conflicto de identificadores.

Hay algunos problemas que debemos evitar, uno de ellos es las llamadas circulares entre unidades. Supongamos que hemos escrito una unidad con nombre A y otra unidad llamada B, y en la cláusula uses de la unidad A hacemos referencia a la unidad B pues nos interesa usar sus variables, tipos o procedimientos visibles en su cláusula interface, pero también en la unidad B nos interesa usar un procedimiento que es visible (está en la cláusula interface) en la unidad A. O sea, A llama a B y B llama a A, esto es una llamada circular lo cual es un error y el compilador se quejará ¿por qué?, porque el compilador cuando hace su trabajo comienza a compilar primero las unidades que están listadas en la cláusula uses del .dpr en el mismo orden en el que están listadas, allí encontrará primero la unidad A que procede a compilar encontrándose en su cláusula uses una llamada a la unidad B la cual procede a compilar pero allí encuentra una llamada a A que todavía no ha terminado de compilar pues apenas va por su cláusula uses por lo que no puede determinar cuál es el contenido completo de esa unidad y así ponerla disponible para la unidad B. ¿Cómo se soluciona esto?. Recurriendo a la cláusula uses que está en la implementation en una de las dos unidades, en nuestro hipotético ejemplo nos conviene quitar la llamada a A en la unidad B de su cláusula uses en la interfase y declararla en la uses de su implementation, de forma tal que el compilador no se vea forzado a compilar la unidad A antes que a la B.

El segundo problema que podemos encontrar es el conflicto de identificadores. El escenario es el siguente, supongamos que tenemos tres unidades A, B, C. A necesita de B y de C pero tanto B como C comparten al resto de la aplicación un procedimiento que se llama igual en ambas unidades, por ejemplo EsMayor(); cuando en A hagamos uso de ese procedimiento al estar declarado con el mismo identificador el compilador no sabrá a cuál de los dos estamos haciendo referencia lo que resultará en un error. Para solucionar esto en la llamada en A debemos calificar la llamada con el siguiente formato:

B.EsMayor(par1, par2); // aquí estoy usando el procedimiento en B
C.EsMayor(par1, par2); // y aquí el que está en C

De esta forma no hay ambigüedades.


Notas:

1.- Me niego a usar el más extendido término "librería" porque es una traducción errónea del inglés "library". "Library" se traduce como "biblioteca" mientras que "librería" en castellano se traduce al inglés como "bookstore". Una biblioteca ("library") es el lugar donde se almacenan libros para consulta, mientras que "librería" ("bookstore") es el negocio donde se venden libros. En programación esto tiene sentido puesto que una biblioteca es el lugar donde se almacena código a ser usando y no el lugar donde se compra código fuente.

Diego Romero -