Can I somehow compress with Delphi using ZLIB (Deflate with ZLIB headers) and get an array of Bytes?
Right now I am copying from TMemoryStream but it would be nice to not copy back to array (so it's faster overall)
PackedStream := TMemoryStream.Create;
ZLib := TZCompressionStream.Create(PackedStream);
ZLib.WriteBuffer(UnpackedArray[0], UnpackedArrayLen);
ZLib.Free;
PackedArrayLen := PackedStream.Size;
SetLength(PackedArray, PackedArrayLen);
PackedStream.Position := 0;
PackedStream.Read(PackedArray[0], PackedArrayLen);
PackedStream.Free;
Simply use TMemoryStream.Memory as the byte array, just don't free the stream until you are done using the bytes:
PackedStream := TMemoryStream.Create;
try
ZLib := TZCompressionStream.Create(PackedStream);
try
ZLib.WriteBuffer(UnpackedArray[0], UnpackedArrayLen);
finally
ZLib.Free;
end;
// use PackedStream.Memory up to PackedStream.Size bytes as needed...
finally
PackedStream.Free;
end;
Otherwise, you can use TBytesStream instead of TMemoryStream:
PackedStream := TBytesStream.Create;
try
ZLib := TZCompressionStream.Create(PackedStream);
try
ZLib.WriteBuffer(UnpackedArray[0], UnpackedArrayLen);
finally
ZLib.Free;
end;
// use PackedStream.Bytes up to PackedStream.Size bytes as needed...
finally
PackedStream.Free;
end;
Or, if you have a pre-allocated byte array, you can use TCustomMemoryStream giving it a pointer to that array so it will write directly into the array:
type
TMemoryBufferStream = class(TCustomMemoryStream)
public
constructor Create(APtr: Pointer; ASize: NativeInt);
function Write(const Buffer; Count: Longint): Longint; override;
end;
constructor TMemoryBufferStream.Create(APtr: Pointer; ASize: NativeInt);
begin
inherited Create;
SetPointer(APtr, ASize);
end;
function TMemoryBufferStream.Write(const Buffer; Count: Longint): Longint;
var
LAvailable: Int64;
LNumToCopy: Longint;
begin
Result := 0;
LAvailable := Size - Position;
if LAvailable > 0 then
begin
LNumToCopy := Count;
if Int64(LNumToCopy) > LAvailable then
LNumToCopy := Longint(LAvailable);
if LNumToCopy > 0 then
begin
Move(Buffer, (PByte(Memory) + Position)^, LNumToCopy);
Seek(LNumToCopy, soCurrent);
Result := LNumToCopy;
end;
end;
end;
PackedStream := TMemoryBufferStream.Create(SomeBuffer, MaxBufferSize);
try
ZLib := TZCompressionStream.Create(PackedStream);
try
ZLib.WriteBuffer(UnpackedArray[0], UnpackedArrayLen);
finally
ZLib.Free;
end;
// use SomeBuffer up to PackedStream.Size bytes as needed...
finally
PackedStream.Free;
end;
Related
I have this function that generate a checksum of a TStream with BobJenkinsHash:
function GetStreamHashBobJenkins(Stream: TStream): String;
var
Hash: THashBobJenkins;
Readed: Integer;
Buffer: PByte;
BufLen: Integer;
begin
Hash := THashBobJenkins.Create;
BufLen := 16 * 1024;
Buffer := AllocMem(BufLen);
try
Stream.Seek(0, 0);
while Stream.Position < Stream.Size do begin
Readed := Stream.Read(Buffer^, BufLen);
if Readed > 0 then begin
Hash.update(Buffer^, Readed);
end;
end;
Stream.Seek(0, 0);
finally
FreeMem(Buffer)
end;
Result := Hash.HashAsString;
end;
from the docwiki of System.Generics.Defaults.BobJenkinsHash i read:
BobJenkinsHash is used to generate the hash code of a memory block.
The wiki seem talk about a generic "memory block", is possible generate the checksum of a generic TObject?
Data.Cloud.CloudAPI.pas has class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string; that return wrong SHA 256 on some file.
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
LBytes : TBytes;
Hash: THashSHA2;
begin
LBytes := TBytesStream(Content).Bytes;
//Hash bytes
Hash := THashSHA2.Create;
Hash.Update(LBytes);
Result := Hash.HashAsString;
end;
AWS S3 return error:
The provided x-amz-content-sha256 header does not match what was computed
GetStreamToHashSHA256Hex seems produce a different sha256 from amazon:
<ClientComputedContentSHA256>f43ee89e2b7758057bb1f33eb8546d4c2c118f2ab932de89dbd74aabc0651053</ClientComputedContentSHA256>
<S3ComputedContentSHA256>3bbf5f864cc139cf6392b4623bd782a69d16929db713bffaa68035f8a5c3c0ce</S3ComputedContentSHA256>
I have made some tests wit a myfile.zip (600 MB) ...
TIdHashSHA256 an alternative from Indy return the right SHA256 (same of aws s3), eg.:
var
aFileStream: TFileStream;
aHash: TIdHashSHA256;
begin
aFileStream := TFileStream.Create('C:\myfile.zip', fmOpenRead or fmShareDenyWrite);
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStreamAsHex(aFileStream).ToLower;
finally
aFileStream.Free;
aHash.Free;
end;
end;
hash_file() from PHP return the right SHA256 (same of aws s3), eg.:
hash_file('sha256', 'C:\myfile.zip');
but THashSHA2 return a wrong sha256, eg.:
var
LBytes : TBytes;
Hash: THashSHA2;
begin
LBytes := TFile.ReadAllBytes('C:\myfile.zip');
Hash := THashSHA2.Create;
Hash.Update(LBytes);
Result := Hash.HashAsString;
end;
why?
UPDATE
this is my bug fix. Import Data.Cloud.CloudAPI.pas into the project and rewrite these function:
uses IdHash, IdHashSHA, IdSSLOpenSSL;
class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
aHash: TIdHashSHA256;
begin
LoadOpenSSLLibrary;
try
if not(TIdHashSHA256.IsAvailable) then
raise Exception.Create('HashSHA256 Isn''t available!');
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStringAsHex(HashString).ToLower;
finally
aHash.Free;
end;
finally
UnLoadOpenSSLLibrary;
end;
end;
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
aHash: TIdHashSHA256;
begin
LoadOpenSSLLibrary;
try
if not(TIdHashSHA256.IsAvailable) then
raise Exception.Create('HashSHA256 Isn''t available!');
aHash := TIdHashSHA256.Create;
try
Result := aHash.HashStreamAsHex(Content).ToLower;
finally
aHash.Free;
end;
finally
UnLoadOpenSSLLibrary;
end;
end;
UPDATE 2
i have also try to implement the FredS suggestion, it works:
class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
Content: TStringStream;
Hash: THashSHA2;
LBytes: TArray<Byte>;
Buffer: PByte;
BufLen: Integer;
Readed: Integer;
begin
BufLen := 16 * 1024;
Buffer := AllocMem(BufLen);
Hash := THashSHA2.Create;
Content := TStringStream.Create(HashString);
try
while Content.Position < Content.Size do
begin
Readed := Content.Read(Buffer^, BufLen);
if Readed > 0 then
Hash.update(Buffer^, Readed);
end;
finally
Content.Free;
FreeMem(Buffer);
end;
Result := Hash.HashAsString;
end;
class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
LBytes : TBytes;
Hash: THashSHA2;
Buffer: PByte;
BufLen: Integer;
Readed: Integer;
begin
BufLen := 16 * 1024;
Buffer := AllocMem(BufLen);
Hash := THashSHA2.Create;
try
Content.Seek(0, soFromBeginning);
while Content.Position < Content.Size do
begin
Readed := Content.Read(Buffer^, BufLen);
if Readed > 0 then
Hash.update(Buffer^, Readed);
end;
Content.Seek(0, soFromBeginning);
finally
FreeMem(Buffer);
end;
Result := Hash.HashAsString;
end;
I just tested a +1.5 GB file using MS Cyrpto and THashSHA2 on Berlin, they both returned the same hash but MS Crypto like OpenSSL is much faster.
The problem is that the file is too large to hold in TBytes in one chunk.
My record helper has TBytes.MaxLen = $F000; {61440} so you need to use a TFileStream and read the file in chunks into HashSHA2.Update instead.
Update:
As per David Heffernan's comment I retested TBytes.MaxLen and it appears to be only limited by available memory.
Practical Example and Speed comparison between MS Crypto and Delphi HashSha2
Note: Requires Jedi API
program SHA2SpeedTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
JwaWindows, Winapi.Windows, System.SysUtils, System.Classes, System.Diagnostics, System.Hash;
const
SHA256_LEN = 256 div 8;
ChunkSize = $F000;
type
TBytesHelper = record helper for TBytes
public
function BinToHex: string;
end;
function TBytesHelper.BinToHex: string;
var
Len : Integer;
begin
Len := Length(Self);
SetLength(Result, Len * 2));
System.Classes.BinToHex(Self, PChar(Result), Len);
end;
procedure DelphiHash256(const AStream: TStream; out Bytes: TBytes);
var
HashSHA2: THashSHA2;
BytesRead: Integer;
begin
HashSHA2 := THashSHA2.create;
SetLength(Bytes, ChunkSize);
AStream.Position := 0;
repeat
BytesRead := AStream.Read(Bytes, ChunkSize);
if (BytesRead = 0) then Break; // Done
HashSHA2.Update(Bytes, BytesRead);
until False;
Bytes := HashSHA2.HashAsBytes;
end;
function CryptoHash256(const AStream: TStream; out Bytes: TBytes): Boolean;
var
SigLen : Cardinal;
hHash : HCRYPTHASH;
hProv : HCRYPTPROV;
BytesRead: Integer;
begin
hProv := 0; hHash := 0;
Result := False;
If not CryptAcquireContext(hProv, nil, nil, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) then Exit;
try
if not CryptCreateHash(hProv, CALG_SHA_256, 0, 0, hHash) then Exit;
try
SetLength(Bytes, ChunkSize);
AStream.Position := 0;
repeat
BytesRead := AStream.Read(Bytes, ChunkSize);
if (BytesRead = 0) then Break; // Done
if not CryptHashData(hHash, #Bytes[0], BytesRead, 0) then Exit;
until False;
SigLen := SHA256_LEN;
SetLength(Bytes, SigLen);
Result := CryptGetHashParam(hHash, HP_HASHVAL, #Bytes[0], SigLen, 0);
finally
CryptDestroyHash(hHash);
end;
finally
CryptReleaseContext(hProv, 0);
end;
end;
var
Stream: TStream;
Bytes : TBytes;
sw : TStopwatch;
CryptoTicks : int64;
FileName : string;
{* CheckFileName *}
function CheckFileName: boolean;
begin
if (FileName='') then FileName := ParamStr(0);
Result := FileExists(FileName);
if not Result then Writeln('Invalid File name');
end;
begin
repeat
Writeln('Please Enter a valid File name, empty for this Executable');
Readln(FileName);
until CheckFileName;
try
Stream := TFileStream.Create(FileName, fmOpenRead + fmShareDenyNone);
try
WriteLn('Crypto - Calculating Checksum');
sw.Start;
if not CryptoHash256(Stream, Bytes) then raise Exception.Create('Something Happened :)');
sw.Stop;
Writeln(Bytes.BinToHex);
WriteLn('Elapsed: ' + sw.Elapsed.ToString);
CryptoTicks := sw.ElapsedTicks;
WriteLn('Delphi - Calculating Checksum');
sw.Reset; sw.Start;
DelphiHash256(Stream, Bytes);
sw.Stop;
Writeln(Bytes.BinToHex);
WriteLn('Elapsed: ' + sw.Elapsed.ToString);
Writeln(Format('MS Crypto is %d%% faster', [(sw.ElapsedTicks-CryptoTicks) * 100 div CryptoTicks]));
finally
Stream.Free;
end;
Writeln('Hit <Enter> to exit');
Readln;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
end.
I've got a minor issue with decompressing using the ZLib unit in Delphi
unit uZCompression;
interface
uses
uCompression;
type
TZZipCompression = class(TInterfacedObject, ICompression)
public
function DoCompression(aContent: TArray<Byte>): TArray<Byte>;
function DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
function GetWindowsBits: Integer; virtual;
end;
TZGZipCompression = class(TZZipCompression)
function GetWindowsBits: Integer; override;
end;
implementation
uses
System.ZLib, System.Classes, uMxKxUtils;
{ TZCompression }
function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
LContentStream, LOutputStream: TMemoryStream;
LCompressedStream: TZCompressionStream;
begin
LContentStream := ByteArrayToStream(aContent);
LOutputStream := TMemoryStream.Create;
LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
LCompressedStream.CopyFrom(LContentStream, LContentStream.Size);
LCompressedStream.Free;
Result := StreamToByteArray(LOutputStream);
LOutputStream.Free;
LContentStream.Free;
end;
function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
LContentStream, LOutputStream: TMemoryStream;
LDecompressedStream: TZDecompressionStream;
begin
LContentStream := ByteArrayToStream(aContent);
LOutputStream := TMemoryStream.Create;
LDecompressedStream := TZDecompressionStream.Create(LContentStream);
LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size);
LDecompressedStream.Free;
Result := StreamToByteArray(LOutputStream);
LOutputStream.Free;
LContentStream.Free;
end;
function TZZipCompression.GetWindowsBits: Integer;
begin
Result := 15;
end;
{ TZGZipCompression }
function TZGZipCompression.GetWindowsBits: Integer;
begin
Result := inherited;
Result := Result + 16;
end;
end.
This is my unit which is driven by an interface (which you don't need to know about), and data is passed in and out through a TArray variables.
I've coded it to be able to do 2 types of compression, standard zip and gzip which is determined by the windowsbits passed in to the functions.
Here are a couple of other functions being used to convert the TArray to TMemoryStream
function ByteArrayToStream(aContent: TArray<Byte>): TMemoryStream;
begin
Result := TMemoryStream.Create;
Result.Write(aContent, length(aContent)*SizeOf(aContent[0]));
Result.Position := 0;
end;
function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
var
LStreamPos: Int64;
begin
if Assigned(aStream) then
begin
LStreamPos := aStream.Position;
aStream.Position := 0;
SetLength(Result, aStream.Size);
aStream.Read(Result, aStream.Size);
aStream.Position := LStreamPos;
end
else
SetLength(Result, 0);
end;
Now I can compress and decompress to .zip using the TZZipCompression class perfectly fine (it doesn't open up as a zip file, but it does decompress back to the original file which I can open and edit).
I can also compress to .gz using the TZGZipCompression class fine as well (interestingly I can open this gzip file perfectly well).
My issue however is that it won't decompress back from the .gz file and throws and error as soon as it hits
LOutputStream.CopyFrom(LDecompressedStream, LDecompressedStream.Size)
Funnily enough the Help file example has it as below
LOutputStream.CopyFrom(LDecompressedStream, 0)
But this doesn't work either.
Can anyone spot the issue?
Your conversion functions between TArray<Byte> and TMemoryStream are wrong, as you are not accessing the array content correctly. TArray is a dynamic array. When calling TMemoryStream.Write() and TMemoryStream.Read(), you are passing the memory address of the TArray itself, not the memory address of the data that the TArray points at. You need to reference the TArray to get the correct memory address, eg:
function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
Result := TMemoryStream.Create;
try
if Length(aContent) > 0 then
Result.WriteBuffer(aContent[0], Length(aContent));
Result.Position := 0;
except
Result.Free;
raise;
end;
end;
function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
if Assigned(aStream) then
begin
SetLength(Result, aStream.Size);
if Length(Result) > 0 then
Move(aStream.Memory^, Result[0], aStream.Size);
end
else
SetLength(Result, 0);
end;
Alternatively:
function ByteArrayToStream(const aContent: TArray<Byte>): TMemoryStream;
begin
Result := TMemoryStream.Create;
try
Result.WriteBuffer(PByte(aContent)^, Length(aContent));
Result.Position := 0;
except
Result.Free;
raise;
end;
end;
function StreamToByteArray(aStream: TMemoryStream): TArray<Byte>;
begin
if Assigned(aStream) then
begin
SetLength(Result, aStream.Size);
Move(aStream.Memory^, PByte(Result)^, aStream.Size);
end
else
SetLength(Result, 0);
end;
That being said, you don't need to waste memory making copies of the array data using TMemoryStream. You can use TBytesStream instead (since dynamic arrays are reference counted), eg:
function TZZipCompression.DoCompression(aContent: TArray<Byte>): TArray<Byte>;
var
LContentStream, LOutputStream: TBytesStream;
LCompressedStream: TZCompressionStream;
begin
LContentStream := TBytesStream.Create(aContent);
try
LOutputStream := TBytesStream.Create(nil);
try
LCompressedStream := TZCompressionStream.Create(LOutputStream, zcDefault, GetWindowsBits);
try
LCompressedStream.CopyFrom(LContentStream, 0);
finally
LCompressedStream.Free;
end;
Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
finally
LOutputStream.Free;
end;
finally
LContentStream.Free;
end;
end;
function TZZipCompression.DoDecompression(aContent: TArray<Byte>): TArray<Byte>;
var
LContentStream, LOutputStream: TBytesStream;
LDecompressedStream: TZDecompressionStream;
begin
LContentStream := TBytesStream.Create(aContent);
try
LOutputStream := TBytesStream.Create(nil);
try
LDecompressedStream := TZDecompressionStream.Create(LContentStream, GetWindowsBits);
try
LOutputStream.CopyFrom(LDecompressedStream, 0);
finally
LDecompressedStream.Free;
end;
Result := Copy(LOutputStream.Bytes, 0, LOutputStream.Size);
finally
LOutputStream.Free;
end;
finally
LContentStream.Free;
end;
end;
I need to save a TObjectList<TStrings> (or <TStringList>) in a TStream and then retrive it.
To be clear, how to apply SaveToStream and LoadFromStream to a TObjectList?
Try something like this:
procedure SaveListOfStringsToStream(List: TObjectList<TStrings>; Stream: TStream);
var
Count, I: Integer;
MStrm: TMemoryStream;
Size: Int64;
begin
Count := List.Count;
Stream.WriteBuffer(Count, SizeOf(Count));
if Count = 0 then Exit;
MStrm := TMemoryStream.Create;
try
for I := 0 to Count-1 do
begin
List[I].SaveToStream(MStrm);
Size := MStrm.Size;
Stream.WriteBuffer(Size, SizeOf(Size));
Stream.CopyFrom(MStrm, 0);
MStrm.Clear;
end;
finally
MStrm.Free;
end;
end;
procedure LoadListOfStringsFromStream(List: TObjectList<TStrings>; Stream: TStream);
var
Count, I: Integer;
MStrm: TMemoryStream;
Size: Int64;
SList: TStringList;
begin
Stream.ReadBuffer(Count, SizeOf(Count));
if Count <= 0 then Exit;
MStrm := TMemoryStream.Create;
try
for I := 0 to Count-1 do
begin
Stream.ReadBuffer(Size, SizeOf(Size));
SList := TStringList.Create;
try
if Size > 0 then
begin
MStrm.CopyFrom(Stream, Size);
MStrm.Position := 0;
SList.LoadFromStream(MStrm);
MStrm.Clear;
end;
List.Add(SList);
except
SList.Free;
raise;
end;
end;
finally
MStrm.Free;
end;
end;
Alternatively:
procedure SaveListOfStringsToStream(List: TObjectList<TStrings>; Stream: TStream);
var
LCount, SCount, Len, I, J: Integer;
SList: TStrings;
S: UTF8String;
begin
LCount := List.Count;
Stream.WriteBuffer(LCount, SizeOf(LCount));
if LCount = 0 then Exit;
for I := 0 to LCount-1 do
begin
SList := List[I];
SCount := SList.Count;
Stream.WriteBuffer(SCount, SizeOf(SCount));
for J := 0 to SCount-1 do
begin
S := UTF8String(SList[J]);
// or, if using Delphi 2007 or earlier:
// S := UTF8Encode(SList[J]);
Len := Length(S);
Stream.WriteBuffer(Len, SizeOf(Len));
Stream.WriteBuffer(PAnsiChar(S)^, Len * SizeOf(AnsiChar));
end;
end;
end;
procedure LoadListOfStringsFromStream(List: TObjectList<TStrings>; Stream: TStream);
var
LCount, SCount, Len, I, J: Integer;
SList: TStrings;
S: UTF8String;
begin
Stream.ReadBuffer(LCount, SizeOf(LCount));
for I := 0 to LCount-1 do
begin
Stream.ReadBuffer(SCount, SizeOf(SCount));
SList := TStringList.Create;
try
for J := 0 to SCount-1 do
begin
Stream.ReadBuffer(Len, SizeOf(Len));
SetLength(S, Len);
Stream.ReadBuffer(PAnsiChar(S)^, Len * SizeOf(AnsiChar));
SList.Add(String(S));
// or, if using Delphi 2007 or earlier:
// SList.Add(UTF8Decode(S));
end;
List.Add(SList);
except
SList.Free;
raise;
end;
end;
end;
What's in your list?
It depends on what type of objects you have in your objectlist.
You loop over the list and save each item in turn.
However the objects inside your list need to have a SaveToStream method.
For reasons unknown SaveToStream is not a method of TPersistent, instead it is implemented independently in different classes.
Test for stream support
If the VCL were built with interfaces in mind, in newer versions has been solved with the IStreamPersist interface.
If all your stuff in the list descents from a base class that has streaming built-in (e.g. TComponent) then there is no problem and you can just use TComponent.SaveToStream.
type
TStreamableClass = TStrings; //just to show that this does not depend on TStrings.
procedure SaveToStream(List: TObjectList; Stream: TStream);
var
i: integer;
begin
for i:= 0 to List.Count -1 do begin
if List[i] is TStreamableClass then begin
TStreamableClass(List[i]).SaveToStream(Stream);
end;
end; {for i}
end;
Add stream support
If you have items in your list that do not derive from a common streamable ancestor then you'll have to have multiple if list[i] is TX tests in your loop.
If the object does not have a SaveToStream method, but you have enough knowledge of the class to implement it yourself, then you have twothree options.
A: implement a class helper that adds SaveToStream to that class or B: add a descendent class that implements that option.
If these are your own objects, then see option C: below.
type
TObjectXStreamable = class(TObjectX)
public
procedure SaveToStream(Stream: TStream); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
end;
procedure SaveToStream(List: TObjectList; Stream: TStream);
...
if List[i] is TObjectX then TObjectXStreamable(List[i]).SaveToStream(Stream);
...
Note that this approach fails if TObjectX has subclasses with additional data. The added streaming will not know about this extra data.
Option C: implement System.Classes.IStreamPersist
type
IStreamPersist = interface
['<GUID>']
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
//enhance your streamable objects like so:
TInterfaceBaseObject = TInterfacedObject //or TSingletonImplementation
TMyObject = class(TInterfaceBaseObject, IStreamPersist)
procedure SaveToStream(Stream: TStream); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
See: Bypassing (disabling) Delphi's reference counting for interfaces
You test the IStreamPersist support using the supports call.
if Supports(List[i], IStreamPersist) then (List[i] as IStreamPersist).SaveToStream(Stream);
If you have a newer version of Delphi consider using a generic TObjectList, that way you can limit your list to: MyList: TObjectList<TComponent>;
Now you can just call MyList[i].SaveToStream, because Delphi knows that the list only contains (descendents of) TComponent.
You will need to create your own routine to do this: One for saving, the other for loading.
For saving, loop through the list, convert each pointer of the list into is hexadecimal (decimal, octal) then add a separator character like ','; When done write the string contain to the stream.
For loading, loop through the list, search for the first separator character, extract the value, convert it back as a pointer then add it to the list.
Procedure ObjListToStream(objList: TObjectList; aStream: TStream);
var
str: String;
iCnt: Integer;
Begin
if not assigned(aStream) then exit; {or raise exception}
for iCnt := 0 to objList.Count - 1 do
begin
str := str + IntToStr(Integer(objList.Items[iCnt])) + ',';
end;
aStream.Write(str[1], Length(str));
End;
Procedure StreamToObjList(objList: TObjectList; aList: String);
var
str: String;
iCnt: Integer;
iStart, iStop: Integer;
Begin
try
if not assigned(aStream) then exit; {or raise exception}
iStart := 0;
Repeat
iStop := Pos(',', aList, iStart);
if iStop > 0 then
begin
objList.Add(StrToInt(Copy(sList, iStart, iStop - iStart)));
iStart := iStop + 1;
end;
Until iStop = 0;
except
{something want wrong}
end;
End;
I haven't test it and wrote it from memory. But it should point you in the right direction.
I have to encode an array of bytes to a base64 string (and decode this string) on an old Delphi 2007.
How could I do?
Further Informations:
I've tried synapse (As suggested here Binary to Base64 (Delphi)).
Indy ships with Delphi, and has TIdEncoderMIME and TIdDecoderMIME classes for handling base64. For example:
uses
..., IdCoder, IdCoderMIME;
var
Bytes: TIdBytes;
Base64String: String;
begin
//...
Bytes := ...; // array of bytes
//...
Base64String := TIdEncoderMIME.EncodeBytes(Bytes);
//...
Bytes := TIdDecoderMIME.DecodeBytes(Base64String);
//...
end;
There are also methods for encoding/decoding String and TStream data as well.
Update: alternatively, if your version does not have the class methods shown above:
// TBytesStream was added in D2009, so define it manually for D2007
uses
..., IdCoder, IdCoderMIME
{$IF RTLVersion < 20)
, RTLConsts
{$IFEND}
;
{$IF RTLVersion < 20)
type
TBytesStream = class(TMemoryStream)
private
FBytes: TBytes;
protected
function Realloc(var NewCapacity: Longint): Pointer; override;
public
constructor Create(const ABytes: TBytes); overload;
property Bytes: TBytes read FBytes;
end;
constructor TBytesStream.Create(const ABytes: TBytes);
begin
inherited Create;
FBytes := ABytes;
SetPointer(Pointer(FBytes), Length(FBytes));
FCapacity := FSize;
end;
const
MemoryDelta = $2000; // Must be a power of 2
function TBytesStream.Realloc(var NewCapacity: Integer): Pointer;
begin
if (NewCapacity > 0) and (NewCapacity <> FSize) then
NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);
Result := Pointer(FBytes);
if NewCapacity <> FCapacity then
begin
SetLength(FBytes, NewCapacity);
Result := Pointer(FBytes);
if NewCapacity = 0 then
Exit;
if Result = nil then raise EStreamError.CreateRes(#SMemoryStreamError);
end;
end;
{$IFEND}
var
Bytes: TBytes;
BStrm: TBytesStream;
Encoder: TIdEncoderMIME;
Decoder: TIdDecoderMIME;
Base64String: String;
begin
//...
Bytes := ...; // array of bytes
//...
BStrm := TBytesStream.Create(Bytes);
try
Encoder := TIdEncoderMIME.Create;
try
Base64String := Encoder.Encode(BStrm);
finally
Encoder.Free;
end;
finally
BStrm.Free;
end;
//...
BStrm := TBytesStream.Create;
try
Decoder := TIdDecoderMIME.Create;
try
Decoder.DecodeBegin(BStrm);
Decoder.Decode(Base64String);
Decoder.DecodeEnd;
finally
Decoder.Free;
end;
Bytes := BStrm.Bytes;
finally
BStrm.Free;
end;
//...
end;
Contrary to what you state in the question, the EncdDecd unit is included in Delphi 2007. You can simply use that.
David Heffernan responded very well!
Add in your uses the class "EncdDecd", it will have the procedures:
function DecodeString (const Input: string): string;
function DecodeBase64 (const Input: string): TBytes;
Testing with https://www.base64encode.org/
The String "Working" in both Delphi and the site resulted in: "V29ya2luZw =="
ShowMessage ('Working =' + EncodeString ('Working'));
ShowMessage ('Working =' + DecodeString ('V29ya2luZw =='));
Here goes EncodeToBase64:
uses
classes, sysutils;
function EncodeToBase64(var Buffer: TBytes): Longint;
const
EncodingTable: PChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var
WriteBuf: array[0..3] of Byte;
Buf: array[0..2] of Byte;
Dest: TMemoryStream;
i, j, Count: Integer;
begin
Result := 0;
Count := Length(Buffer);
j := Count div 3;
if j > 0 then
begin
Dest:= TMemoryStream.Create();
try
Dest.Position := 0;
for i := 0 to j - 1 do
begin
Move(Buffer[i * 3], Buf[0], 3);
WriteBuf[0] := Ord(EncodingTable[Buf[0] shr 2]);
WriteBuf[1] := Ord(EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)]);
WriteBuf[2] := Ord(EncodingTable[(Buf[1] and 15) shl 2 or (Buf[2] shr 6)]);
WriteBuf[3] := Ord(EncodingTable[Buf[2] and 63]);
Dest.Write(WriteBuf, 4);
Inc(Result, 4);
Dec(Count, 3);
end;
if Count in [1, 2] then
begin
Move(Buffer[i * 3], Buf[0], Count);
WriteBuf[0] := Ord(EncodingTable[Buf[0] shr 2]);
WriteBuf[1] := Ord(EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)]);
if Count = 1 then
WriteBuf[2] := Ord('=')
else
WriteBuf[2] := Ord(EncodingTable[(Buf[1] and 15) shl 2 or (Buf[2] shr 6)]);
WriteBuf[3] := Ord('=');
Dest.Write(WriteBuf, 4);
Inc(Result, 4);
Dec(Count, Count);
end;
if Result > 0 then
begin
SetLength(Buffer, Result);
Dest.Position := 0;
Dest.Read(Buffer[0], Result);
end;
finally
Dest.Free;
end;
end;
end;
And this should work without any special units or components.
For OLDER versions of Delphi (before of Delphi XE7), use:
uses
Soap.EncdDecd
procedure DecodeFile(const Base64: AnsiString; const FileName: string);
var
BStream: TBytesStream;
begin
BStream := TBytesStream.Create(DecodeBase64(Base64));
try
BStream.SaveToFile(Filename);
finally
BStream.Free;
end;
end;
For NEWS versions of Delphi, use:
uses
System.NetEncoding;
procedure DecodeFile(const Base64: String; const FileName: string);
var
BStream: TBytesStream;
begin
BStream:= TBytesStream.Create(TNetEncoding.Base64.DecodeStringToBytes(Base64));
try
BStream.SaveToFile(Filename);
finally
BStream.Free;
end;
end;