Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Netzwerkprotokoll zum Übertragen von Variant Streams (https://www.delphipraxis.net/160248-netzwerkprotokoll-zum-uebertragen-von-variant-streams.html)

Zacherl 4. Mai 2011 13:45

Delphi-Version: 2010

Netzwerkprotokoll zum Übertragen von Variant Streams
 
Hallo zusammen,

nun da ich das Problem mit TVarRec / Variant gelöst habe, presentiere ich hier mal die vorläufige Version des Protokolls. Die Unit besteht aus zwei Klassen:
Der Encoder dient zum Erstellen des Pakets. Über die Methode Append() können Daten angehangen werden.
Der Decoder kann Datensätze anhand ihrer Indizes aus dem fertigen Paket auslesen. Die Daten werden als Variant zurückgegeben.

Praktisch könnte man also sagen, es handelt sich um eine Art Stream für Variants.

:arrow: Der Code sieht etwas umständlich aus. Meine Frage daher, ob jemand spontan noch Verbesserungsvorschläge hat. Insbesondere auch zur Performance.
:arrow: Achso, vielleicht noch zum generellen Aufbau des Protokolls. Vor jedem Datensatz wird ein Byte geschrieben, welches den Typ der folgenden Daten enthält. Bei Strings folgt diesem Typ Identifier zusätzlich noch ein 4 Byte Cardinal Wert, welcher die Länge des Strings beinhaltet. Danach folgen die eigentlichen Daten.

Delphi-Quellcode:
unit dxVariantStream;

interface

uses
  Windows;

type
  TdxVariantEncoder = class(TObject)
  private
    FData: Pointer;
    FSize: Int64;
  protected
    function CalculateDataSize(V: Variant): Cardinal;
    function WriteValue(Offset: Cardinal; V: Variant): Cardinal;
  public
    procedure Append(Value: Variant); overload;
    procedure Append(Values: array of Variant); overload;
    constructor Create;
    destructor Destroy; override;
    property Data: Pointer read FData;
    property Size: Int64 read FSize;
  end;

  TdxVariantDecoder = class(TObject)
  private
    FIndexMap: array of Cardinal;
    FItemCount: Integer;
    FData: Pointer;
  protected
    function GetVariantItem(I: Integer): Variant;
  public
    constructor Create(Data: Pointer; Size: Int64);
    destructor Destroy; override;
    property Items[Index: Integer]: Variant read GetVariantItem; default;
    property ItemCount: Integer read FItemCount;
  end;

implementation

uses
  dxException;

type
  ECommandParserException = class(Exception);

{ TCommandEncoder }

procedure TdxVariantEncoder.Append(Value: Variant);
var
  Offset: Cardinal;
begin
  Offset := FSize;
  FSize := FSize + CalculateDataSize(Value);
  if Assigned(FData) then
  begin
    ReallocMem(FData, FSize);
  end else
  begin
    GetMem(FData, FSize);
  end;
  WriteValue(Offset, Value);
end;

procedure TdxVariantEncoder.Append(Values: array of Variant);
var
  BufferSize, Offset: Cardinal;
  I: Integer;
begin
  BufferSize := 0;
  for I := Low(Values) to High(Values) do
  begin
    Inc(BufferSize, CalculateDataSize(Values[I]));
  end;
  Offset := FSize;
  FSize := FSize + BufferSize;
  if Assigned(FData) then
  begin
    ReallocMem(FData, FSize);
  end else
  begin
    GetMem(FData, FSize);
  end;
  for I := Low(Values) to High(Values) do
  begin
    Inc(Offset, WriteValue(Offset, Values[I]));
  end;
end;

constructor TdxVariantEncoder.Create;
begin
  inherited Create;
  FSize := 0;
end;

destructor TdxVariantEncoder.Destroy;
begin
  if Assigned(FData) then
  begin
    FreeMem(FData);
  end;
  inherited;
end;

function TdxVariantEncoder.WriteValue(Offset: Cardinal; V: Variant): Cardinal;
var
  VarData: TVarData;
begin
  Result := 0;
  VarData := TVarData(V);
  case VarData.VType of
    varSmallInt:
      begin
        PByte(Cardinal(FData) + Offset)^ := $0;
        PSmallInt(Cardinal(FData) + Offset + 1)^ := VarData.VSmallInt;
        Result := 1 + SizeOf(SmallInt);
      end;
    varInteger:
      begin
        PByte(Cardinal(FData) + Offset)^ := $1;
        PInteger(Cardinal(FData) + Offset + 1)^ := VarData.VInteger;
        Result := 1 + SizeOf(Integer);
      end;
    varSingle:
      begin
        PByte(Cardinal(FData) + Offset)^ := $2;
        PSingle(Cardinal(FData) + Offset + 1)^ := VarData.VSingle;
        Result := 1 + SizeOf(Single);
      end;
    varDouble:
      begin
        PByte(Cardinal(FData) + Offset)^ := $3;
        PDouble(Cardinal(FData) + Offset + 1)^ := VarData.VDouble;
        Result := 1 + SizeOf(Double);
      end;
    varCurrency:
      begin
        PByte(Cardinal(FData) + Offset)^ := $4;
        PCurrency(Cardinal(FData) + Offset + 1)^ := VarData.VCurrency;
        Result := 1 + SizeOf(Currency);
      end;
    varDate:
      begin
        PByte(Cardinal(FData) + Offset)^ := $5;
        PDateTime(Cardinal(FData) + Offset + 1)^ := VarData.VDate;
        Result := 1 + SizeOf(TDateTime);
      end;
    varBoolean:
      begin
        PByte(Cardinal(FData) + Offset)^ := $6;
        PWordBool(Cardinal(FData) + Offset + 1)^ := VarData.VBoolean;
        Result := 1 + SizeOf(WordBool);
      end;
    varShortInt:
      begin
        PByte(Cardinal(FData) + Offset)^ := $7;
        PShortInt(Cardinal(FData) + Offset + 1)^ := VarData.VShortInt;
        Result := 1 + SizeOf(ShortInt);
      end;
    varByte:
      begin
        PByte(Cardinal(FData) + Offset)^ := $8;
        PByte(Cardinal(FData) + Offset + 1)^ := VarData.VByte;
        Result := 1 + SizeOf(Byte);
      end;
    varWord:
      begin
        PByte(Cardinal(FData) + Offset)^ := $9;
        PWord(Cardinal(FData) + Offset + 1)^ := VarData.VWord;
        Result := 1 + SizeOf(Word);
      end;
    varLongWord:
      begin
        PByte(Cardinal(FData) + Offset)^ := $A;
        PLongWord(Cardinal(FData) + Offset + 1)^ := VarData.VLongWord;
        Result := 1 + SizeOf(LongWord);
      end;
    varInt64:
      begin
        PByte(Cardinal(FData) + Offset)^ := $B;
        PInt64(Cardinal(FData) + Offset + 1)^ := VarData.VInt64;
        Result := 1 + SizeOf(Int64);
      end;
    varUInt64:
      begin
        PByte(Cardinal(FData) + Offset)^ := $C;
        PUInt64(Cardinal(FData) + Offset + 1)^ := VarData.VUInt64;
        Result := 1 + SizeOf(UInt64);
      end;
    varString:
      begin
        PByte(Cardinal(FData) + Offset)^ := $D;
        Result := Length(AnsiString(VarData.VString)) * SizeOf(AnsiChar);
        PCardinal(Cardinal(FData) + Offset + 1)^ := Result;
        CopyMemory(Pointer(Cardinal(FData) + Offset + 5), VarData.VString,
          Result);
        Inc(Result, 5);
      end;
    varUString:
      begin
        PByte(Cardinal(FData) + Offset)^ := $E;
        Result := Length(AnsiString(VarData.VUString)) * SizeOf(WideChar);
        PCardinal(Cardinal(FData) + Offset + 1)^ := Result;
        CopyMemory(Pointer(Cardinal(FData) + Offset + 5), VarData.VUString,
          Result);
        Inc(Result, 5);
      end;
  end;
end;

function TdxVariantEncoder.CalculateDataSize(V: Variant): Cardinal;
var
  VarData: TVarData;
begin
  Result := 0;
  VarData := TVarData(V);
  case VarData.VType of
    varSmallInt: Result := 1 + SizeOf(SmallInt);
    varInteger: Result := 1 + SizeOf(Integer);
    varSingle:  Result := 1 + SizeOf(Single);
    varDouble:  Result := 1 + SizeOf(Double);
    varCurrency: Result := 1 + SizeOf(Currency);
    varDate:    Result := 1 + SizeOf(TDateTime);
    varBoolean: Result := 1 + SizeOf(WordBool);
    varShortInt: Result := 1 + SizeOf(ShortInt);
    varByte:    Result := 1 + SizeOf(Byte);
    varWord:    Result := 1 + SizeOf(Word);
    varLongWord: Result := 1 + SizeOf(LongWord);
    varInt64:   Result := 1 + SizeOf(Int64);
    varUInt64:  Result := 1 + SizeOf(UInt64);
    varString:  Result :=
      5 + Length(AnsiString(VarData.VString)) * SizeOf(AnsiChar);
    varUString: Result :=
      5 + Length(AnsiString(VarData.VUString)) * SizeOf(WideChar);
  end;
end;

{ TCommandDecoder }

constructor TdxVariantDecoder.Create(Data: Pointer; Size: Int64);
var
  Offset: Cardinal;
begin
  inherited Create;
  FData := Data;
  FItemCount := 0;
  Offset := 0;
  while (Offset < Size) do
  begin
    Inc(FItemCount);
    SetLength(FIndexMap, FItemCount);
    FIndexMap[FItemCount - 1] := Offset;
    case PByte(Cardinal(FData) + Offset)^ of
      $0: Inc(Offset, 1 + SizeOf(SmallInt));
      $1: Inc(Offset, 1 + SizeOf(Integer));
      $2: Inc(Offset, 1 + SizeOf(Single));
      $3: Inc(Offset, 1 + SizeOf(Double));
      $4: Inc(Offset, 1 + SizeOf(Currency));
      $5: Inc(Offset, 1 + SizeOf(TDateTime));
      $6: Inc(Offset, 1 + SizeOf(WordBool));
      $7: Inc(Offset, 1 + SizeOf(ShortInt));
      $8: Inc(Offset, 1 + SizeOf(Byte));
      $9: Inc(Offset, 1 + SizeOf(Word));
      $A: Inc(Offset, 1 + SizeOf(LongWord));
      $B: Inc(Offset, 1 + SizeOf(Int64));
      $C: Inc(Offset, 1 + SizeOf(UInt64));
      $D: Inc(Offset, 5 + PCardinal(DWord(FData) + Offset + 1)^);
      $E: Inc(Offset, 5 + PCardinal(DWord(FData) + Offset + 1)^);
    end;
  end;
end;

destructor TdxVariantDecoder.Destroy;
begin

  inherited;
end;

function TdxVariantDecoder.GetVariantItem(I: Integer): Variant;
var
  DataType: Byte;
  VarData: TVarData;
  A: AnsiString;
  W: WideString;
  Size: Cardinal;
begin
  if (not (I in [Low(FIndexMap) .. High(FIndexMap)])) then
  begin
    raise ECommandParserException.Create('Data table index out of bounds.');
  end;
  DataType := PByte(DWord(FData) + FIndexMap[I])^;
  case DataType of
    $0: Result := PSmallInt(Cardinal(FData) + FIndexMap[I] + 1)^;
    $1: Result := PInteger(Cardinal(FData) + FIndexMap[I] + 1)^;
    $2: Result := PSingle(Cardinal(FData) + FIndexMap[I] + 1)^;
    $3: Result := PDouble(Cardinal(FData) + FIndexMap[I] + 1)^;
    $4: Result := PCurrency(Cardinal(FData) + FIndexMap[I] + 1)^;
    $5: Result := PDateTime(Cardinal(FData) + FIndexMap[I] + 1)^;
    $6: Result := PWordBool(Cardinal(FData) + FIndexMap[I] + 1)^;
    $7: Result := PShortInt(Cardinal(FData) + FIndexMap[I] + 1)^;
    $8: Result := PByte(Cardinal(FData) + FIndexMap[I] + 1)^;
    $9: Result := PWord(Cardinal(FData) + FIndexMap[I] + 1)^;
    $A: Result := PLongWord(Cardinal(FData) + FIndexMap[I] + 1)^;
    $B: Result := PInt64(Cardinal(FData) + FIndexMap[I] + 1)^;
    $C: Result := PUInt64(Cardinal(FData) + FIndexMap[I] + 1)^;
    $D:
      begin
        VarData.VType := varString;
        Size := PCardinal(Cardinal(FData) + FIndexMap[I] + 1)^;
        SetLength(A, Size div SizeOf(AnsiChar));
        CopyMemory(@A[1], Pointer(Cardinal(FData) + FIndexMap[I] + 5), Size);
        Result := A;
      end;
    $E:
      begin
        VarData.VType := varUString;
        Size := PCardinal(Cardinal(FData) + FIndexMap[I] + 1)^;
        SetLength(W, Size div SizeOf(WideChar));
        CopyMemory(@W[1], Pointer(Cardinal(FData) + FIndexMap[I] + 5), Size);
        Result := W;
      end;
  end;
end;

end.
Hier noch ein kurzes Beispiel, wie die Klassen verwendet werden:
Delphi-Quellcode:
procedure TForm6.Button1Click(Sender: TObject);
var
  Encoder: TdxVariantEncoder;
  Decoder: TdxVariantDecoder;
begin
  Encoder := TdxVariantEncoder.Create;
  try
    Encoder.Append([1, -120000, 'testwide', AnsiString('testansi'), false]);
    Encoder.Append(Word(2));
    Encoder.Append([Int64(33), 123.444]);
    Decoder := TdxVariantDecoder.Create(Encoder.Data, Encoder.Size);
    try
      ShowMessage(Decoder[3]); // testansi
      ShowMessage(Decoder[7]); // 123.444
    finally
      Decoder.Free;
    end;
  finally
    Encoder.Free;
  end;
end;
Viele Grüße
Zacherl

Zacherl 5. Mai 2011 13:10

AW: Netzwerkprotokoll zum Übertragen von Variant Streams
 
push

Stevie 10. Mai 2011 10:37

AW: Netzwerkprotokoll zum Übertragen von Variant Streams
 
Ohne mir den Source genau anzuschauen kann ich dir schon sagen, dass du dir mit so einem Protokoll keinen Gefallen tust, wenn du mal verschiedene Versionen miteinander kompatibel machen willst. Sobald sich irgendwelche Internas ändern (wie zum Beispiel mit Unicode geschehen, oder wie mit NativeInt unter verschiedenen Systemen geschehen wird) ist es nicht mehr zu gebrauchen, außer du bringst beide Seiten wieder auf einen Stand.

Zacherl 10. Mai 2011 12:18

AW: Netzwerkprotokoll zum Übertragen von Variant Streams
 
Das ist kein Problem. Ansi und Unicode Strings werden differenziert. Ebenso bleiben bei den 64 Bit Delphi Versionen die Datentypen wie Integer, etc. ja 4 Byte groß. Die neuen NativeInt Typen kann ich einfach nachrüsten.

Wobei ich diese eh nicht verwenden werde, wenn eine Seite noch für 32 Bit kompiliert wurde. Insofern müsste ich eh alles neu builden.

Stevie 10. Mai 2011 14:26

AW: Netzwerkprotokoll zum Übertragen von Variant Streams
 
Sofern du dich auf Delphi auf beiden Seiten festlegen willst, und gegenüber möglichen Änderungen deiner Daten (neues Feld hinzugekommen, Datentyp hat sich geändert) anfällig sein möchtest, ist das sicherlich ganz brauchbar. Ich kann aber aus Erfahrung sagen, dass solch ein Protokoll die schlimmste Krätze sein kann, wenn man mit unterschiedlichen Versionen kompatibel sein möchte.

Zacherl 10. Mai 2011 15:28

AW: Netzwerkprotokoll zum Übertragen von Variant Streams
 
Das glaube ich gerne, habe da auch ein wenig Erfahrung mit. Das Protokoll ist allerdings für ein kleines Projekt entstanden, bei dem beide Seiten mit Delphi programmiert sind. Im Prinzip ging es mir nur um einfachen Zugriff (ohne ständige Typkonvertierungen) und möglichst wenig Protokoll Overhead.


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:27 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz