La gestione dei dati permanenti
(approccio in Delphi 4 o succ.)
Introduzione
I dati "permanenti" di qualsiasi applicazione sono sempre salvati in contenitori logici chiamati FILE; per
esempio i dati relativi ai libri di una biblioteca sono salvati su file e di conseguenza su disco, così come i dati dei
dipendenti di una certa azienda. I FILE sono poi memorizzati su alcuni supporti fisici quali i dischi (floppy, hard,
CD rom, DVD, Riscrivibili) ed i nastri ( ).
Il Sistema Operativo, ed in particolare un suo modulo: il File System, svolge il compito di fornire all'utente una
visione logica uniforme delle informazioni memorizzate sui vari tipi di supporti a disposizione tramite una serie di
routine di medio livello.
Vediamo ora come si colloca il concetto di FILE all'interno di una scala gerarchica dei dati:
DATABASE
(biblioteca)
Insieme di più file, anche diversi tra di loro, e dei legami tra di loro
FILE o ARCHIVIO
(elenco libri)
Sequenza lineare di informazioni omogenee oppure come un insieme di registrazioni
RECORD o REGISTRAZIONE
(libro)
Sequenza, non sempre di uguale lunghezza, di campi
CAMPO o FIELD
(titolo)
Sequenza di byte con una precisa interpretazione
CARATTERE o BYTE
(U)
BIT
(0)
Sintetizzando possiamo ritenere di classificare l'accesso ai dati su memoria di massa tramite due diversi
approcci:
a) l'approccio FILE SYSTEM:
 Accesso ai file tramite le primitive di sistema messe a disposizione dal Sistema Operativo; nel sistema
Unix sono le System Call relative al File System, nel sistema Windows sono le API (application
programming interface). Per ulteriori approfondimenti si può fare riferimento all'Help di Delphi 4 o
succ. al capitolo MS SDK Help Files -> Win32 Developer's References, alla voce File Function
(win32 HLP).

Accesso ai file le routine messe a disposizione da un linguaggio di programmazione, come Object
Pascal (OpenFile, CloseFile, Read, Write, Seek, ecc.); questo approccio verrà ripreso di seguito;
b) l'approccio DATA BASE:
 Accesso ai dati in modo astratto facendo riferimento ad un modello logico standard dei dati: per
esempio il modello Relazionale (Tabelle, Query, linguaggio SQL, ecc.); in tal modo l'accesso ai dati è
gestito tramite un modulo detto DBMS che
582846450
1/19
Quanto espresso in precedenza è riassunto, in ambiente Delphi 4 o successivi, dal seguente schema:
Applicazione Deplhi
approccio
DATA BASE
Approccio
FILE SYSTEM
(API)
(API)
Borland Database Engine (BDE)
File
SQL Links
dBase
Paradox
ODBC
File
Oracle
Informix
Approccio
FILE SYSTEM
(Object Pascal)
Driver ODBC
per Accesss
Access
582846450
Altri
driver
ODBC
SQLServer
SyBase
2/19
L'approccio FILE SYSTEM tramite Object Pascal (Delphi 4 o successivi):
Una variabile file è ogni variabile dichiarata di tipo file. Ci sono tre classi di file: tipizzati (typed), testo
(text) e non tipizzati (untyped)
I file tipizzati (typed file)
Un file è un insieme ordinato di elementi dello stesso tipo; la sintassi per la dichiarazione del tipo file è:
type
fileTypeName = file of type
dove fileTypeName è ogni identificatore valido e type è un tipo a dimensione fissa. I tipi puntatori (sia
impliciti che espliciti) non sono permessi, inoltre un file non può contenere dynamic arrays, long strings, classes,
objects, pointers, variants, altri file, o tipi strutturati contenenti uno di essi.
Per esempio:
type
PhoneEntry = record
FirstName, LastName: string[20];
PhoneNumber: string[15];
Listed: Boolean;
end;
PhoneList = file of PhoneEntry;
è la dichiarazione di un tipo file di record.
Prima di poter usare una variabile file è necessario associarla ad un file esterno tramite una chiamata alla
procedure AssignFile. Un file esterno è tipicamente un nome di file su disco, ma esso può anche essere un
dispositivo esterno come la tastiera o il display. Il file esterno memorizza le informazioni scritte nella variabile file
(file interno) o ritorna le informazioni lette dalla variabile file (file interno).
Una volta che è stabilita l'associazione con il file esterno la variabile file deve essere "aperta" per
predisporsi alle operazioni di input o output. Un file esistente può essere aperto tramite la chiamata alla procedure
Reset, e un nuovo file può essere creato e aperto tramite la chiamata alla procedure Rewrite.
Dopo aver aperto una variabile file con Reset (se esistente) o Rewrite (se non esistente) è possibile scriverci o
leggerci indifferentemente.
Dicevo all'inizio che ogni variabile file è vista come una sequenza lineare di componenti di un certo tipo; i
componenti sono numerati partendo da zero.
Alle variabili file normalmente si accede sequenzialmente; e cioè quando un componente del file è letto
tramite la procedure standard Read o scritto tramite la procedure standard Write, la posizione corrente del file si
sposta sulla successiva componente del file (dopo aver letto o scritto la componenete i, la posizione corrente si
sposta sulla componente i+1. Oltre ad accedere sequenzialmente è possibile accedere anche in modo casuale
tramite la procedure standard Seek, la quale muove la posizione corrente ad una specificata componente. Le
funzioni standard FilePos e FileSize possono essere usate per determinare la posizione corrente e la
dimensione (in componenti) del file specificato.
Quando un programma ha completato l'utilizzo di una variabile file essa deve essere chiusa usando la
procedure standard CloseFile. Dopo aver chiuso la variabile file, il file esterno associato viene aggiornato. La
variabile file, a questo punto, può essere associata ad un altro file esterno.
Per default, tutte le chiamate alle procedure e function standard di I/O sono automaticamente controllate
dagli errori, e se accade un errore viene invocata un'eccezione (o il programma termina se l'eccezione non è
abilitata). Questo controllo automatico può essere abilitato o non abilitato usando le direttive al compilatore {$I+}
582846450
3/19
e {$I–}. Quando il controllo di I/O è off, cioè quando una chiamata di procedure o function è compilata con
{$I–}, se capita un errore di I/O esso non genera un'eccezione; il controllo del risultato dell'operazione di I/O può
essere ugualmente effettuato tramite la chiamata alla function standard IOResult. Si deve chiamare la function
IOResult per superare l'errore, anche se l'errore non deve essere sostanzialmente gestito. Se non si eleimina l'errore
e {$I+} è lo stato corrente, la successiva chiamata ad una function di I/O potrà fallire.
Lista delle procedure o function, e loro descrizione, da utilizzare con file tipizzati
(Molte di queste operazioni sono presenti nella unti System, la quale è implicitamente compilata con ogni applicazione; altre
sono nella unit SysUtils la quale deve essere listata nella clausola uses del programma per il quale deve essere resa
disponibile)
Operazioni preliminari
Procedure AssignFile (var F; Nome:String);
Assegna il nome di un file esterno (l'identificatore del file
system) ad una variabile file




Procedure Reset (var F [:file; DimRec:word]);

Apre un file esistente

Procedure Rewrite (var F [:file;
DimRec:word]);

Crea e apre un nuovo file

F è una variabile file qualunque
L’assegnamento effettuato continua ad
esistere fino a quando non viene
effettuato un nuovo assegnamento su F.
Questa procedura non può essere
utilizzata su un file già aperto
Se la stringa del nome è la stringa vuota
si fa riferimento al file standard di
ingresso e al file standard di uscita
La dimensione del buffer di
trasferimento è opzionale ed usata solo
nel file non tipizzati.
Se il file non esiste viene generata
un’eccezione; è possibile gestire
l’eccezione con IOResult
Se il file era già esistente viene
cancellato, se non esistente viene creato,
in ogni caso la posizione corrente è
l’inizio del file e l’eof(F) è vera.
La dimensione del buffer di
trasferimento è opzionale ed usata solo
nel file non tipizzati.
Operazioni sulla variabile file
Procedure CloseFile(var F);

Chiude un file aperto
Function Eof (var F):boolean;

Ritorna lo stato della fine del file F

Function FilePos (var F):longint;

Ritorna la posizione corrente del file F

Function FileSize (var F):longint;

Il file associato viene chiuso ed
aggiornato (vengono flashati i buffer), si
libera il relativo handle DOS per poter
essere riutilizzato
Se la posizione corrente è successiva
all'ultima posizione del file allora ritorna
TRUE
Se la posizione corrente è precedente
all'ultima posizione del file allora ritorna
FALSE
Se la posizione corrente è all’inizio
ritorna 0
Se la posizione corrente è alla fine del
file, eof(F) è true, ritorna FileSize(F).
Se il file F è vuoto restituisce 0
Ritorna il numero di componenti di F
582846450
4/19
Function IOResult:word;

Ritorna un valore intero che è lo stato dell’ultima operazione
di I/O: 0 se l’operazione è andata a buon fine, un valore
diverso da 0 è il codice dell’errore provocato




Procedure Read (F, V1 [,V2, ..,Vn]);

Legge dal file uno o più valori e li pone rispettivamente in
una o più variabili specificate


Procedure Seek( var F; N:longInt);
Muove la posizione corrente del file F alla posizione N


Procedure Truncate (var F);


Tronca le dimensioni del file alla posizione corrente

Procedure Write ((F, V1 [,V2, ..,Vn]);

Scrive una o più variabili in una o più componenti del file


582846450
Per poter rilevare gli errori non deve
essere attivo il controllo degli errori,
{$I-}
Se avviene un errore di I/O, tutte le
successive operazioni di I/O vengono
ignorate fino alla prossima chiamata di
IOResult.
Una chiamata ad IOResult elimina il
flag di errore interno.
Per ripristinare il controllo automatico
degli errori di I/O usare {$I+}
Codici di errore:
- 100 disk read error (se si legge dopo la
fine del file)
- 101 disk write error (se il disco è
pieno)
– 102 file not assigned (il file non è stato
assegnato tramite AssignFile)
– 103 file not open (il file non è stato
aperto)
– 104 file not oper for input
– 105 file not oper for output
– 106 invalid numeric format
Ogni variabile specificata deve essere
dello stesso tipo del tipo base del file.
Per ogni variabile letta, la posizione
corrente viene avanzata di una
posizione.
Se si continua a leggere anche dopo la
fine del file si provoca un errore
Il numero della prima posizione è 0.
È possibile spostarsi sulla fine del file
con l’istruzione Seek(F, FileSize(F))
Tutti i record successivi alla posizione
corrente vengono cancellati;
La posizione corrente diventa la fine del
file
Ogni variabile specificata deve essere
dello stesso tipo del tipo base del file.
Per ogni variabile scritta, la posizione
corrente viene avanzata di una
posizione.
Se la posizione su cui si scrive è la fine
del file il file stesso viene espanso
5/19
Operazioni sul file esterno
Procedure ChDir (S:String);
Cambia la directory corrente
Procedure Erase (var F);

Il file F non deve essere aperto

Nella stringa S viene restitutita la
directory corrente del drive specificato
da D.
D=0 specifica il drive corrente, 1 il drive
A, 2 il drive B ecc.
Crea una subdirectory: il nome S può
corrispondere ad un path relativo o
assoluto;
L’ultimo elemento del percorso non può
essere un file
Al file associato ad F viene assegnato un
nuopvo nome; tutte le successive
operazioni effettuate su F opereranno sul
file col nuovo nome
Il file F non deve essere aperto
Cancella il file associato
Procedure GetDir (D.Byte; var S:String);
Restituisce la directory corrente del drive specificato

Procedure MkDir (S:String);

Crea una subdirectory

Procedure Rename (var F; NuovoNome:String);

Rinomina un file

Procedure RmDir (S:String);
Elimina una subdirectory vuota
I file non tipizzati (untyped file)
I file non tipizzati sono canali di I/O a basso livello usati primariamente per accedere direttamente ai file
del disco senza preoccuparsi del tipo o della struttura. Un file non tipizzato è dichiarato con la parola file e nulla
più.
Per esempio:
var DataFile: file;
Per i file non tipizzati, le procedure Reset e Rewrite prevedono un parametro extra per specificare la
dimensione del record usato nel trasferimento. Per ragioni storiche, la dimensione di default del record è 128 byte.
(Un record di dimensione 1 è il solo valore che riflette la misura esatta per ogni file)
Lista delle procedure o function, e loro descrizione, da utilizzare con file non tipizzati
Eccetto per Read e Write, tutte le procedure e function standard per i file tipizzati sono disponibili anche
per i file tipizzati. In luogo di Read e Write, vi sono due procedure chiamate BlockRead e BlockWrite usate
anche per trasferire dati ad alta velocità:
procedure BlockRead (var F:file; var Buf;
Cont:Word [; var Risultato:Word]);

legge uno o più record da un file non tipizzato




582846450
F è una variabile file qualsiasi, Buf è
una variabile qualsiasi, Cont e
Risultato sono di tipo Word;
Legge al più Cont record dal file F;
In Risultato viene messo il numero
effettivo di record letti (<= Cont)
Il blocco trasferito occuperà
Cont*DimRec byte (DimRec è
specificato nell’apertura, 128 se non
specificato nell’apertura)
Cont*DimRec deve essere minore
di 64K
6/19

Procedure BlockWrite (var F:file; var Buf;
Cont:Word [; var Risultato:Word]);
legge uno o più record da un file non tipizzato








BolckRead provoca l’avanzamento
della posizione corrente di Risultato
Record
Il file deve essere aperto
F è una variabile file qualsiasi, Buf è
una variabile qualsiasi, Cont e
Risultato sono di tipo Word;
Scrive dalla memoria al file al più
Cont record nel file F;
In Risultato viene messo il numero
effettivo di record completi scritti (<=
Cont)
Il blocco trasferito occuperà
Cont*DimRec byte (DimRec è
specificato nell’apertura, 128 se non
specificato nell’apertura)
Cont*DimRec deve essere minore
di 64K
BolckRead provoca l’avanzamento
della posizione corrente di Risultato
Record
Il file deve essere aperto
Esempio:
Procedure CopyFile (File1, File2: String);
(* procedure che copia un file File1 nel file File2 *)
var
FromF, ToF: file;
NumRead, NumWritten: word;
Buf: array[1..2048] of char;
begin
AssignFile(FromF, File1);
Reset(FromF,1);
AssignFile(ToF, File2);
Rewrite(ToF,1);
repeat
BlockRead(FromF, Buf, SizeOf(Buf), NumRead);
BlockWrite(ToF, Buf, NumRead, NumWritten);
until (NumRead = 0) or (NumWritten <> NumRead);
CloseFile(FromF);
CloseFile(ToF);
end;
I FILE di testo (TEXT FILE)
I file di testo (Text) sono file esterni costituiti da una sequenza di caratteri formattata in linee, dove ogni
linea è terminata con un marcatore particolare di fine linea (un carriage-return character #13, possibilmente
seguito linefeed character #10). Anche e soprattutto per questo fatto il tipo text è distinto dal tipo file of Char. Per i
file di tipo text sono definite 2 speciali procedure di lettura e scrittura, che approfondiremo più avanti, vengono
sovrascritte le procedure Read e Write e tramite di esse è possibile leggere o scrivere valori anche non di tipo char,
in tal caso i valori sono automaticamente trasformati nella corrispondente rappresentazione in caratteri. Per
esempio Read(F, I), dove I è una variabile di tipo Integer, legge una sequenza di byte, interpretandola come la
rappresentazione di un numero intero relativo e carica il valore corrispondente in I.
582846450
7/19
A differenza dei file tipizzati, per i quali l'accesso ai singoli record è diretto, tramite la routine Seek(F,i),
nei file di testo l'accesso ai singoli caratteri è sequenziale, pertanto con tali file la routine seek non è utilizzabile.
Passiamo ora in rassegna le operazioni previste sui file tipizzati e per ciascuna di esse diremo se è
applicabile anche sui file di testo ed in che modo.
Operazioni preliminari
Procedure AssignFile (var F; Nome:String);
Stesso funzionamento
Assegna il nome di un file esterno (l'identificatore del
file system) ad una variabile file
Procedure Reset (var F [:file; DimRec:word]);
Apre un file esistente
Procedure Rewrite (var F [:file;
DimRec:word]);

Stesso funzionamento, tranne per il fatto
che dopo tale apertura è possibile solo
la lettura e non la scittura.
Stesso funzionamento
Crea e apre un nuovo file
Operazioni sulla variabile file
Procedure CloseFile(var F);
Stesso funzionamento
Chiude un file aperto
Function Eof (var F):boolean;
Stesso funzionamento
Ritorna lo stato della fine del file F
Function FilePos (var F):longint;
NON DEFINITA
Ritorna la posizione corrente del file F
Function FileSize (var F):longint;
NON DEFINITA
Ritorna il numero di componenti di F
Function IOResult:word;
Ritorna un valore intero che è lo stato dell’ultima
operazione di I/O: 0 se l’operazione è andata a buon fine, un
valore diverso da 0 è il codice dell’errore provocato
Procedure Read (F, V1 [,V2, ..,Vn]);
Legge dal file uno o più valori e li pone
rispettivamente in una o più variabili specificate
Procedure Seek( var F; N:longInt);
Stesso funzionamento

Avviene una conversione tra la
rappresentazione in caratteri del valore
letto nella rappresentazione del tipo
della variabile specificata.
 Per ogni variabile letta, la posizione
corrente viene avanzata di un numero di
caratteri variabile, dipendente sempre
dal tipo specificato per ogni variabile
 Se si continua a leggere anche dopo la
fine del file si provoca un errore
NON DEFINITA
Muove la posizione corrente del file F alla posizione
N
Procedure Truncate (var F);
NON DEFINITA
Tronca le dimensioni del file alla posizione corrente
Procedure Write ((F, V1 [,V2, ..,Vn]);

Scrive una o più variabili in una o più componenti
del file


582846450
Avviene una conversione tra la
rappresentazione in terna del valore da
scrivere nella rappresentazione esterna
in caratteri in funzione del tipo della
variabile specificata.da scrivere.
Per ogni variabile scritta, la posizione
corrente viene avanzata di un numero di
caratteri variabile, dipendente sempre
dal tipo specificato per ogni variabile
La posizione su cui si scrive è sempre la
fine del file per via della limitazione
sequenziale
8/19
Operazioni sul file esterno
Procedure ChDir (S:String);
Stesso funzionamento
Cambia la directory corrente
Procedure Erase (var F);
Stesso funzionamento
Cancella il file associato
Procedure GetDir (D.Byte; var S:String);
Restituisce la directory corrente del drive specificato
Procedure MkDir (S:String);
Crea una subdirectory
Procedure Rename (var F;
NuovoNome:String);
Rinomina un file
Procedure RmDir (S:String);
Elimina una subdirectory vuota
Nuove Operazioni definite solo su file di testo (text)
Procedure Append (var F:text);
Apre un file di testo esistente F e si predispone alla
scrittura accodando le informazioni
Function Eoln (F:text):boolean;
Ritorna lo stato dell'end-of-line, per la posizione
corrente, del file F specificato
Function SeekEoln (F:text):boolean;
Ritorna lo stato dell'end-of-line del file F specificato
Function SeekEof (F:text):boolean;
Ritorna lo stato dell'end-of-file del file F specificato
Stesso funzionamento
Stesso funzionamento
Stesso funzionamento
Stesso funzionamento





La posizione corrente è la fine del file
Con tale apertura è possibile solo la
scrittura
Se la posizione corrente si trova
sull'end-of-line la funzione ritorna true,
diversamente ritorna false
La differenza con la funzione precedente
è che tale funzione ritorna true anche nel
caso in cui tra la posizione corrente e
l'end-of-line si trovino caratteri di
tabulazione
La differenza con la funzione eof è che
tale funzione ritorna true anche nel caso
in cui tra la posizione corrente e l'endof-file si trovino caratteri di tabulazione.
Procedure Flush (F:text);
Scarica il buffer del text file F specificato
Procedure Readln (F:text; V1 [,V2,
..,Vn]);
Legge, come la read, e poi salta all'inizio della linea
successiva
Procedure Writeln (F:text; V1 [,V2,
..,Vn]);
Scrive, come la write, e poi inserisce il marcatore di
fine linea
582846450
9/19
Sommario finale delle routines per l’input output in OP
La seguente lista rappresenta tutte le routines per la manipolazione dei file di input - output previste da
Object Pascal.
Procedure o function Descrizione
Append
Apre un file di testo esistente per appendere informazione (caratteri) in fondo al file stesso.
AssignFile
Assegna il nome di un file esterno (l'identificatore del file system) ad una variabile file
interna ad una Unit.
BlockRead
Legge uno o più record da un file non tipizzato (untyped file).
BlockWrite
Scrive uno o più record in un file non tipizzato (untyped file).
ChDir
Cambia la directory corrente.
CloseFile
Chiude un file aperto.
Eof
Ritorna lo stato corrente dell' end-of-file di un file aperto.
Eoln
Ritorna lo stato dell' end-of-line di un file di testo aperto.
Erase
Cancella un file esterno.
FilePos
Ritorna la posizione corrente di un file tipizzato o non tipizzato file aperto.
FileSize
Ritorna la lunghezza corrente del file aperto (non utilizzabile per file di testo).
Flush
Svuota, scaricandolo su file, il buffer di un file di testo.
GetDir
Ritorna la directory corrente del drive specificato in ingresso.
IOResult
Ritorna un valore intero che è lo stato dell'ultima operazione di I/O.
MkDir
Crea una subdirectory.
Read
Legge uno o più valori da un file e li memorizza in una o più variabili specificate.
Readln
Come la routine precedente, ed in più avanza fino alla linea successiva. (utilizzabile solo
per i file di testo).
Rename
Cambia il nome di un file esterno.
Reset
Apre in lettura/scrittura un file esistente.
Rewrite
Crea e apre in scrittura un nuovo file.
RmDir
Rimuovi una subdirectory vuota.
Seek
Sposta la posizione corrente di un file tipizzato o non tipizzato. (Non usabile per file di
testo).
SeekEof
Ritorna il valore dell' end-of-file di un file di testo.
SeekEoln
Ritorna lo stato dell' end-of-line di un file di testo.
SetTextBuf
Assigna un buffer di I/O ad un file di testo.
Truncate
Trunca un file tipizzato o non tipizzato alla posizione corrente.
Write
Scrive uno o più valori nel file specificato.
Writeln
Come la routine precedente, ed inoltre inserisce nel file di testo un marcatore di fine linea.
582846450
10/19
L'approccio FILE SYSTEM tramite API:
E’ possibile accedere ai file tramite le primitive di sistema messe a disposizione dal Sistema Operativo; nel
sistema Unix sono le System Call relative al File System, nel sistema Windows sono le API (application
programming interface), funzioni C il cui identificatore è riportato qui di seguito (per ulteriori approfondimenti si
può fare riferimento all'Help di Delphi 4 al capitolo MS SDK Help Files -> Win32 Developer's References, alla
voce File Function (win32 HLP)):
-
AreFileApisANSI
CancelIO
CopyFile
CopyFileEx
CopyProgressRoutine
CreateDirectory
CreateDirectoryEx
CreateFile
CreateIoCompletionPort
DefineDosDevice
DeleteFile
FileIOCompletionRoutine
FindClose
FindCloseChangeNotification
FindFirstChangeNotification
FindFirstFile
FindFirstFileEx
FindNextChangeNotification
FindNextFile
FlushFileBuffers
GetBinaryType
GetCompressedFileSize
GetCurrentDirectory
GetDiskFreeSpace
GetDiskFreeSpaceEx
GetDriveType
GetFileAttributes
GetFileAttributesEx
GetFileInformationByHandle
GetFileSize
GetFileType
GetFullPathName
GetLogicalDrives
GetLogicalDriveStrings
GetQueuedCompletionStatus
GetShortPathName
GetTempFileName
GetTempPath
GetVolumeInformation
LockFile
LockFileEx
MoveFile
MoveFileEx
PostQueuedCompletionStatus
QueryDosDevice
ReadDirectoryChangesW
ReadFile
ReadFileEx
RemoveDirectory
SearchPath
SetCurrentDirectory
582846450
-
SetEndOfFile
SetFileApisToANSI
SetFileApisToOEM
SetFileAttributes
SetFilePointer
SetVolumeLabel
UnlockFile
UnlockFileEx
WriteFile
WriteFileEx
11/19
Approfondimento su alcune routine API di uso più frequente:
Apertura
HANDLE CreateFile(
LPCTSTR lpFileName,
// pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode,
// share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security
attributes
DWORD dwCreationDistribution,
// how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile
// handle to file with attributes to copy
);
dwDesiredAccess :
0
GENERIC_READ
dwShareMode :
0
FILE_SHARE_DELETE
lpSecurityAttributes
NIL
GENERIC_WRITE
FILE_SHARE_READ
FILE_SHARE_WRITE
dwCreationDistribution
CREATE_NEW
CREATE_ALWAYS
OPEN_EXISTING
OPEN_ALWAYS
Creates a new file. The function fails if the specified file already exists.
Creates a new file. The function overwrites the file if it exists.
Opens the file. The function fails if the file does not exist.
Opens the file, if it exists. If the file does not exist, the function creates the
file.
TRUNCATE_EXISTING Opens the file. Once opened, the file is truncated so that its size is zero bytes.
dwFlagsAndAttributes
Attributes
FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED,
FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_NORMAL,
FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY
Flag
FILE_FLAG_WRITE_THROUGH, FILE_FLAG_OVERLAPPED,
FILE_FLAG_NO_BUFFERING, FILE_FLAG_RANDOM_ACCESS,
FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_DELETE_ON_CLOSE,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_POSIX_SEMANTIC
hTemplateFile
0
Return Values: if the function succeeds, the return value is an open handle to the specified file. If the function
fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
Chiusura
BOOL CloseHandle(
HANDLE hObject
);
// handle to file
Return Values : if the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
582846450
12/19/
Scrittura
BOOL WriteFile(
HANDLE hFile,
// handle to file to write to
LPCVOID lpBuffer, // pointer to data to write to file
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten,
// pointer to number of bytes written
LPOVERLAPPED lpOverlapped
// pointer to structure needed for
overlapped I/O
);
hFile : Identifies the file to be written to (must have been created with GENERIC_WRITE access)
lpBuffer : Points to the buffer containing the data to be written to the file.
nNumberOfBytesToWrite : Specifies the number of bytes to write to the file.
lpNumberOfBytesWritten : Points to the number of bytes written by this function call.
lpOverlapped NIL
Return Values: if the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
Lettura
BOOL ReadFile(
HANDLE hFile,
// handle of file to read
LPVOID lpBuffer, // address of buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // address of number of bytes read
LPOVERLAPPED lpOverlapped
// address of structure for data
);
hFile
file.
Identifies the file to be read. The file handle must have been created with GENERIC_READ access to the
lpBuffer
Points to the buffer that receives the data read from the file.
nNumberOfBytesToRead
Specifies the number of bytes to be read from the file.
lpNumberOfBytesRead Points to the number of bytes read.
lpOverlapped NIL
Return Values : If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
Posizionamento (seek)
DWORD SetFilePointer(
HANDLE hFile,
// handle of file
LONG lDistanceToMove, // number of bytes to move file pointer
582846450
13/19/
PLONG lpDistanceToMoveHigh, // address of high-order word of distance to
move
DWORD dwMoveMethod
);
// how to move
hFile Identifies the file whose file pointer is to be moved. The file handle must have been created with
GENERIC_READ or GENERIC_WRITE access to the file.
lDistanceToMove Specifies the number of bytes to move the file pointer. A positive value moves the pointer
forward in the file and a negative value moves it backward.
lpDistanceToMoveHigh Points to the high-order word of the 64-bit distance to move. If the value of this
parameter is NULL, SetFilePointer can operate only on files whose maximum size is 2^32 - 2. If this parameter is
specified, the maximum file size is 2^64 - 2. This parameter also receives the high-order word of the new value of
the file pointer.
dwMoveMethod
Specifies the starting point for the file pointer move.
FILE_BEGIN The starting point is zero or the beginning of the file.
FILE_CURRENT
The current value of the file pointer is the starting point.
FILE_END
The current end-of-file position is the starting point.
Return Values If the SetFilePointer function succeeds, the return value is the low-order doubleword of the new
file pointer. If the function fails the return value is 0xFFFFFFFF (-1).
582846450
14/19/
L'approccio DATA BASE tramite BDE (Delphi 4 o succ):
I componenti di Delphi per la gestione dei dati su disco
Delphi dispone di diversi componenti relativi ai database:
 la pagina DATA ACCESS della Components Palette raccoglie i componenti utilizzati per interagire con i
database: DataSource, Table, Query, StoredProc, DatBase, ecc. (componenti non visuali)
 la pagina DATA CONTROL della Components Palette che raccoglie i componenti visuali utilizzati per
vedere e sottoporre ad editing i dati di un database in un form: DBGrid, DBNavigator, DBText, DBEdit, ecc.
la pagina DATA ACCESS
la pagina DATA CONTROL (data-aware)
Per accedere ai dati in Delphi serve una sorgente di dati descritta nel componente DataSource (nella
pagina DATA ACCESS) che, non indica direttamente i dati ma fa riferimento ad una Tabella, o al risultato di una
Query o ad una Stored Procedure, per fornire le informazioni ad uno dei componenti visuali della pagina DATA
CONTROL come indicato nel seguente schema:
Data base
Data base
BDE
…..
Table
…..
….
Query
DataSource
Data Control
582846450
15/19/
COMPONENTI DI DATA ACCESS
Centrale è l'oggetto TDataSet, che altro non è che l'astrazione di un insieme ordinato di righe di dati
(record), suddivise ognuna secondo un insieme ordinato di colonne, ognuna delle quali identifica un campo
all'interno del record. Generalmente non si utilizza direttamente tale classe, ma si ricorre ad uno dei discendenti
TTable, TQuery oppure TStoredProc. Questi componenti discendono tutti dalla classe TDBDataSet, la quale
discende a sua volta da TDataSet.
TTable:
la classe TTable incapsula le funzionalità proprie delle tabelle di database. Normalmente si tratta
dell'astrazione relativa ad un file che contiene record fisicamente immagazzinati su di un disco. Ogni volta che si
ha bisogno di accedere ad una specifica tabella o ad un insieme di tabelle di database si fa riferimento ad una o
più istanze di questa classe. Per configurare correttamente le proprietà di un oggetto TTable, dopo averlo
posizionato sulla form, occorre impostare il contenuto di alcune proprietà fondamentali, elencate di seguito.
Per accedere ad un database, corrispondente ad una directory che contiene un insieme di file nel formato
dBase oppure Paradox, oppure corrispondente ad un unico file nel formato MS Access, si deve per prima cosa
impostare la proprietà DatabaseName immettendo o la directory in questione, oppure un alias definito nel
Borland Database Engine (BDE); non è necessario, una volta configurato BDE, ricordarsi i nomi degli alias
disponibili: facendo click alla destra del campo che permette l'immissione della proprietà, Delphi provvede a
riempire una combo-box con i nomi di tutti gli alias disponibili.
La seconda proprietà da impostare è TableName, ovvero il nome della tabella cui si vuole avere accesso
tramite l'oggetto TTable. Come per DatabaseName è sufficiente cliccare alla destra della proprietà per avere una
lista delle tabelle disponibili all'interno del database selezionato.
La terza proprietà da impostare è Active da impostare a true.
Queste sono le uniche proprietà che si devono impostare obbligatoriamente per avere accesso ad una
tabella: per le altre sono normalmente sufficienti le impostazioni di default. Altre proprietà utili sono la ReadOnly
che, se impostata a valore logico vero (true), fa in modo che qualsiasi tentativo di cambiare i dati contenuti nella
tabella fallisca: non si potrà quindi né variare il contenuto di alcun campo, né effettuare cancellazioni o
inserimenti di record. La proprietà Exclusive, impostata a true, fa guadagnare accesso esclusivo alla tabella,
assicurandoci quindi che nessun altro utente, ma anche nessun altro oggetto TTable, o comunque un Dataset,
all'interno della propria applicazione, possa accedere alla tabella. Questa proprietà si applica solo a tabelle locali,
quindi non nel caso di accesso a tabelle gestite da server SQL.
TQuery:
la classe TQuery rappresenta l'insieme di dati restituiti da una query SQL, ovvero una ricerca su una o più
tabelle di un database espressa utilizzando un linguaggio sviluppato ad hoc per tali compiti (Structured Query
Language). Il risultato di una query SQL, generalmente, non ha controparte diretta su disco, nel senso che i record
risultanti possono essere pensati solo come insieme temporaneo, tanto da poterlo immaginare conservato nella
sola memoria centrale del computer.(Cfr approfondimento più avanti)
TStoredProc:
tale classe incapsula una stored procedure all'interno di un server SQL: in pratica, anche se si commette
un'imprecisione ad affermare ciò, può essere considerata alla pari di una query, dalla quale differisce per il fatto di
essere memorizzata sul server e non sul client come accade con TQuery.
All'interno delle tabelle è possibile definire una serie di indici, grazie ai quali è possibile mantenere
ordinate, secondo il contenuto di uno o più campi, le righe delle tabelle. Non esiste alcuna classe VCL in grado di
rappresentare indici: in generale le funzioni per lo sfruttamento ed il mantenimento di tali strutture di ordinamento
vengono fornite direttamente dalla classe TTable. Essendo per natura tabelle temporanee, le query SQL non
possono fornire indici per la consultazione.
IL BDE
Per iniziare a lavorare con i dati è necessario, prima della compilazione, individuarne il modello
concettuale (definendo Entità e Relazioni) e successivamente costruire il modello Relazionale corrispondente ed
implementare, mediante appositi ambienti (DBMS), le tabelle dei dati; nel nostro caso useremo il sistema di
costruzione di Data Base integrato nell'ambiente Delphi 4 o succ. , che è Database Desktop contenuto nel menu
TOOLS, oppure il sistema MS ACCESS. Dopo di che è necessario configurare il BDE il motore di Delphi per
582846450
16/19/
l'accesso ai dati su disco. Per raggiungere l'obiettivo dell'indipendenza dei programmi dalla particolare
rappresentazione fisica della base di dati, BDE permette di definire una serie di alias in grado di identificare con
stringhe di caratteri un particolare database, i cui dettagli implementativi (tipo di server, path, formati di file)
vengono inseriti attraverso un'apposita utility di configurazione (BDE Administrator) liberamente distribuibile
insieme al Database Engine e richiamabile alla voce BDE Explorer dal menu DATABASE.
Nel DataBase Explorer si deve definire:
l'alias (nome simbolico del Database)
il DataBase driver (quale tipo di data base si utilizza)
il path del database su disco (directory se Paradox, file se MS Access)
In DataBase Desktop si devono definire:
le tabelle (campi, tipi, chiavi, indici, valori predefiniti, vincoli di integrità, ecc.)
la directory di lavoro
COMPONENTI DI DATA CONTROL
Dell'oggetto TTable abbiamo parlato a sufficienza, almeno per quanto riguarda le caratteristiche sfruttate
nell'esempio: le novità sono costituite da TDBGrid, elemento di interfaccia con l'utente ed il database e da
TDataSource. Cominciando da quest'ultimo, si può dire che esso provvede a collegare la TTable con la griglia di
database. In generale si deve ricorrere ad un'istanza di tale oggetto tutte le volte che si ha necessità di collegare un
Dataset ad uno o più componenti Data-bound di interfaccia con l'utente. A quest'ultima categoria appartengono
una serie di "repliche" degli elementi di interfaccia incontrati nella terza puntata del corso, ovvero i Visual
Components.
I controlli data-aware, sono il corrispettivo dei visual controls, come list-box, griglie o campi di editing che non
solo sono in grado di rappresentare elementi di controllo di Windows, ma sono anche capaci di collegare il loro
contenuto informativo ad uno o più campi di una tabella. Il tutto senza passare attraverso il programmatore, ma
semplicemente collegandoli ad un oggetto della classe TDataSource, a sua volta collegato ad un Dataset.
Approfondimenti su: TQuery - rudimenti di SQL
All'interno della gerarchia di oggetti della VCL possiamo trovare un oggetto, chiamato TQuery, che presenta
molte analogie con TTable, in quanto opera su dataset e permette di effettuare quasi tutte le operazioni viste
durante la scorsa puntata, in particolare quelle di navigazione e di gestione dei campi di dati. La differenza
fondamentale tra queste due classi sta nel metodo di definizione del dataset: se con TTable si fa riferimento ad un
insieme di record fisicamente presenti su disco, al più filtrati secondo il contenuto di uno o più campi chiave o in
relazione master-detail con un insieme di altri record, con TQuery il dataset fa riferimento ad un insieme,
generalmente temporaneo, di record che costituiscono il risultato dell'invocazione di una query espressa attraverso
un particolare linguaggio, chiamato SQL (Structured Query Language).
Generalmente, e questo lo si intuisce anche dalla somiglianza tra TQuery e TTable, il risultato di una query SQL è
un insieme di record che, abbiamo visto, in Delphi viene identificato con un dataset. L'istruzione più importante
nel linguaggio SQL è la select, che permette di selezionare record da una o più tabelle, rispondenti a determinati
criteri. Supponiamo di avere una tabella chiamata Clienti, con i campi Nome, Cognome, Indirizzo e Codice;
quest'ultimo campo, di tipo intero, costituisce riferimento per i record della tabella Ordini, costituiti dai campi
CodiceCliente, Data, Articolo e Quantità. Se volessimo avere come risposta dalla query una tabella (ovviamente
temporanea) contenente Nome e Cognome di tutti i clienti che hanno cognome che inizia con la lettera 'C' oppure
con lettere successive, dovremmo eseguire la seguente query:
SELECT Nome, Cognome FROM Clienti WHERE Cognome >= 'C'
Si capisce immediatamente la sintassi della select che richiede la lista dei campi da includere nella tabella di
risposta. Tramite la clausola from si specifica il nome della tabella da cui andare a pescare i dati; con la where si
impongono condizioni sui campi: tutti e soli i record che le soddisfano andranno a far parte dell'insieme di record
di risposta.
582846450
17/19/
E' importante sottolineare che, sulla tabella Clienti, a parte l'esistenza dei campi menzionati sopra e del loro tipo
di dato, non si sono fatte ipotesi sulla presenza di indici particolari, né si è indicata la strada da seguire per
ottenere il risultato: con un approccio più di basso livello, quale è ad esempio quello delle TTable, si sarebbero
dovuti fare i conti con l'esistenza di indici, per ottimizzare le operazioni di ricerca e selezione. Ad esempio
sarebbe stato stupido, in presenza di un indice sul campo Cognome, non utilizzarlo per filtrare i record contenenti
un valore minore di 'C'.
Ebbene, uno dei punti di forza di SQL è proprio quello di denotare il risultato voluto, lasciando libertà pressoché
assoluta al motore di database di scegliere la strategia migliore di accesso ai dati richiesti, ad esempio a seconda
della presenza di indici su campi particolari: sempre ritornando all'esempio sopra, se costruisco una procedura di
accesso a basso livello che poggia sulla presenza di indici sul campo Cognome, se l'indice viene rimosso, il mio
programma necessiterà di profondi cambiamenti. Viceversa, se non sfrutto eventuali indici costruiti a posteriori,
magari per altri compiti di ricerca, rischio di realizzare programmi che non ottimizzano le prestazioni della
macchina. Con SQL è il gestore di database che pensa a come accedere ai dati: noi richiediamo solo un
particolare risultato. Ovviamente si perde qualcosa in efficienza in termini assoluti (basta pensare solo al tempo
necessario per decidere il piano di accesso ai dati richiesti, cosa che nel caso di algoritmo codificato "a mano" è
istantanea, ma che per query di una certa complessità può richiedere un tempo superiore a qualche minuto
secondo)
La sintassi di SELECT è la seguente:
SELECT [DISTINCT] lista_colonne
FROM lista_tabelle
[WHERE condizioni_di_ricerca]
[ORDER BY lista_campi_ordinamento]
[GROUP BY lista_campi_raggruppamento]
[HAVING condizioni_sui_gruppi]
lista_colonne è l'insieme di colonne delle tabelle specificate in lista_tabelle; le condizioni di ricerca possono
essere multiple, sfruttando gli operatori and ed or. E' possibile ricavare, indipendentemente dagli indici presenti
sulle tabelle coinvolte dalla query, un insieme di record ordinati a seconda del contenuto del campo. Specificando
la parola chiave DISTINCT è possibile ricavare un insieme di righe non duplicate.
Facciamo alcuni esempi:
SELECT Cognome FROM Clienti
seleziona tutti i cognomi dalla tabella clienti.
Se ci sono record multipli in Clienti con lo stesso cognome, otteniamo lo stesso numero di cognomi duplicati
nella tabella di risposta. Se vogliamo una tabella con tutti i cognomi non duplicati, dobbiamo lanciare:
SELECT DISTINCT Cognome FROM Clienti
Se poi li vogliamo anche in ordine alfabetico dobbiamo specificare la clausola ORDER BY:
SELECT DISTINCT Cognome FROM Clienti ORDER BY Cognome
Visto che abbiamo iniziato a parlare di SQL ed abbiamo fatto alcuni esempi di query, è importante sottolineare il
fatto che SQL è un linguaggio case-insensitive. L'aver utilizzato lettere maiuscole per le parole chiave SQL ed un
misto di maiuscole e minuscole per i campi è assolutamente ininfluente: solitamente, però, si tende ad assumere
una convenzione per motivi di leggibilità. Quella mostrata negli esempi è una delle più comuni.
Uno dei punti di forza del linguaggio SQL è quello di poter eseguire query che coinvolgono più tabelle, le
cosiddette join: supponiamo di dover elencare, sempre a partire dalle tabelle Clienti ed Ordini definite sopra, tutti
gli ordini del cliente Filippo Bosi
SELECT Ordini.Articolo, Ordini.Quantita, Ordini.Data
FROM Clienti,Ordini
WHERE Ordini.CodiceCliente = Clienti.Codice
AND Clienti.Nome = 'Filippo'
AND Clienti.Cognome = 'Bosi'
582846450
18/19/
Si noti come si sia specificato, per ogni campo, il file di appartenenza. In SQL standard questo non è necessario
quando i campi delle due tabelle non condividono gli stessi nomi. Operando su tabelle Paradox e dBase,
utilizzando quindi database locali, gestiti direttamente dal Borland Database Engine, se non si specificano le
tabelle di appartenenza, il risultato, invece di un insieme di record, è un errore a run-time!
Nelle query viste finora abbiamo specificato sempre l'insieme di campi da inserire nella tabella di risposta. Molto
spesso è desiderabile ottenere in risposta tutte le colonne delle tabelle coinvolte dalla query. Esiste a tale scopo
una wildcard che denota "tutti i campi", ovvero "*". Volendo, ad esempio, un insieme di record composto da tutti
i campi della tabella Ordini, ordinato per data, dovremmo lanciare la seguente query:
SELECT * FROM Ordini ORDER BY Data
SQL non viene utilizzato solo per la selezione di record, ma anche per effettuare inserimenti, cancellazioni ed
aggiornamenti di record, attraverso le istruzioni INSERT, DELETE ed UPDATE.
Se, ad esempio, si vuole cancellare dalla tabella Ordini tutti i record relativi ad un particolare cliente,
semplicemente si fa eseguire questa query (ovviamente dopo aver impostato il codice del cliente)
DELETE FROM Ordini WHERE CodiceCliente = n
Se invece si vogliono incrementare del 20% i prezzi di un ipotetico listino:
UPDATE Listino SET Prezzo=Prezzo*1.20
Anche alla UPDATE è possibile aggiungere una clausola WHERE, ad esempio per limitare l'incremento del 20%
agli articoli che hanno prezzo inferiore alle 15000 lire:
UPDATE Listino SET Prezzo=Prezzo*1.20 WHERE Prezzo<15000
Naturalmente, a differenza di quanto accade con la SELECT, le query di inserimento, cancellazione ed
aggiornamento non restituiscono un set di record. Per questo, vedremo, ad esse sarà riservato un trattamento
particolare in sede di lancio della query attraverso la classe TQuery.
582846450
19/19/