Logo

Marco Cantù
Essential Pascal


Tradotto dall'inglese da: Paolo Rossi

Capitolo 9
La programmazione Windows

Delphi fornisce una completa incapsulazione delle funzioni API di Windows di basso livello usando l'Object Pascal e la Visual Component Library (VCL), quindi e' raramente necessario costruire un'applicazione Windows usando semplice codice Pascal e chiamando direttamente le funzione WinAPI. Tuttavia, i programmatori che devono usare tecniche speciali non supportate dalla VCL, possono ancora farlo in Delphi. Bisogna usare questo approccio solo per casi davvero speciali, come lo sviluppo di nuovi componenti Delphi basati su chiamate API insolite. In questo libro, comunque, non se ne discuteranno i dettagli. Al contrario si guarderanno pochi elementi di interazione tra Delphi e il sistema operativo e alcune tecniche di cui possono beneficiare i programmatori Delphi.

Gli Handle di Windows

In mezzo ai tipi di dato introdotti da Windows, gli handle rappresentano il gruppo piu' importante. Il nome di questo tipo di dato e' THandle ed e' definito nella unit Windows come:

type
  THandle = LongWord;

Il tipo Handle e' implementato come numero, ma non e' usato come tale. In Windows, un handle e' un riferimento ad una struttura dati interna al sistema. Ad esempio, quando si lavora con una finestra (o un form di Delphi), il sistema fornisce un handle alla finestra. Il sistema informa che la finestra sulla quale si sta lavorando e' la numero 142, ad esempio. Da questo punto, l'applicazione puo' chiedere al sistema di operare sulla finestra numero 142, muovendola, ridimensionandola, iconizzandola e cosi' via. Diverse funzioni API di Windows, di fatto, vogliono un handle come primo parametro. Questo non riguarda solo le funzioni operanti sulle finestre, altre funzioni API vogliono come primo parametro un handle GDI, un handle ad un menu, un handle di istanza, un handle ad un bitmap, o uno degli altri tipi di handle.

In altre parole, un handle e' un codice interno che si puo' usare per puntare ad uno specifico elemento gestito dal sistema: una finestra, un bitmap, un'icona, un blocco di memoria, un cursore, un font, un menu e cosi' via. In Delphi, raramente si ha la necessita' di usare un handle direttamente, siccome sono nascosti all'interno dei form, dei bitmap e degli altri oggetti Delphi. Essi diventano utili quando si cerca di chiamare una funzione API di Windows non supportata da Delphi.

Per completare questa descrizione, ecco un semplice esempio sull'uso degli handle in Windows. Il programma WHandle ha un semplice form, contenente solo un pulsante. Nel codice, si risponde all'evento OnCreate del form e all'evento OnClick del pulsante, come indicato dalla seguente dichiarazione testuale del form principale:

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

Appena il form e' creato, il programma recupera l'handle della finestra corrispondente al form, accedendo allaproprieta' Handle del form stesso. Si invoca la funzione IntToStr per convertire il valore numerico dell'handle in una stringa aggiungendola alla caption del form, come si puo' vedere nella Figura 9.1:

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

Siccome FormCreate e' un metodo della classe form, puo' accedere direttamente alle proprieta' e metodi della stessa classe. Percio' in questa procedura ci si puo' semplicemente riferire direttamente alle proprieta' Caption e Handle.

Figura 9.1: L'esempio WHandle mostra l'handle del form. Ogni volta che si esegue il programma si otterra' un valore differente.

Se si esegue questo programma diverse volte generalmente si otterranno differenti valori per l'handle. Questo valore, di fatto, e' determinato da Windows e rispedito all'applicazione. (Gli handle non sono mai determinati dal programma ed essi non hanno valori predefiniti, sono determinati dal sistema, il quale genera nuovi valori ogni volta che viene eseguito un programma).

Quando l'utente preme il pulsante, il programma chiama semplicemente una funzione API di Windows, SetWindowText, che cambia il testo o il titolo della finestra passata come primo parametro. Per essere piu' precisi, il primo parametro di questa funzione API e' l'handle della finestra che si vuole modificare:

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

Questo codice ha lo stesso effetto del precedente gestore d'evento, il quale cambiava il testo della finestra assegnando un nuovo valore alla proprieta' Caption del form. In questo caso, chiamare una funzione API non ha molto senso, siccome esiste un modo molto piu' semplice in Delphi. Alcune funzioni API, comunque, non hanno la corrispondente in Delphi, come si potra' vedere negli esempi piu' avanzati piu' avanti in questo libro.

Dichiarazioni External

Un altro importante elemento per la programmazione Windows e' rappresentato dalle dichiarazioni external. Originariamente usate per collegare il codice Pascal a funzioni esterne scritte in linguaggio assembly, le dichiarazioni external sono usate in Windows per chiamare funzioni da una DLL (dynamic link library). In Delphi, ci sono parecchie dichiarazioni di questo tipo nella unit Windows:

// forward declaration
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;

// external declaration (instead of actual code)
function LineTo; external 'gdi32.dll' name 'LineTo';

Questa dichiarazione dice che il codice della funzione LineTo e' memorizzato nella libreria dinamica GDI32.DLL (una delle piu' importanti librerie di sistema di Windows) con lo stesso nome usato nel codice. All'interno di una dichiarazione external, comunque, si puo' specificare che la nosta funzione punta ad una funzione di una DLL che originariamente ha un altro nome.

Raramente si ha bisogno di scrivere dichiarazioni come quella sopra, visto che sono gia' elencate nella unit Windows e altre unit di sistema di Delphi. La sola ragione che si puo' avere per scrivere questo tipo di dichiarazione e' la chiamata di funzioni da una DLL non di sistema, o per chiamare funzioni Windows non documentate.

Nota: Nella versione a 16 bit di Delphi, la dichiarazione external usa il nome della DLL senza estensione, e e' seguita dalla direttiva name (come nel codice in alto) o in alternativa dalla direttiva index, seguita dal numero ordinale della funzione all'interno della DLL. Il cambiamento riflette un cambiamento di sistema nel modo in cui sono trattate: Benche' la piattaforma Win32 permette l'accesso alle funzioni DLL a partire dal numero ordinale, Microsoft afferma che non sara' piu' supportato in futuro. Da notare anche che la unit Windows sostituisce le unit WinProcs e WinTypes della versione a 16 bit di Delphi.

Funzioni Callback di Windows

Si e' visto nel Capitolo 6 che l'Object Pascal supporta i tipi procedurali. Un uso comune dei tipi procedurali e' di provvedere chiamate callback alle funzioni API di Windows.

Prima di tutto, cosa e' una funzione callback? L'idea e' che alcune funzioni API compiono una data azione sopra diversi elementi interni di sistema, come l'insieme delle finestre di un certo tipo. Cosi' una funzione, chiamata anche una funzione enumerativa, richiede come parametro l'azione da eseguire su ognuno degli elementi, la quale e' passata come una funzione o procedura compatibile con una certo tipo procedurale. Windows usa le funzioni callback in altre circostanze, ma lo studio sara' limitato ai casi piu' semplici.

Adesso si consideri la funzione API EnumWindows, che ha il seguente prototipo (copiato dal file di Help Win32):

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // address of callback function
  LPARAM lParam // application-defined value
  );

Ovviamente, questa e' una definizione in linguaggio C. Si puo' guardare nella unit Windows per recuperare la corrispondente definzione in Pascal:

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

Consultando il file di help, si trova che la funzione passata come parametro deve essere del seguente tipo (ancora in C):

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

Questo corrisponde alla seguente definizione di tipo procedurale in Delphi:

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

Il primo parametro e' l'handle di ogni finestra principale successiva, mentre il secondo e' il valore passato nella chiamata alla funzione EnumWindows. Effettivamente in Pascal il tipo TFNWndEnumProc non e' correttamente definita, esso e' semplicemente un puntatore. Questo vuol dire che si deve fornire una funzione con gli opportuni parametri quindi usarla come puntatore, prendendo l'indirizzo della funzione invece di invocarla. Sfortunatamente, questo vuol dire che il compilatore non fornira' nessun aiuto in caso di errore nel tipo di un parametro.

Windows ai programmatori di seguire la convenzione di chiamata stdcall ogni volta che si chiama una funzione API di Windows o si passa una funzione callback al sistema. Delphi, di default, usa una convenzione diversa e piu' efficiente, indicata dalla keyword register.

Ecco la definizione di una opportuna funzione compatibile, la quale legge il titolo della finestra lo mette in una stringa e lo aggiunge ad un ListBox di un dato form:

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;

Il form ha un ListBox che copre quasi tutta l'area, insieme ad un pannello in alto che contiene un pulsante. Quando il pulsante e' premuto, la funzione API EnumWindows e' invocata e la funzione GetTitle e' passata come suo parametro:

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

Ho chiamato la funzione senza memorizzare il valore in una variabile temporanea di tipo procedurale, ma voglio rendere chiaro quello che succede nell'esempio. L'effetto di questo programma e' davvero interessante, come si puo' vedere in Figura 9.2. L'esempio Callback mostra un elenco di tutte le finestre esistenti nel sistema.

Figura 9.2: L'output dell'esempio Callback, con l'elenco delle finestre correnti (visibili e nascoste).

Un Programma Windows minimale

Per completare la discussione sulla programmazione Windows e sul linguaggio Pascal, voglio mostrare una semplicissima ma completa applicazione costruita senza usare la VCL. Il programma semplicemente prende un parametro sulla linea di comando (immagazzinato dal sistema nella variabile globale cmdLine) e quindi estrae informazioni con le funzioni Pascal ParamCount e ParamStr. La prima di queste funzioni, ritorna il numero di parametri, la seconda ritorna il parametro in una data posizione.

Sebbene gli utenti raramente specificano i parametri sulla linea di comando in un'interfaccia grafica, il parametri su linea di comando di Windows sono importanti per il sistema. Ad esempio, una volta definita un'associazione tra estensione del file e applicazione, si puo' semplicemente eseguire il programma selezionando un file associato. In pratica, quando si esegue un doppio click su un file, Windows esegue il programma associato e passa il file selezionato come parametro sulla linea di comando.

Ecco il codice sorgente completo del progetto (un file DPR, non un file 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.

Il codice di output usa la funzione API MessageBox, semplicemente per evitare di includere l'intera VCL nel progetto. Un programma Windows "puro" come quello sopra, di fatto, ha il vantaggio di occupare poca memoria: il file eseguibile e' circa 16 Kbytes.

Per fornire un parametro in linea di comando a questo programma, si puo' usare il menu di Delphi Run -> Parameters. Un'altra tecnica e' di aprire l'Explorer di Windows, localizzare la directory che contiene il file eseguibile e trascinare il file che si scelto sopra il file eseguibile. L'Explorer fara' partire il programma usando il nome del file trascinato come parametro in linea di comando. La Figura 2.12 mostra sia Explorer sia l'output corrispondente.

Figura 9.3: Si puo' fornire un parametro in linea di comando all'esempio StrParam trascinando un file sopra al file nell'Explorer di Windows.

Conclusioni

In questo capitolo si e' vista un'introduzione alla programmazione Windows a basso livello, si sono visti gli handle e un programma semplice Windows. Per i normali programmi Windows, generalmente si usa l'ambiente di programmazione visuale fornito da Delphi e basato sulla VCL. Questo pero' non e' negli scopi di questo libro che e' un testo sul linguaggio Pascal.

Il prossimo capitolo parla dei variant, un tipo abbastanza "strano" aggiunto di recente al Pascal, introdotto per un pieno supporto alla tecnologia OLE.

Prossimo Capitolo: Il tipo variant

© Copyright Marco Cantù, Wintech Italia Srl 1995-2000