Programowanie w języku Delphi » Artykuły

Jak skonwertować strumień do łańcucha i łańcuch do strumienia

Wstęp


Artykuł opisuje dwie procedury służące do konwersji dowolnego strumienia do łańcucha tesktu i odwrotnie - łańcucha do strumienia. Operacje tego typu wykorzystywane są m.in. w plikach INI do zapisu i odczytu danych binarnych. Konwersja polega na zamianie każdego bajtu pobranego ze strumienia na ciąg odpowiadający wartości heksadecymalnej o długości dwóch znaków. W ten sposób budowany jest łańcuch stanowiący ciąg wartości szesnastkowych o długości dokładnie dwukrotnie większej, niż rozmiar danych strumienia.



Obsługiwane typy danych


Procedury dokonujące konwersji wykorzystują łańcuchy AnsiString (jeden bajt na jeden znak) do tekstowej reprezentacji ciągu bajtów, oraz dowonego strumienia dziedziczącego z klasy TStream (np. TMemoryStream czy TStringStream).


Konwersja strumienia do łańcucha (StreamToString)


Do konwersji strumienia do łańcucha potrzebne są dwa argumenty: strumień źródłowy klasy dziedziczącej z TStream oraz łańcuch docelowy typu AnsiString. W pierwszej kolejności zawartość strumienia należy skopiować do buforu pomocniczego - dynamicznej macierzy przechowującej pojedyncze bajty (array of Byte), następnie ustawić długość łańcucha docelowego, równą dwukrotnej długości buforu. Kolejnym krokiem jest skonwertowanie wszystkich bajtów pobranych z buforu do wartości szesnastkowych i wstawienie ich w odpowiednie miejsca w łańcuchu docelowym. W razie wystąpienia jakiegokolwiek wyjątku (czy to podczas odczytu danych ze strumienia, czy podczas konwersji bajtu na wartość heksadecymalną) długość łańcucha docelowego zostanie wyzerowana.

Kod procedury konwertującej strumień do łańcucha:

procedure StreamToString(AStream: TStream; out AString: AnsiString);
var
  arrBuffer: array of Byte;
  ansiHexValue: AnsiString;
  intBufferLen, I: Integer;
begin
  try
    intBufferLen := AStream.Size;
    SetLength(arrBuffer, intBufferLen);
    SetLength(AString, intBufferLen shl 1);
    AStream.Position := 0;
    AStream.ReadBuffer(arrBuffer[0], intBufferLen);
 
    for I := 0 to intBufferLen - 1 do
    begin
      ansiHexValue := IntToHex(arrBuffer[I], 2);
      Move(ansiHexValue[1], AString[I shl 1 + 1], 2);
    end;
  except
    SetLength(AString, 0);
  end;
end;

Dzięki takim zabiegom, jak wykorzystanie operatora logicznego shl, jednorazowe ustawienie rozmiaru łańcucha docelowego oraz skorzystanie z procedury Move do kopiowania przekonwertowanych wartości bajtów, operacja konwersji wykonuje się znacznie szybciej, niż miało by to miejsce z użyciem operatora div, mnożeniem przez 2 i każdorazowym dodawaniu wartości szesnastkowej do łańcucha docelowego.


Konwersja łańcucha do strumienia (StringToStream)


Podobnie do poprzedniej - do konwersji łańcucha do strumienia wymagane będą dwa argumenty - łańcuch źródłowy typu AnsiString oraz strumień docelowy klasy dziedziczącej z TStream. W pierwszej kolejności należy obliczyć rozmiar buforu dzieląc długość łańcucha źródłowego przez 2. Następnie jeśli rozmiar jest większy od 0, należy ustawić rozmiar buforu, do którego zostaną zapisane skonwertowane liczby heksadecymalne. Kolejnym krokiem jest nadanie wartości domyślnej dla zmiennej pomocniczej przechowującej wartość szesnastkową. Kolejnym krokiem jest kopiowanie w pętli po dwia znaki z łańcucha źródłowego do zmiennej pomocniczej, przekonwertowanie łańcucha pomocniczego do wartości liczbowej i wpisanie jej do buforu w odpowiednie miejsce. Po wykonaniu konwersji bufor należy wpisać do strumienia.

Kod procedury konwertującej łańcuch do strumienia:

procedure StringToStream(const AString: AnsiString; AStream: TStream);
var
  arrBuffer: array of Byte;
  ansiHexValue: AnsiString;
  intBufferLen, I: Integer;
begin
  intBufferLen := Length(AString) shr 1;
 
  if intBufferLen > 0 then
  begin
    SetLength(arrBuffer, intBufferLen);
    ansiHexValue := '$00';
 
    for I := 0 to intBufferLen - 1 do
    begin
      Move(AString[I shl 1 + 1], ansiHexValue[2], 2);
      arrBuffer[I] := StrToInt(ansiHexValue);
    end;
 
    AStream.WriteBuffer(arrBuffer[0], intBufferLen);
  end;
end;

Z racji tej, że do obliczenia rozmiaru buforu wykorzystane jest dzielenie bez reszty - w razie niepoprawnej długości łańcucha źródłowego (długość nieparzysta) ostatni znak łańcucha zostaje pominięty.


Przykład zastosowania


Wykonanie poniższego kodu:

var
  ssBuffer: TStringStream;
  ansiBuffer: AnsiString;
begin
  ssBuffer := TStringStream.Create('object pascal');
  try
    StreamToString(ssBuffer, ansiBuffer);
    Write('ansiBuffer: "', ansiBuffer, '"');
  finally
    ssBuffer.Free();
  end;
 
  ReadLn;
end.

spowoduje wyświetlenie na ekranie konsoli tekstu:

ansiBuffer: "6F626A6563742070617363616C"


Zakończenie


Powyższe procedury stanowią jedynie podstawę. Podane algorytmy można dowolnie rozbudowywać, np. dodając możliwość dzielenia łańcucha docelowego na linie po np. 32 bajty (wstawiając odpowiednie separatory), czy dodając obsługę łańcuchów WideString.