ç

Logo

Marco Cantù
Pascal Esencial

Capítulo 9 Actualizar
Programación bajo Windows

Delphi proporciona una encapsulación completa de la API de Windows de bajo nivel, utilizando Object Pascal y la biblioteca de componentes visual (VCL), de forma que rara vez es necesario construir aplicaciones Windows utilizando Pascal tradicional y haciendo llamadas directas a funciones API de Windows. Con todo, los programadores que quieran utilizar algunas técnicas especiales no incluidas en la VCL aun tienen esa posibilidad el Delphi. Sólo es necesario este método en ciertos casos muy especiales, como el desarrollo de nuevas componentes Delphi basadas en llamadas a la API poco habituales, pero no mencionaré los detalles aquí. En su lugar, examinaremos algunos elementos de la interacción de Delphi con el sistema operativo y un par de técnicas de que se pueden aprovechar los programadores de Delphi.

Handles en Windows

Entre los tipos de datos introducidos por Windows en Delphi, los handles representan el grupo más importante. El nombre de este tipo de datos es THandle, y es definido en la unidad Windows como:

type
  THandle = LongWord;

Los tipos de datos handle se implementan como números, pero no se usan como tales. En Windows, un handle es una referencia a una estructura de datos interna del sistema. Por ejemplo, cuando trabaja con una ventana (o un formulario Delphi), el sistema le da un handle a la ventana. El sistema le informa de que la ventana con que está trabajando lleva el número 142, por ejemplo. Desde ese momento, su aplicación puede pedirle al sistema que opere sobre la ventana número 142 — moviéndolo, cambiando su tamaño, reduciéndolo a un icono, etc. Muchas funciones API de Windows tienen, de hecho, un handle como primer parámetro. Esto no es sólo cierto para funciones que operan sobre ventanas; otras funciones API de Windows tienen como su primer parámetro un handle GDI, un handle de menú, un handle de instancias, un handle de mapa de bits u otro de los muchos tipos de handle.

En otras palabras, un handle es un código interno que se usa para referirse a un elemento específico manejado por el sistema, incluyendo una ventana, un mapa de bits, un icono, un bloque de memoria, un cursor, una fuente de caracteres, un menú, etc. En Delphi, rara vez necesitará acceder a los handles directamente, ya que se encuentran ocultos dentro de formularios, mapas de bits y otros objetos en Delphi. Resultan útiles cuando quiere hacer llamadas a funciones API de Windows no implementadas en Delphi.

Para completar esta descripción, he aquí un ejemplo sencillo que muestra el funcionamiento de los handles bajo Windows. El programa WHandle tiene un formulario sencillo, que contiene sólo un botón. En el código, respondo al evento OnCreate del formulario y al evento OnClick del botón, como se muestra en la siguiente definición textual del formulario principal:

object FormWHandle: TFormWHandle
  Caption = 'Window Handle'
  OnCreate = FormCreate
  object BtnCallAPI: TButton
    Caption = 'Call API'
    OnClick = BtnCallAPIClick
  end
end

Tan pronto como el formulario se crea, el programa busca el handle de la ventana que corresponde al formulario, accediendo a la propiedad Handle del formulario mismo. Hacemos una llamada a IntToStr para convertir el valor numérico del handle en una cadena de caracteres, y añadimos aquel al título del formulario, como se ve en la Figura 9.1:

procedure TFormWHandle.FormCreate(Sender: TObject);
begin
  Caption := Caption + ' ' + IntToStr (Handle);
end;

Ya que FormCreate es un método de la clase del formulario, puede acceder a otras propiedades y métodos de la misma clase, directamente. Por tanto, en este procedimiento podemos apuntar, sencillamente, al Caption del formulario y a su propiedad Handle, de forma directa.

Figura 9.1: El ejemplo WHandle muestra el handle de la ventana del formulario. Cada vez que ejecute este programa, obtendrá un valor distinto.

Si ejecuta este programa varias veces, en general obtendrá distintos valores para el handle. Este valor, de hecho, viene determinado por Windows, y es remitido a la aplicación. (Los handles nunca son establecidos por el programa, y no tienen valores predefinidos; son determinados por el sistema, que genera nuevos valores cada vez que usted ejecuta un programa.)

Cuando el usuario pulsa el botón, el programa simplemente hace una llamada a una función API, SetWindowText, que cambia el texto o título de la ventana transmitido como primer parámetro. Para ser más precisos, el primer parámetro de esta función API es el handle de la ventana que queremos modificar:

procedure TFormWHandle.BtnCallAPIClick(Sender: TObject);
begin
  SetWindowText (Handle, 'Hi');
end;

Este código tiene el mismo efecto que el gestor de eventos anterior, que cambió el texto de la ventana dando un nuevo valor a la propiedad Caption (título) del formulario. En este caso, hacer una llamada a una función API no tiene sentido, ya que hay una técnica en Delphi más sencilla. Algunas funciones API, sin embargo, no tienen correspondencia en Delphi, como veremos en ejemplos más avanzados en el libro.

Declaraciones externas

Otros elementos importante para la programación bajo Windows viene representada por declaraciones externas. Utilizada en un principio para enlazar el código Pascal a funciones externas escritas en lenguaje ensamblador, la declaración externa se usa en la programación Windows para hacer una llamada a una función desde una DLL (una biblioteca de enlace dinámico). En Delphi, hay varias declaraciones tales en la unidad Windows:

// declaración típica
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;

// declaration externa (en lugar de código)
function LineTo; external 'gdi32.dll' name 'LineTo';


Esta declaración significa que el código de la función LineTo está almacenado en la biblioteca dinámica GDI32.DLL (una de las más importantes en Windows), con el mismo nombre que estamos usando en nuestro código. Dentro de una declaración externa, de hecho, podemos especificar que nuestra función se refiera a la función de una DLL que originalmente tenía otro nombre.

Rara vez necesitará escribir declaraciones como la que acabamos de ilustrar, ya que ya están enumeradas en la unidad Windows y muchas otras unidades de sistema en Delphi. La única razón por la que pudiera necesitar escribir esta declaración externa es que necesitase hacer llamadas a funciones desde una DLL que se haya hecho a medida, o para hacer llamadas a funciones de Windows que no aparecen en la documentación.

Nota: en la versión de Delphi de 16 bits, la declaración externa utilizaba el nombre de la biblioteca sin la extensión, e iba seguida por la directiva de nombre (como en el código que aparece arriba) o por una directiva de índice alternativa, seguida por el número ordinal de la función dentro de la DLL. El cambio refleja un cambio en el sistema en relación a cómo se accede a las bibliotecas: a pesar de que Win32 aún permite acceder a las funciones de las DLL por su número, Microsoft ha declarado que esto no será posible en el futuro. Tenga en cuenta, también, que la unidad Windows reemplaza las unidades WinProcs y WinTypes de la versión de 16 bits de Delphi.


Funciones Callback de Windows

Hemos visto en el Capítulo 6 que Object Pascal incluye tipos de procedimiento. Una práctica habitual de tipos de procedimiento es facilitar funciones callback a una función API de Windows.

Pero, ¿qué es una función callback? Se trata de una cierta función API que realiza una acción dada sobre una cierta cantidad de elementos internos del sistema, como todas las ventanas de cierto tipo. Tal función, también llamada función enumerada, requiere como parámetro la acción que ha de ejecutarse en cada elemento, que se transmite como una función o procedimiento compatible con un tipo de procedimiento dado. Windows usa las funciones callback en otras circunstancias, pero limitaremos nuestro estudio a este caso sencillo.

Ahora, considere la función API EnumWindows, que tiene el prototipo siguiente (copiado del archivo de ayuda de Win32):

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // dirección de la función callback
  LPARAM lParam // valor definido por la aplicación
  );

Por supuesto, esta es la definición en lenguaje C. Podemos echar un vistazo al interior de la unidad Windows para obtener la definición correspondiente en lenguaje Pascal:

function EnumWindows (
  lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall;

Consultando el archivo de ayuda, encontramos que la función pasada como parámetro debería ser del siguiente tipo (de nuevo en C):

BOOL CALLBACK EnumWindowsProc (
  HWND hwnd, // handle of parent window
  LPARAM lParam // application-defined value
  );

Esto se corresponde con la siguiente definición de tipo de procedimiento en Delphi:

type
  EnumWindowsProc = function (Hwnd: THandle;
    Param: Pointer): Boolean; stdcall;

El primer parámetro es el handle de la ventana principal a que le toca el turno, mientras que el segundo es el valor que hemos transmitido al efectuar la llamada a la función EnumWindows. De hecho, en Pascal, el tipo TFNWndEnumProc no está bien definido. Esto significa que necesitamos proporcionar una función con los parámetros adecuados y entonces utilizarla como puntero, tomando la dirección de la función, en vez de llamarla. Desgraciadamente, esto también significa que el compilador no proporcionará ayuda alguna en caso de que se produzca un error en el tipo de uno de los parámetros.

Windows exige a los programadores que sigan la convención de llamada stdcall cada vez que llamen a una función API de Windows o transmitan una función callback al sistema. Delphi, por defecto, usa una convención distinta, más eficiente, indicada por la palabra clave register.

He aquí la definición de una función compatible adecuada, que pasa el título de la ventana a una cadena, y lo añade a un ListBox de un formulario dado:

function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall;
var
  Text: string;
begin
  SetLength (Text, 100);
  GetWindowText (Hwnd, PChar (Text), 100);
  FormCallBack.ListBox1.Items.Add (
    IntToStr (Hwnd) + ': ' + Text);
  Result := True;
end;

El formulario tiene un ListBox que cubre casi toda su área, junto con un pequeño panel en la parte superior, que incluye un botón. Cuando se pulsa dicho botón, se efectúa una llamada a la función API EnumWindows, y la función GetTitle se usa como parámetro :

procedure TFormCallback.BtnTitlesClick(Sender: TObject);
var
  EWProc: EnumWindowsProc;
begin
  ListBox1.Items.Clear;
  EWProc := GetTitle;
  EnumWindows (@EWProc, 0);
end;

Podría igualmente haber efectuado la llamada a la función sin almacenar el valor en una variable de tipo de procedimiento previdamente, pero quería dejar claro qué ocurre en este ejemplo. El efecto de este programa es, de hecho, bastante interesante, como se ver en la Figura 9.2. El ejemplo Callback muestra una lista de las ventanas principales que se encuentran presentes actualmente en el sistema. La mayoría de ellas son ventanas ocultas que normalmente no ve (y muchas de ellas no tienen ni siquiera título).

Figura 9.2: La salida del ejemplo Callback, enumerando las ventanas principales actuales (visibles u ocultas).

Un breve programa Windows

Para completar la introducción a la programación bajo Windows en lenguaje Pascal, quisiera mostrarle una aplicación muy simple, pero completa, construida sin usar el VCL. El programa simplemente toma el parámetro de línea de comandos (almacenado por el sistema en la variable global cmdLine) y luego extrae información de él con las funciones de Pascal ParamCount y ParamStr Pascal. La primera de estas funciones devuelve el número de parámetros; la segunda devuelve el parámetro en una posición dada.

Aunque los usuarios rara vez usan parámetros de línea de comandos en un entorno de interfaz de usuario gráfica (GUI), los parámetros de línea de comandos de Windows son importantes para el sistema. Por ejemplo, una vez que se ha definido una asociación entre una extensión de archivo y una aplicación, se puede ejecutar un programa con sólo seleccionar un archivo asociado. En la práctica, cuando usted pincha dos veces (doble click) en un archivo, Windows inicia el programa asociado y transmite el archivo asociado como parámetro de línea de comandos.

Sigue el código fuente completo del proyecto (un archivo DPR, no PAS):

program Strparam;

uses
  Windows;

begin
  // show the full string
  MessageBox (0, cmdLine, 
    'StrParam Command Line', MB_OK);

  // show the first parameter
  if ParamCount > 0 then
    MessageBox (0, PChar (ParamStr (1)), 
      '1st StrParam Parameter', MB_OK)
  else
    MessageBox (0, PChar ('No parameters'), 
      '1st StrParam Parameter', MB_OK);
end.

El código de salida usa la función API MessageBox, simplemente para evitar introducir todo el VCL en el proyecto. Un programa Windows puro como el de arriba, tiene, de hecho, la ventaja de dejar una huella pequeña: el archivo ejecutable del programa comporta sólo 16 kB.

Para proporcionar un parámetro de línea de comandos a este programa, puede usar el comando de menú de Delphi Run > Parameters. Otra técnica es abrir el Explorador de Windows, localizar el directorio que contiene el archivo ejecutable del programa, y arrastrar el archivo que se quiere ejecutar al archivo ejecutable. El Explorador de Windows iniciará el programa usando el nombre del archivo arrastrado como parámetro de línea de comandos. La Figura 9.3 muestra tanto el Explorador como la salida correspondiente.

Figura 9.3: Puede proporcionar un parámetro de línea de comandos al ejemplo StrParam arrastrando un archivo sobre el archivo ejecutable, en el Explorador de Windows.

Conclusión

En este capítulo hemos visto una introducción a bajo nivel a la programación Windows, discutiendo los handles y un programa Windows muy simple. Para tareas de programación habituales en Windows, en general usará las posibilidades de desarrollo en un entorno visual que proporciona Delphi, basadas en el VCL. Pero esto sobrepasa el ámbito de este libro, que se circunscribe al lenguaje Pascal.

El siguiente capítulo cubre los variantes, un añadido muy extraño al sistema de tipos de Pascal, introducido para proporcionar total compatibilidad OLE (Object Linking and Embedding).

Capítulo siguiente: Variantes

© Copyright Marco Cantù, Wintech Italia Srl 1995-99
© Copyright de la traducción, Rafael Barranco-Droege, 2000