Auch die zweite mORMot Vorstellung hat als Ausgangspunkt diese
Frage im Forum. Die erstellte Beispiel-Anwendung kann als Startpunkt eigener Erkundungen dienen. Ziel ist es, so viel Funktionalität wie möglich mit nur wenigen Zeilen Quelltext zu präsentieren. Es geht hier um Konzepte, nicht um eine fertige Copy-Paste Lösung. Für das Beispiel wird
mORMot2 verwendet.
Im Anhang befindet sich der Sourcecode und das ausführbare Programm.
Disclaimer: Der Sourcecode ist weder getestet noch optimiert. Er sollte mit Delphi ab Version 10.2 funktionieren. Die Benutzung der zur Verfügung gestellten Materialien erfolgt auf eigene Gefahr.
Die erstellte Klasse
TImageResourceFile verwaltet Bilder und verwendet als Speicher eine ZIP-Datei. Die Einträge können optional AES verschlüsselt abgelegt werden. Im zweiten Teil wird die Anbindung einer Fortschrittsanzeige mit Hilfe eines Mediators vorgestellt. Zum Ende zeige ich einige praktische Funktionen und die Ergebnisse einer kleinen Write-Speed-Challenge zwischen Delphi- und mORMot-ZIP, mit einem für mich unerwarteten Ergebnis.
Das Interface der Klasse
TImageResourceFile ist sehr einfach gehalten und umfasst nur wenige Funktionen:
Delphi-Quellcode:
TImageResourceFile = class(TObject)
private
FPassword: RawUtf8;
FFileName: TFileName;
FIsWritable: Boolean;
protected
function LoadStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8): Boolean;
procedure SaveStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8; pmIsCompressed: Boolean = True);
public
constructor Create(const pmcFileName: TFileName; const pmcPassword: RawUtf8);
destructor Destroy; override;
function LoadImage(pmImage: TImage; const pmcImageName: String): Boolean;
procedure SaveImage(pmImage: TImage; const pmcImageName: String); overload;
procedure SaveImage(pmImageData: TCustomMemoryStream; const pmcImageName: String); overload;
end;
Mit dem Beispiel Quelltext bekommt man:
- Eine ZIP-Datei als Datenablage, die Einträge optional AES verschlüsselt speichern kann.
- Anbindung einer Fortschrittsanzeige mit Hilfe eines Mediators.
- Vorstellung einer Funktion, die eine Datei AES ver- und entschlüsseln kann.
- Beschleunigung beim Speichern und Lesen der Grafik-Formate JPEG, PNG, GIF, TIFF.
Beschleunigung beim Speichern und Lesen von Bildern
Um den letzten Punkt gleich abzuhandeln, hierzu ein paar Benchmark-Werte aus der Anwendung ermittelt mit einem 2MB großen PNG-Bild:
Funktion / Unit Name | Vcl.Imaging.pngimage | mormot.ui.gdiplus |
SaveImage() | 900 ms | 25 ms |
LoadImage() | 70 ms | 10 ms |
Die Zahlen sprechen für sich. Bei einem Problem mit der Geschwindigkeit Folgendes probieren:
Unit Vcl.Imaging.pngimage entfernen, danach
Unit mormot.ui.gdiplus einbinden und die Funktion
RegisterSynPictures aufrufen.
ZIP-Datei Implementierung
Durch die Einbindung der
Unit mormot.core.zip erhält man Support für ZIP und GZ. Lesen und Schreiben einer ZIP-Datei werden immer getrennt behandelt. Für das Lesen ist die Klasse
TZipRead vorhanden und für das Schreiben die Klasse
TZipWrite. Die Quelle kann eine Datei oder ein Stream sein. TZipRead kann auch direkt aus einer Ressource, eingebettet in die ausführbare Datei, lesen. Zusätzlich beinhaltet die
Unit einige nützliche Funktionen. Beispielhaft sind:
- ZipTest: Diese Funktion entpackt den Inhalt einer ZIP-Datei und überprüft den jeweiligen CRC.
- CompressZLib: Ver- oder entschlüsselt den Inhalt unter Verwendung des ZIP Algorithmus.
- CompressGZip: Ver- oder entschlüsselt den Inhalt unter Verwendung des GZ Algorithmus.
- FileAppend: Fügt einer ausführbaren Datei eine ZIP-Datei hinzu.
Fortschrittsanzeige mit Mediator
Mediatoren sind eine elegante Möglichkeit Funktionalität zu kapseln und die einfache und sichere Anwendung zu erreichen. Auch wenn das folgende Beispiel
unterkomplex ist, ist der Gewinn erkennbar. Hinter der Funktion könnte sich auch ein semi-modaler Dialog mit Fortschrittsanzeige verbergen. Und wer weiß immer, welche Felder genau PProgressInfo enthält? Mit der einfachen Anbindung an einen Mediator erledigt sich das von selbst. Ich wähle nur aus, der Rest wird zuverlässig erledigt. Einmal entwickelt und immer wieder genutzt.
Delphi-Quellcode:
type
TZipProgressBarGuiHelper = class(TCustomZipProgressGuiHelper)
private
FProgressBar: TProgressBar;
protected
procedure DoOnInfoProgress(pmSender: TObject; pmInfo: PProgressInfo); override;
public
constructor Create(pmProgressBar: TProgressBar); reintroduce;
procedure Prepare(pmZip: TZipAbstract); override;
end;
procedure TZipProgressBarGuiHelper.DoOnInfoProgress(pmSender: TObject; pmInfo: PProgressInfo);
begin
if pmInfo.CurrentSize = 0 then
begin
FProgressBar.Min := 0;
FProgressBar.Max := 100;
FProgressBar.Position := 0
end
else if pmInfo.CurrentSize >= pmInfo.ExpectedSize then
FProgressBar.Position := 0
else
FProgressBar.Position := pmInfo.Percent;
end;
procedure TZipProgressBarGuiHelper.Prepare(pmZip: TZipAbstract);
begin
if pmZip = Nil then Exit; //=>
pmZip.ReportDelay := 50;
pmZip.OnProgress := DoOnInfoProgress;
end;
Die Umsetzung der Anbindung ist ein einfacher Aufruf der Funktion Prepare():
Delphi-Quellcode:
var zipWrite: TZipWrite := TZipWrite.Create(MakePath([Executable.ProgramFilePath, 'TestFile.zip']));
try
FProgressHelper.Prepare(zipWrite);
zipWrite.AddDeflated(fileName);
finally
zipWrite.Free;
end;
**Der Begriff Mediator wird hier allgemeiner verwendet, als es der Kontext des gleichnamigen Design-Patterns vorgibt.
Nützliche kryptografische Funktionen
Das Verzeichnis
crypt beherbergt die kryptografischen Klassen und Funktionen. Insgesamt umfasst es ca. 28K Zeilen Quelltext. Dreh- und Angelpunkt ist die
Unit mormot.crypt.core. Ein kurzer Auszug aus der Beschreibung gibt einen Einblick in den Inhalt:
- AES Encoding/Decoding with optimized asm and AES-NI/CLMUL support
- AES-256 Cryptographic Pseudorandom Number Generator (CSPRNG)
- SHA-2 SHA-3 Secure Hashing
- HMAC Authentication over SHA and CRC32C
- PBKDF2 Key Derivation over SHA2 and SHA3
Eine dieser vielen Funktionen ist
AesPkcs7File. Mit ihr lässt sich eine Datei sicher ver- oder entschlüsseln. Einmal verschlüsseln und zurück sieht wie folgt aus:
Delphi-Quellcode:
AesPkcs7File('FileName.txt', 'FileName.dat', True, 'TopSecretPassword');
AesPkcs7File('FileName.dat', 'FileName2.txt', False, 'TopSecretPassword');
if HashFileMd5('FileName.txt') = HashFileMd5('FileName2.txt') then
ShowMessage('Everything is fine!');
Die Funktion
CryptDataForCurrentUser kann Daten mittels AES-256-CFB und einem nur dem aktuellen Benutzer bekannten Geheimnis schützen. Für dessen Erstellung wird Windows DPAPI verwendet und die Datei im lokalen AppData-Ordner des Benutzers gespeichert. Zusätzlich kann in der Anwendung noch ein anwendungsspezifischer AppSecret-Wert angegeben werden.
Delphi-Quellcode:
var
plainText, secretText: RawByteString;
begin
plainText := 'This is a secret text that only I should know.';
secretText := CryptDataForCurrentUser(plainText, 'AppSecret', True);
plainText := CryptDataForCurrentUser(secretText, 'AppSecret', False);
try
ShowMessage(Utf8ToString(plainText));
finally
FillZero(plainText);
end;
end;
Mit der Kombination aus den Klassen
TAesPkcs7Reader/
TAesPkcs7Writer und den Funktionen
RecordLoadJson/
RecordSaveJson ein Lizenzhandling erstellen:
Delphi-Quellcode:
type
TLicenseData = record
CustomerNum: Integer;
CustomerName: RawUtf8;
CustomerAddress: RawUtf8;
LicenceDate: TDate;
ProductName: RawUtf8;
ProductVersion: record
Major: Integer;
Minor: Integer;
end;
end;
var
licData: TLicenseData;
begin
licData.CustomerNum := 1;
licData.CustomerName := 'Thomas';
licData.LicenceDate := Date;
licData.ProductName := 'Delphi';
licData.ProductVersion.Major := 11;
licData.ProductVersion.Minor := 1;
var tmpStream: TMemoryStream := TMemoryStream.Create;
try
var licStream: TRawByteStringStream := TRawByteStringStream.Create(RecordSaveJson(licData, TypeInfo(TLicenseData)));
try
var aesWriter: TAesPkcs7Writer := TAesPkcs7Writer.Create(tmpStream, 'TopSecretPassword');
try
StreamCopyUntilEnd(licStream, aesWriter);
aesWriter.Finish;
finally
aesWriter.Free;
end;
finally
licStream.Free;
end;
tmpStream.SaveToFile('LicenseData.lic');
finally
tmpStream.Free;
end;
Die JSON Deserialisierung ist
fehlertolerant! Das heißt, man kann dem Record
TLicenseData neue Felder hinzufügen und die Funktion
RecordLoadJson lädt auch vorherige Versionen zuverlässig. Damit muss man keine Streaming-Versionen mehr pflegen.
Write-Speed-Challenge
Ein paar Benchmark-Werte mit Hilfe der Beispiel-Anwendung ermittelt:
Bibliothek - Klasse / Dateigröße | 1 MB | 10 MB | 100 MB |
mORMot TZipWrite | 35 ms | 349 ms | 2,8 s |
Delphi TZipFile | 38 ms | 362 ms | 2,9 s |
Die ZIP Implementierung von mORMot ist nur unwesentlich schneller als die von Delphi. Da hat Embarcadero seine Hausaufgabe gemacht. mORMot unterstützt zwar Delphi 7 bis 11.1, dazu noch den FP-Compiler, aber auch für Delphi gilt: Besser spät als nie.
Zusammenfassung
mORMot ist gut dokumentiert. Die Hilfe umfasst mehr als 2500 Seiten. Davon enthalten die ersten ca. 650 Seiten einen sehr lesenswerten allgemeinen Teil, der Rest ist
API Dokumentation. mORMot muss nicht in der
IDE installierten werden! Es reicht aus, die entsprechenden Bibliothekspfade einzufügen. Es stehen viele Beispiele und ein freundliches
Forum zur Verfügung. Wenn mehr Interesse an mORMot besteht, kann ich auch andere Teile in ähnlicher Weise kurz vorstellen.
Bis bald...
Thomas