Here is a small library which manages writing into and reading out a byte array. It uses TValue arrays to get and put data, its my first time using them. Its crudely written, and poorly optimized but I tried to make the best solution in the 3 hrs I was given to build and debug it. Right now it only supports Integer, String, TGUID, TBytes and TDateTime as data inputs.
I want your thoughts on it, any areas that may be improved with know how of yours. And for the raging ones, I am not trying to be faster than delphi!
Library
unit AgBuffer;
interface
uses System.SysUtils, System.Rtti, Windows;
type
{$SCOPEDENUMS ON}
TAgData = ( Int, Str, GUID, Bytes, Date );
TAgBuffer = class ( TObject )
private
fSize : Int64;
procedure CheckGrowNecessity ( const AElementSize: Int64 );
procedure EmbedInteger ( const AInteger: Integer ); inline;
procedure EmbedString ( const AString: String ); inline;
procedure EmbedGUID ( const AGUID: TGUID ); inline;
procedure EmbedBytes ( const ABytes : TBytes ); inline;
procedure EmbedDateTime ( const ADateTime: TDateTime ); inline;
function ExtractInteger: Integer; inline;
function ExtractString: String; inline;
function ExtractGUID: TGUID; inline;
function ExtractBytes: TBytes; inline;
function ExtractDateTime: TDateTime; inline;
public
Buffer : TBytes;
InitialSize : Int64;
Position : Int64;
Values : TArray <TValue>;
constructor Create ( const ABuffer: TBytes = nil );
procedure Reinitialize ( const ABuffer: TBytes );
procedure Add ( const AData: Array of TValue );
procedure Extract ( const ADataTypes: Array of TAgData );
procedure Clear;
property Size : int64 read fSize;
end;
implementation
constructor TAgBuffer.Create ( const ABuffer: TBytes = nil );
begin
if Assigned ( ABuffer ) then
begin
Buffer := ABuffer;
fSize := Length ( ABuffer );
InitialSize := fSize;
end;
end;
procedure TAgBuffer.Reinitialize ( const ABuffer: TBytes );
begin
Buffer := ABuffer;
fSize := Length ( Buffer );
InitialSize := fSize;
Position := 0;
SetLength ( Values, 0 );
end;
procedure TAgBuffer.CheckGrowNecessity ( const AElementSize: Int64 );
begin
fSize := fSize + AElementSize;
if fSize > InitialSize then
begin
InitialSize := InitialSize + 512;
SetLength ( Buffer, InitialSize );
end;
end;
procedure TAgBuffer.EmbedInteger ( const AInteger: Integer );
begin
CheckGrowNecessity ( 4 );
Move ( AInteger, Buffer [Position], 4 );
Position := Position + 4;
end;
procedure TAgBuffer.EmbedString ( const AString: String );
var
StringLength : DWORD;
StringSize : Int64;
begin
StringLength := Length ( AString );
StringSize := StringLength * 2;
CheckGrowNecessity ( StringSize + 4 );
Move ( StringLength, Buffer [Position], 4 );
Move ( AString [1], Buffer [Position + 4], StringSize );
Position := Position + StringSize + 4;
end;
procedure TAgBuffer.EmbedGUID ( const AGUID: TGUID );
begin
CheckGrowNecessity ( 16 );
Move ( AGUID, Buffer [Position], 16 );
Position := Position + 16;
end;
procedure TAgBuffer.EmbedBytes ( const ABytes : TBytes );
var
StringLength : DWORD;
begin
StringLength := Length ( ABytes );
CheckGrowNecessity ( StringLength + 4 );
Move ( StringLength, Buffer [Position], 4 );
Move ( ABytes [0], Buffer [Position + 4], StringLength );
Position := Position + StringLength + 4;
end;
procedure TAgBuffer.EmbedDateTime ( const ADateTime: TDateTime );
begin
CheckGrowNecessity ( 8 );
Move ( ADateTime, Buffer [Position], 8 );
Position := Position + 8;
end;
procedure TAgBuffer.Add ( const AData: Array of TValue );
var
I : DWORD;
TypeChar : Char;
begin
if Length ( AData ) <> 0 then
begin
// Initialization
Position := fSize;
InitialSize := fSize + 1024;
SetLength ( Buffer, InitialSize );
for I := 0 to Length ( AData ) - 1 do
begin
// Preparation
TypeChar := Char ( AData [I].TypeInfo.Name [2] );
// Type determination
if TypeChar = 'n' then
EmbedInteger ( AData [I].AsInteger )
else if TypeChar = 't' then
EmbedString ( AData [I].AsString )
else if TypeChar = 'G' then
EmbedGUID ( AData [I].AsType <TGUID> )
else if TypeChar = 'A' then
EmbedBytes ( AData [I].AsType <TBytes> )
else if TypeChar = 'D' then
EmbedDateTime ( AData [I].AsType <TDateTime> );
end;
// Freeing left over space
SetLength ( Buffer, fSize );
end;
end;
function TAgBuffer.ExtractInteger: Integer;
begin
Move ( Buffer [Position], Result, 4 );
Position := Position + 4;
end;
function TAgBuffer.ExtractString: String;
var
StrLength : DWORD;
StrSize : int64;
begin
Move ( Buffer [Position], StrLength, 4 );
SetLength ( Result, StrLength );
StrSize := StrLength * 2;
Move ( Buffer [Position + 4], Result [1], StrSize );
Position := Position + 4 + StrSize;
end;
function TAgBuffer.ExtractGUID: TGUID;
begin
Move ( Buffer [Position], Result, 16 );
Position := Position + 16;
end;
function TAgBuffer.ExtractBytes: TBytes;
var
ArrayLength: DWORD;
begin
Move ( Buffer [Position], ArrayLength, 4 );
SetLength ( Result, ArrayLength );
Move ( Buffer [Position + 4], Result [0], ArrayLength );
Position := Position + 4 + ArrayLength;
end;
function TAgBuffer.ExtractDateTime: TDateTime;
begin
Move ( Buffer [Position], Result, 8 );
Position := Position + 8;
end;
procedure TAgBuffer.Extract ( const ADataTypes: Array of TAgData );
var
I : DWORD;
begin
if Length ( ADataTypes ) <> 0 then
begin
SetLength ( Values, Length ( ADataTypes ));
for I := 0 to Length ( ADataTypes ) - 1 do
case ADataTypes [I] of
TAgData.Int : Values [I] := ExtractInteger;
TAgData.Str : Values [I] := ExtractString;
TAgData.GUID : Values [I] := TValue.From <TGUID> ( ExtractGUID );
TAgData.Bytes : Values [I] := TValue.From <TBytes> ( ExtractBytes );
TAgData.Date : Values [I] := TValue.From <TDateTime> ( ExtractDateTime );
end;
end;
end;
procedure TAgBuffer.Clear;
begin
SetLength ( Buffer, 0 );
SetLength ( Values, 0 );
fSize := 0;
InitialSize := 0;
Position := 0;
end;
end.
Example
To use it, I am giving an example which embeds a header of sorts of 2 ints and 2 guids:-
uses
AgBuffer, System.SysUtils, System.Rtti;
---
var
Packet1, Packet2 : TAgBuffer;
Int1, Int2, Int3, Int4 : Integer;
GUID1, GUID2, GUID3, GUID4 : TGUID;
---
// Embedding Data
Int1 := 4;
Int2 := 56000;
CreateGUID ( GUID1 );
CreateGUID ( GUID2 );
Packet1 := TAgBuffer.Create;
Packet1.Add
( [Int1, Int2,
TValue.From <TGUID> ( GUID1 ), TValue.From <TGUID> ( GUID2 )] );
// Packet1.Buffer can now be used for any transmission
// Extracting Data
Packet2 := TAgBuffer.Create ( Packet1.Buffer );
Packet2.Extract ( [AGB_INT, AGB_INT, AGB_GUID, AGB_GUID] );
Int3 := Packet.Values [0].AsInteger;
Int4 := Packet.Values [1].AsInteger;
GUID3 := Packet.Values [2].AsType <TGUID>;
GUID4 := Packet.Values [3].AsType <TGUID>;