Se non siete riusciti a reperire l'informazione che cercavate nei nostri articoli o sul nostro forum vi consiglio di visitare il
Forum ufficiale di Lazarus in lingua inglese.
Fino ad ora tutto il codice che abbiamo scritto era in un blocco solo, ovvero abbiamo scritto tutte le righe una dietro l'altra, ma solo perché gli esempi visti erano semplici, ma capita e anche sovente di dover scrivere pezzi di codice che serve riutilizzare. Le motivazioni possono essere molteplici, ma sovente conviene utilizzare le procedure e le funzioni piuttosto che scrivere un monoblocco di codice. Ipotizziamo di dover invertire il valore di due variabili di nome Valore1 e Valore2 e di dover effettuare questa operazione sovente all'interno del nostro programma, per poter fare questa operazione abbiamo bisogno anche della variabile di nome Valore3. Vediamo ora come andrebbe scritto il codice che effettua l'inversione dei valori:
Valore3:=Valore1;
Valore1:=Valore2;
Valore2:=Valore3;
Bene ora immaginiamo di doverlo scrivere 10, 100, 1000 volte all'interno del codice, possiamo facilmente capire che il codice in questione diverrà molto lungo, quindi difficile da gestire in un secondo momento, e in più il programma diverrebbe molto grosso senza tenere conto che la variabile di nome Valore3 rimarrebbe allocata in memoria per tutta l'esecuzione del programma. Mentre ora immaginiamo di immettere questo codice scritto in una procedura, le migliorie sono diverse, ad esempio quando la procedura ha finito la sua vita la variabile Valore3 viene deallocata, il programma occupa meno spazio e se bisogna fare manutenzione il codice diventa più leggibile.
Bene fino ad ora abbiamo parlato di procedure e funzioni come se fossero la stessa cosa, ma fondamentalmente c'è una differenza tra le due cose, ovvero che le funzioni restituiscono un valore, mentre le procedure no. Vediamo ora come si dichiara una procedura e come si dichiara una funzione. Procedura:
procedure NomeProcedura();
begin
.
.
.
end;
Funzione:
function NomeFunzione(): tiporestituito;
begin
.
.
NomeFunzione:=valorerestituito;
end;
La prima cosa che bisogna notare è che sia le funzioni che le procedure hanno un nome che le contraddistingue, per seconda cosa bisogna notare che entrambe terminano con le parentesi e con il punto e virgola. Ma le due cose più importanti da notare sono che la funzione per restituire un valore deve specificare di che tipo è, e per effettuare la restituzione bisogna attribuire il valore da restituire al nome della funzione stessa.
Un altra cosa da apprendere quando si tratta l'argomento procedure e funzioni è il passaggio di parametri. Per passaggio di parametri si intende il passare delle informazioni alla procedura o alla funzione. Vediamo un esempio concreto:
procedure StampaParametrizzato(valore1:integer; valore2:integer; valore3:integer);
begin
if (Valore1<=5) then
writeln(valore2)
else
writeln(valore3);
end;
Bene come si può notare, all'interno delle parentesi tonde subito dopo il nome della procedura, è presente la dichiarazione di tre variabili intervallate da dei punti e virgola. In questo modo definisco quanti parametri passare alla funzione/procedura specificando il tipo del dato che gli passo.
I parametri possono essere passati in due modi, per valore o per referenza. La differenza tra le due è che se sono passati per valore, il valore stesso non può essere modificato all'esterno della procedura, se invece il parametro è passato per referenza allora il valore se viene modificato verrà modificato sempre nel programma. In pratica i due tipi si differenziano grazie alla parola riservata var all'interno delle parentesi tonde. Vediamo un esempio di passaggio di parametri per valore:
procedure Inverti( Valore1: integer; Valore2: integer);
e ora lo stesso ma per referenza:
procedure Inverti(var Valore1: integer; var Valore2: integer);
Ora per capire bene cosa si intende con passaggio di parametri per valore andate a compilare e ad eseguire il seguente sorgente:
program project2;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, CustApp
{ you can add units after this };
type
{ TMyApplication }
TMyApplication = class(TCustomApplication)
protected
procedure DoRun; override;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
procedure WriteHelp; virtual;
end;
procedure Inverti(Valore1: integer; Valore2: integer);
var
appoggio: integer;
begin
appoggio:=Valore1;
Valore1:=Valore2;
Valore2:=appoggio;
writeln('All''interno della procedura valore1 vale: ', valore1,' e valore2 vale: ', valore2);
end;
{ TMyApplication }
procedure TMyApplication.DoRun;
var
ErrorMsg: String;
valore1: integer;
valore2: integer;
begin
// quick check parameters
ErrorMsg:=CheckOptions('h','help');
if ErrorMsg<>'' then begin
ShowException(Exception.Create(ErrorMsg));
Terminate;
Exit;
end;
// parse parameters
if HasOption('h','help') then begin
WriteHelp;
Terminate;
Exit;
end;
{ add your program here }
valore1:=5;
valore2:=7;
Inverti(valore1,valore2);
writeln('Dopo l''esecuzione della procedura valore1 vale: ', valore1,' e valore2 vale: ', valore2);
// stop program loop
Terminate;
end;
constructor TMyApplication.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
StopOnException:=True;
end;
destructor TMyApplication.Destroy;
begin
inherited Destroy;
end;
procedure TMyApplication.WriteHelp;
begin
{ add your help code here }
writeln('Usage: ',ExeName,' -h');
end;
var
Application: TMyApplication;
{$IFDEF WINDOWS}{$R project2.rc}{$ENDIF}
begin
Application:=TMyApplication.Create(nil);
Application.Title:='My Application';
Application.Run;
Application.Free;
end.
Per capire come funziona il passaggio per referenza basta sostituire la riga:
procedure Inverti(Valore1: integer; Valore2: integer);
con la riga:
procedure Inverti(var Valore1: integer; var Valore2: integer);
e ricompilare. Una volta eseguite entrambe le versioni è evidente la differenza tra le due.
N.B.: se per caso si immettesse la riga procedure Inverti(Valore1: integer; var Valore2: integer); allora Valore1 sarebbe passato per valore e Valore2 per referenza.
Le strutture sono dei tipi di variabile composte da variabile semplici, ipotizziamo di voler descrivere un animale con una variabile, dovremmo stabilire che tipo di animale è, quanti anni di vita ha, e tutta una serie di altri fattori. Bene con i mezzi fino ad ora acquisiti, dovremmo fare tutto ciò con due o più variabili, dipende dal numero di caratteristiche che si vogliono far avere all'animale; con una struttura è possibile fare ciò con una variabile. Vediamo ora come si dichiara una struttura:
type
nomedadareallastruttura=record
caratteristica1: tipovariabile;
caratteristica2: tipovariabile;
.
.
.
caratteristicaN: tipovariabile;
end;
Ora che sappiamo come si dichiara una struttura dobbiamo anche sapere che si posiziona subito dopo altre eventuali strutture già dichiarate o dopo l'inclusione delle librerie, ovvero dopo la sezione USES. Vediamo ora l'esempio di cui parlavamo poco sopra, ovvero della variabile che identifica l'animale, io ho usato solo 2 caratteristiche, razza e anni. Le parti importanti sono sottolineate.
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, CustApp
{ you can add units after this };
type
{ TMyApplication }
TMyApplication = class(TCustomApplication)
protected
procedure DoRun; override;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
procedure WriteHelp; virtual;
end;
type
animale=record
razza: string[50];
anni: byte;
end;
{ TMyApplication }
procedure TMyApplication.DoRun;
var
MioCane: animale;
MioGatto: animale;
ErrorMsg: String;
begin
// quick check parameters
ErrorMsg:=CheckOptions('h','help');
if ErrorMsg<>'' then begin
ShowException(Exception.Create(ErrorMsg));
Halt;
end;
// parse parameters
if HasOption('h','help') then begin
WriteHelp;
Halt;
end;
{ add your program here }
MioCane.razza:='cane';
MioCane.anni:=3;
MioGatto.razza:='gatto';
MioGatto.anni:=2;
writeln('Animale1: ', MioCane.razza, ' di anni ', MioCane.anni);
writeln('Animale2: ', MioGatto.razza, ' di anni ', MioGatto.anni);
// stop program loop
Terminate;
end;
constructor TMyApplication.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
StopOnException:=True;
end;
destructor TMyApplication.Destroy;
begin
inherited Destroy;
end;
procedure TMyApplication.WriteHelp;
begin
{ add your help code here }
writeln('Usage: ',ExeName,' -h');
end;
var
Application: TMyApplication;
{$IFDEF WINDOWS}{$R project1.rc}{$ENDIF}
begin
Application:=TMyApplication.Create(nil);
Application.Title:='My Application';
Application.Run;
Application.Free;
end.
La prima cosa che dovrebbe saltare all'occhio come già detto prima è il posizionamento della dichiarazione della struttura, ovvero la prima parte sottolineata. La seconda cosa da notare è che una volta definita la struttura dobbiamo dichiarare delle variabili con tipo di variabile la struttura stessa, ovvero la seconda parte sottolineata. La terza cosa da capire è che per accedere alle singole caratteristiche della struttura si usa il punto, sia per valorizzarlo che per leggerlo, esattamente come nel terzo blocco sottolineato nell'ultimo esempio.
Vediamo ora un altro tipo di dato non molto utilizzato, ma che può risultare comodo in alcune occasioni: gli insiemi, che in Pascal sono molto simili al concetto di insieme in matematica; dato un insieme A di oggetti di un insieme di dati B, ogni oggetto di B appartiene o non appartiene ad A.
Vediamo come dichiarare gli insiemi con un esempio:
var giorni: SET OF integer;
Possiamo altrimenti scrivere:
type giorniLavorativi: SET OF integer;
var giorniRip : giorniLavorativi;
Ma il risultato è lo stesso. Per inizializzare un insieme usiamo la sintassi:
giorniLavorativi := [1,3..7, 10..25, 29];
giorniRip := []; {insieme vuoto}
Si noti l'uso di un range nell'assegnazione. La sintassi generale è quindi per un insieme [el1, el2, el3..el7, …] dove per elN si intende elemento numero.
Le operazioni fattibili con gli insiemi sono le stesse in matematica: ad esempio, "+" corrisponde all'unione, "-" alla differenza e "*" all'intersezione.
[1, 3, 4] + [3, 9..11] = [1, 3, 4, 9..11]
[5, 7, 19, 22] - [22] = [5, 7, 19]
[5, 7, 19, 22] - [23] = [5, 7, 19, 22]
[11..22] * [15..56] = [15..22]
[11..22] + [15..56] = [11..56]
[5, 7, 19, 22] * [22] = [22]
[5, 7, 19, 22] * [23] = []
Per verificare se un elemento è in un insieme, usiamo l'operatore IN:
if 1 in giorniLavorativi then
È possibile anche confrontare gli insiemi:
1. =: restituisce true se i due insiemi sono uguali;
2. <>: restituisce true se i due insiemi sono diversi;
3. <=: restituisce true se il primo insieme è sottoinsieme del secondo (il secondo contiene il primo);
4. >=: contrario di <=
Potete trovare questo utile e potente strumento qui: http://code.sd/turbobird
Sorgenti : https://github.com/motaz/turbobird
PeaZip è un gestore di archivi Open Source ed è flessibile, portabile, sicuro e libero.
http://www.peazip.org/
Cosa succede quando avete finito un software e volete distribuirlo, previa vostra autorizzazione? Questo How To vi viene incontro e vi spiega come io ho affrontato il problema.
La soluzione a tale problema è composta di due parti, un codice php posizionato nel sito dal quale distribuisco il software e dal quale faccio fare l'attivazione del prodotto.
Quella che segue è la classe PHP che mi genera il mio codice di attivazione nel formato: XXXX-XXXX-XXXX-XXXX
[code]
/*
Classe realizzata da Sammarco Francesco
Es:
$OggettoSN = new Serial_Number;
$CodSer = $OggettoSN->PKV_MakeKey($IdCod);
*/
class Serial_Number
{
// Costruttore
function __construct()
{
}
// Distruttore
public function __destruct()
{
//non faccio niente
}
private function Shr($a, $b)
{
return ($a/(2^$b));
}
private function PKV_GetKeyByte($Seed,$a,$b,$c)
{
$a = $a % 25;
$b = $b % 3;
if( $a % 2 == 0 )
{
$result = ( $Seed >> $a ) ^ ( ( $Seed >> $b ) | $c );
} else
{
$result = ( $Seed >> $a ) ^ ( ( $Seed >> $b ) & $c );
}
return $result & 0x000000FF;
}
private function IntToHex($Variabile,$Num)
{
$result=dechex($Variabile);
$k=$Num-strlen($result);
for($i=0;$i<$k;$i++)
{
$result="0" . $result;
}
return strtoupper($result);
}
public function PKV_MakeKey($Seed)
{
$KeyBytes[0]=$this->PKV_GetKeyByte($Seed, 24, 3, 200);
$KeyBytes[1]=$this->PKV_GetKeyByte($Seed, 10, 0, 56);
$KeyBytes[2]=$this->PKV_GetKeyByte($Seed, 1, 2, 91);
$KeyBytes[3]=$this->PKV_GetKeyByte($Seed, 7, 1, 100);
$result=$this->IntToHex($Seed,8);
for ($i=0;$i<=3;$i++)
{
$result=$result . $this->IntToHex($KeyBytes[$i],2);
}
$valorecs=$this->PKV_GetChecksum($result);
$result=$result . $valorecs;
$app="";
$j=1;
for ($i=0;$i<strlen($result);$i++)
{
if (($j == 4)&&($i<(strlen($result)-1)))
{
$app=$app . $result[$i] . "-";
$j=1;
}else
{
$app=$app . $result[$i];
$j++;
}
}
return $app;
}
private function PKV_GetChecksum($s)
{
$left = 0x0056;
$right = 0x00AF;
if(strlen($s) > 0 )
{
for( $i = 0 ; $i < strlen( $s ) ; $i++ )
{
$right = $right + Ord($s[$i]);
if($right > 0x00FF )
{
$right=$right-0x00FF;
}
$left=$left+$right;
if($left > 0x00FF )
{
$left=$left-0x00FF;
}
}
}
$sum = ( $left << 8 ) + $right;
$result = $this->IntToHex($sum, 4 );
return $result;
}
}
?>
[/code]
Usare tale classe è molto molto semplice basta usare il codice (sempre in php):
$OggettoSN = new Serial_Number;
$CodSer = $OggettoSN->PKV_MakeKey($IdCod);
Dove $IdCod equivale all'inizializzatore da cui mi ricavo il mio codice di attivazione e $CodSer è il nostro tanto agognato codice di serie.
Per rendere la nostra classe unica e diversa da questo codice di esempio basta cambiare i parametri numerici nella funzione PKV_GetKeyByte
$KeyBytes[0]=$this->PKV_GetKeyByte($Seed, 24, 3, 200);
$KeyBytes[1]=$this->PKV_GetKeyByte($Seed, 10, 0, 56);
$KeyBytes[2]=$this->PKV_GetKeyByte($Seed, 1, 2, 91);
$KeyBytes[3]=$this->PKV_GetKeyByte($Seed, 7, 1, 100);
Ora questa parte scritta in PHP crea un codice che comunico (tramite mail automatica) all'interessato.
Ora toccherà al programma (scritto in Lazarus) elaborare questa informazione, e io ho scritto una libreria per fare questo:
[code]
unit MySerialNumber;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
const
KEY_GOOD = 0;
KEY_INVALID = 1;
KEY_BLACKLISTED = 2;
KEY_PHONY = 3;
type
SerialNumber=object
private
public
function PKV_GetKeyByte(const Seed : Int64; a, b, c : Byte) : Byte;
function PKV_CheckKey(const S : String) : Integer;
function PKV_CheckKeyChecksum(const Key : String) : Boolean;
function PKV_MakeKey(const Seed : Int64) : String;
function PKV_GetChecksum(const s : String) : String;
end;
var
BL : array[0..0] of String = ('11111111');
implementation
{ serial number }
function StartsStr(str: string; inizio: string): boolean;
var
Esci: boolean;
ret: boolean;
i: integer;
len1: integer;
len2: integer;
begin
len1:=length(str);
len2:=length(inizio);
ret:=false;
if len1>=len2 then
begin
i:=0;
ret:=TRUE;
if len1=0 then
Esci:=TRUE
else
Esci:=FALSE;
while (Esci=False) do
begin
if str[i]<>inizio[i] then
begin
Esci:=TRUE;
ret:=FALSE;
end;
Inc(i);
if i>len1 then
Esci:=TRUE;
end;
end;
StartsStr:=ret;
end;
function SerialNumber.PKV_GetKeyByte(const Seed : Int64; a, b, c : Byte) : Byte;
var
risultato: byte;
begin
a := a mod 25;
b := b mod 3;
if a mod 2 = 0 then
risultato := ((Seed shr a) and $000000FF) xor ((Seed shr b) or c)
else
risultato := ((Seed shr a) and $000000FF) xor ((Seed shr b) and c);
PKV_GetKeyByte:=risultato;
end;
function SerialNumber.PKV_GetChecksum(const s : String) : String;
var
left, right, sum : Word;
i : Integer;
app: string;
begin
left := $0056;
right := $00AF;
if Length(s) > 0 then
for i := 1 to Length(s) do
begin
right := right + Byte(s[i]);
//app:= app + 'r: ' + s[i] + '(' + IntToStr(Byte(s[i])) + ')';
//right := right + Byte(s[i]);
if right > $00FF then
Dec(right, $00FF);
Inc(left, right);
if left > $00FF then
Dec(left, $00FF);
end;
sum := (left shl 8) + right;
result := IntToHex(sum, 4);
end;
function SerialNumber.PKV_MakeKey(const Seed : Int64) : String;
var
KeyBytes : array[0..3] of Byte;
i : Integer;
app: string;
begin
// Fill KeyBytes with values derived from Seed.
// The parameters used here must be extactly the same
// as the ones used in the PKV_CheckKey function.
// A real key system should use more than four bytes.
KeyBytes[0] := PKV_GetKeyByte(Seed, 24, 3, 200);
KeyBytes[1] := PKV_GetKeyByte(Seed, 10, 0, 56);
KeyBytes[2] := PKV_GetKeyByte(Seed, 1, 2, 91);
KeyBytes[3] := PKV_GetKeyByte(Seed, 7, 1, 100);
// the key string begins with a hexidecimal string of the seed
result := IntToHex(Seed, 8);
// then is followed by hexidecimal strings of each byte in the key
for i := 0 to 3 do
begin
result := result + IntToHex(KeyBytes[i], 2);
end;
// add checksum to key string
result := result + PKV_GetChecksum(result);
// Add some hyphens to make it easier to type
i := Length(result) - 3;
while (i > 1) do
begin
Insert('-', result, i);
Dec(i, 4);
end;
end;
function SerialNumber.PKV_CheckKeyChecksum(const Key : String) : Boolean;
var
s, c : String;
begin
result := False;
// remove cosmetic hypens and normalize case
s := UpperCase(StringReplace(Key, '-', '', [rfReplaceAll]));
if Length(s) <> 20 then
exit; // Our keys are always 20 characters long
// last four characters are the checksum
c := Copy(s, 17, 4);
SetLength(s, 16);
// compare the supplied checksum against the real checksum for
// the key string.
result := c = PKV_GetChecksum(s);
end;
function SerialNumber.PKV_CheckKey(const S : String) : Integer;
var
Key, kb : String;
Seed : Int64;
i : Integer;
b : Byte;
begin
result := KEY_INVALID;
if not PKV_CheckKeyChecksum(S) then
exit; // bad checksum or wrong number of characters
// remove cosmetic hypens and normalize case
Key := UpperCase(StringReplace(S, '-', '', [rfReplaceAll]));
// test against blacklist
if Length(BL) > 0 then
for i := Low(BL) to High(BL) do
if StartsStr(BL[i], Key) then
begin
result := KEY_BLACKLISTED;
exit;
end;
// At this point, the key is either valid or forged,
// because a forged key can have a valid checksum.
// We now test the "bytes" of the key to determine if it is
// actually valid.
// When building your release application, use conditional defines
// or comment out most of the byte checks! This is the heart
// of the partial key verification system. By not compiling in
// each check, there is no way for someone to build a keygen that
// will produce valid keys. If an invalid keygen is released, you
// simply change which byte checks are compiled in, and any serial
// number built with the fake keygen no longer works.
// Note that the parameters used for PKV_GetKeyByte calls MUST
// MATCH the values that PKV_MakeKey uses to make the key in the
// first place!
result := KEY_PHONY;
// extract the Seed from the supplied key string
if not TryStrToInt64('$' + Copy(Key, 1, 8), Seed) then
exit;
{$IFDEF KEY00}
kb := Copy(Key, 9, 2);
b := PKV_GetKeyByte(Seed, 24, 3, 200);
if kb <> IntToHex(b, 2) then
exit;
{$ENDIF}
{$IFDEF KEY01}
kb := Copy(Key, 11, 2);
b := PKV_GetKeyByte(Seed, 10, 0, 56);
if kb <> IntToHex(b, 2) then
exit;
{$ENDIF}
{$IFDEF KEY02}
kb := Copy(Key, 13, 2);
b := PKV_GetKeyByte(Seed, 1, 2, 91);
if kb <> IntToHex(b, 2) then
exit;
{$ENDIF}
{$IFDEF KEY03}
kb := Copy(Key, 15, 2);
b := PKV_GetKeyByte(Seed, 7, 1, 100);
if kb <> IntToHex(b, 2) then
exit;
{$ENDIF}
// If we get this far, then it means the key is either good, or was made
// with a keygen derived from "this" release.
result := KEY_GOOD;
end;
end.
[/code]
Quello che è importante è che i valori numerici della funzione PKV_GetKeyByte siano valorizzati in maniera identica con quelli del codice PHP
KeyBytes[0] := PKV_GetKeyByte(Seed, 24, 3, 200);
KeyBytes[1] := PKV_GetKeyByte(Seed, 10, 0, 56);
KeyBytes[2] := PKV_GetKeyByte(Seed, 1, 2, 91);
KeyBytes[3] := PKV_GetKeyByte(Seed, 7, 1, 100);
Per poter sfruttare questa libreria bastano poche linee di codice in free pascal
if length(Codice)=24 then
begin
Valore:=MioOggetto.PKV_CheckKey(Codice);
if Valore=0 then
begin
{CODICE DI ATTIVAZIONE VALIDO}
end;
end;
Dove la variabile Codice è valorizzata con il codice che precedentemente avevamo comunicato al nostro cliente.
Bene questa è la soluzione che avevo usato io in uno dei miei progetti. Se avete altre soluzioni non esitate a comunicarmele e vedrò di pubblicarle. Avverto che pubblicherò solo soluzioni che possono essere multipiattaforma.