Compago

...free knowledge

 
  • Increase font size
  • Default font size
  • Decrease font size
Home Manuali Programmazione Guida sui variant record e variant

Guida sui variant record e variant

E-mail Stampa PDF

Record con parti variant

I record sono insiemi strutturati di dati, i suoi campi o elementi possono essere dei dati di tipo elementare o complesso e la sua dimensione sarà la somma di quella degli elementi che lo compongono. In memoria infatti ogni elemento sarà collocato in spazi consecutivi, esempio:

type
MyRec = record
Nome:array [0..9] of char;
Numero:integer;
end

Il campo nome è grande 10 byte, il campo numero 4 byte, in totale il record occuperà 10+4 byte più altr 2 di allineamento, quindi 16 byte ovvero 4 double word.
Questo perchè il compilatore cerca sempre di allineare i vari campi in termini di double word:

MyRec Nome 01 02 03 04
05 06 07 08
09 10

Numero 01 02 03 04

Per evitare l'allineamento tra i vari campi basta usare la direttiva packed:

type
MyRec = packed record
Nome:array [0..9] of char;
Numero:integer;
end

In questo caso il record avrà dimensione 14 byte.

Una possibilità a volte trascurata è quella di poter sovrapporre in altenativa i campi di un record; questa modalità viene chiamata parte variant di un record e va inserita alla fine del record.
I record variant sono l'analogo in Delphi delle unioni in C.Vediamo un esempio:

type
MyRec = packed record
Nome:array [0..7] of char;
case tipo:boolean of
true :(Numero1:integer;Numero2:integer);
false:(Codice: array [0..15] of char);
end;

la parte in rosso è la parte fissa del record , mentre quella blu è la parte variant. Funzionalmente è come se avessimo definito due tipi di record diversi, infatti a seconda che il campo tipo sia vero o falso avremo i seguenti record:

type
MyRec = packed record
Nome:array [0..7] of char;
Tipo:boolean; //true
Numero1:integer;
Numero2:integer;
end;
type
MyRec = packed record
Nome:array [0..7] of char;
Tipo:boolean; //false
Codice: array [0..15] of char;
end;

Il campo "Tipo" è stato introdotto per aiutare a discriminare tra le due varianti di record, ma non ha un carattere vincolante dal punto di vista della programmazione o meglio della compilazione, quindi occorre tenerne conto al momento in cui si scrivono o si leggono i dati del record.
Questo vuol dire che se anche impostassi il valore di Tipo a falso potrei sempre leggere il campo Numero1, ma probabilmente il valore sarebbe sbagliato, dato che al suo posto dovrebbero esserci dei byte che rappresentano delle lettere.

A questo punto, se non fosse necessario tenere traccia del tipo di dato conservato nel record, potremo considerare l'opzione di non inserire il campo Tipo.
Un esempio ne sono le coordinate che identificano una finestra:

type 
TPoint = packed record
X: integer;
Y: integer;
end;

TRect = packed record
  case Integer of
    0: (Left, Top, Right, Bottom: Integer);
    1: (TopLeft, BottomRight: TPoint);
end;

In questo modo abbiamo creato due rappresentazione diverse della stessa struttura dati, infatti i dati all'interno sono in entrambi i casi quattro numeri interi, e quello che cambia è solo il modo di chiamarli, dato che il valore contenuto nel campo left è lo stesso contenuto in TopLeft.X.  

Il tipo contenuto all'interno della  direttiva case solitamente serve a definire il tipo indici usati per specificare i vari casi. Nel caso precedente avendo solo due opzioni avremo potuto usare tranquillamente un tipo boolean e non sarebbe cambiato nulla:

TRect = packed record
  
case Boolean of
    false: (Left, Top, Right, Bottom: Integer);
    true: (TopLeft, BottomRight: TPoint);
end;

Un'altra cosa da notare è la presenza della direttiva packed, abbastanza inutile in questo caso dato che gli interi in questione occupano una double word ovvero quattro byte e quindi non verrebbero aggiunti ulteriori byte di allineamento, ma questo è vero in un contesto a 32 bit , mentre non sarà vero usando un sistema operativo a 64 bit.

Ora vediamo un esempio un po più complicato:

  PVarRec = ^TVarRec;
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

Il precedente tipo di record è interamente variant e la sua dimensione nonostante il lungo elenco è di due double word, questo è dovuto al primo elemento della lista, il quale contiene due campi, il primo il dato intero, mentre il secondo(VType) un byte che ci indica la tipologia del dato. Tutte le restanti opzioni hanno un solo campo sempre di lunghezza massima una double word, e che vanno quindi di volta in volta a sovrapporsi al primo campo, mentre il campo VType che abbiamo visto prima, nonostante venga specificato in una parte variant del record, ne rappresenta la parte fissa.
Con questa tecnica quindi è possibile anteporre la parte variabile a quella fissa, nonostante occorra porre un limite alla dimensione di quella variabile.

Il caso seguente è un record con na struttura piuttosto complicata ed è molto importante perchè sta alla base dei tipi variant usati nella tecnologia COM.

  TVarType = Word;
PVarData = ^TVarData;
TVarData = packed record
case Integer of
0: (VType: TVarType;
case Integer of
0: (Reserved1: Word;
case Integer of
0: (Reserved2, Reserved3: Word;
case Integer of
varSmallInt: (VSmallInt: SmallInt);
varInteger: (VInteger: Integer);
varSingle: (VSingle: Single);
varDouble: (VDouble: Double);
varCurrency: (VCurrency: Currency);
varDate: (VDate: TDateTime);
varOleStr: (VOleStr: PWideChar);
varDispatch: (VDispatch: Pointer);
varError: (VError: HRESULT);
varBoolean: (VBoolean: WordBool);
varUnknown: (VUnknown: Pointer);
varShortInt: (VShortInt: ShortInt);
varByte: (VByte: Byte);
varWord: (VWord: Word);
varLongWord: (VLongWord: LongWord);
varInt64: (VInt64: Int64);
varString: (VString: Pointer);
varAny: (VAny: Pointer);
varArray: (VArray: PVarArray);
varByRef: (VPointer: Pointer);
);
1: (VLongs: array[0..2] of LongInt);
);
2: (VWords: array [0..6] of Word);
3: (VBytes: array [0..13] of Byte);
);
1: (RawData: array [0..3] of LongInt);
end;

Qui possiamo notare l'uso delle parti variant annidate, ma il concetto rimane sempre lo stesso, cioè ogni opzione di tipologia di record si sovrappone alle altre della stessa lista.
Quindi al primo livello avremo che il record potrà essere di tipo RawData, di dimensione 4 double word, oppure di tipo Vtype con la successiva parte variabile.
Quello che occorre capire è che la dimensione di un record di tipo TVarData sarà sempre di 16 byte ovvero 4 double word, dopo di che sarà possibile accedere alle sue parti in più modi, come avevamo visto per le coordinate di un rettangolo.

Variant

Questa tipologia di record è alla base del tipo di dati variant , usati per la flessibilità con cui con la stessa variabile è possibile gestire più tipi di dati:

var
v:variant;
begin
  v:=123;
writeln(v);
v:='ciao';
writeln(v);
end;

questo tipo di dati viene gestito automaticamente dal compilatore, che al momento di assegnarne o leggerne il contenuto chiama delle procedure di gestione dei variant a seconda dei casi, ma per conservare il contenuto dei variant farà sempre riferimento ad un record di tipo TVarRec.
La facilità e la versatilità dei variant però potrebbe portare ad errori di programmazione, dato che molti casi non sono o non possono essere gestiti in automatico dal compilatore.
I tipi variant sono molto importati soprattutto perchè ci permettono di interagire con la tecnologia COM, a questo proposito spesso questa tipologia di dato è sostituita dagli OleVariant che possono essere classifocati come una sottospecie di variant. La differenza tra variant e OleVariant è solo che questi ultimi gestiscono solo tipologie di dati compatibili con gli standard Ole, quindi se per esempio possiamo assegnare una variabile string ad un variant , questo non sarà possibile farlo con un OleVariant, perchè la string verrebbe convertita in una widestring.

Se può sembrare semplice usare i variant con numeri e stringhe non lo è per gli array. Infatti gli array variant in Delphi sono stati pensati per incapsulare i safearray COM, ed usano delle funzioni particolari (VarArrayxxx) per essere gestiti. Questi array sono chiamati safe perchè sono autodefiniti, nel senso che al loro interno hanno tutte le informazioni necessarie che li riguardano, ovvero dimensione, tipologia degli elementi etc..

procedure test;
var
  Arr: Variant;
  I: Integer;
begin
  { Crea un array variant di 10 elementi, da 0 a 9. 
  L'array contiene degli elementi di tipo "integer". }
  Arr := VarArrayCreate([0, 9], varInteger);
 
  { Aumenta la lunghezza dell'array. }
  VarArrayRedim(Arr, 49);
 
  { rileva l'attuale dimensione dell'array. }
  MessageDlg('Variant array has ' + IntToStr(VarArrayDimCount(Arr)) + ' dimensions',
      mtInformation, [mbOK], 0);
 
  { Percorre gli elementi dell'array dal limite inferiore a quello superiore. }
  for I := VarArrayLowBound(Arr, 1) to VarArrayHighBound(Arr, 1) do
  begin
    { Inserisce il valore I nell'elemento con indice I. }
    VarArrayPut(Arr, I, [I]);
  end;
 
  { Legge gli elementi dell' array. }
  for I := VarArrayLowBound(Arr, 1) to VarArrayHighBound(Arr, 1) do
  begin
    if VarArrayGet(Arr, [I]) <> I then
      MessageDlg('Error! Invalid element found at current position!', mtError, [mbOK], 0);
  end;
 
  { Cancella l'array variant. }
  VarClear(Arr);
end;


Nell'esempio precedente abbiamo usato alcune delle funzioni principali nella gestione dei variant array, come ad esempio VarArrayCreate , dove impostiamo la lunghezza e il tipo di dati contenuti nell'array. Naturalmente non è l'unico modo per creare un array, infatti nell'esempio successivo verrà creato un variant array tramite la conversione da un array normale:

function CalculateMean(const LArray: Variant): Double;
var
  LLen: Integer;
  LSum: Double;
  PElem: PVariant;
  I: Integer;
begin
  LSum := 0;
 
  { Blocca l'array e fornisce un puntatore alla posizione di memoria che conserva il primo elemento. }
  PElem := VarArrayLock(LArray);
 
  { ricava la lunghezza dell' array }
  LLen := VarArrayHighBound(LArray, 1) - VarArrayLowBound(LArray, 1) + 1;
 
  { Calcola la somma di tutti gli elementi. }
  for I := 0 to LLen - 1 do
  begin
    { PElem^ è l'elemento attuale dell'array. }
    LSum := LSum + PElem^;
    { Per passare all'elemento successivo viene incrementato il puntatore PElem. }
    Inc(PElem);
  end;
 
  { Sblocca l'array. }
  VarArrayUnlock(LArray);
 
  { Calcola la media. }
  Result := LSum / LLen;
end;
 
var
  X: Double;
  L: Integer;
  LValues: array of Variant;
  LVarArray: Variant; 
begin
  { Inizia un loop che finisce con la condizione (X = 0). }
  while True do begin
    { Legge un nuovo elemento dalla console. }
    Write('Enter a double value (0 for stop): '); Readln(X);
 
    { Verifica la condizione di fine loop }
    if X = 0 then
      Break;
 
    { Calcola la lunghezza dell'array di variant. }
    L := Length(LValues);
    { Incrementa la lunghezza dell'array dinamico. }
    SetLength(LValues, L + 1);
    { Aggiunge un elemento all'array. }
    LValues[L] := X;
  end;
 
  { Crea un variant array dall'array dinamico di variant. }
  LVarArray := VarArrayOf(LValues);
  { Stampa a video la media calcolata. }
  Writeln('Mean of the given values is: ', CalculateMean(LVarArray));
end.
 

Per ulterori informazioni sui variant ed il loro uso vi consiglio di leggere la documentazione ufficiale, dove sono elencate tutte le varie funzioni per gestirli.






Ultimo aggiornamento ( Venerdì 19 Novembre 2010 10:47 )  
Loading

Login




Chiudi