Delphi. Массив байтов в строку шестнадцатеричных значений.

| рубрика: Заметки | автор: st
Метки: ,

Иногда бывает нужно посмотреть значение массива байтов в понятном человеку виде, чаще всего в виде пар шестнадцатеричных цифр. Задача может быть решена простой функцией, на примере которой мы увидим три подхода к работе со строками в Delphi и Паскале.

Паскалевские строки: наглядно и просто

Предлагаемый ниже способ хотя и не максимально быстрый, но близок к нему, обладая при этом наглядностью и компактностью. Наиболее быстрым решением, видимо, будет определение постоянного массива символов от Chr(0) до Chr(255) и адресация по значению очередного байта в цикле.

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char =
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
begin
  len := Length(bytes);
  SetLength(Result, len * 2);
  for i := 0 to len - 1 do begin
    Result[i * 2 + 1] := BytesHex[bytes[i] shr 4];
    Result[i * 2 + 2] := BytesHex[bytes[i] and $0F];
  end;
end;

Пример использования

const
  Bytes: array[0..11] of byte = (1, 2, 3, 4, 5, 10, 25, 35, 99, 122, 173, 255);
...
Writeln(ByteToStr(Bytes));
...

На экране видим: 01020304050A1923637AADFF

Функцию легко модифицировать, чтобы показывать значения в формате 0xDD или разделенные пробелом.

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char =
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  for i := 0 to len - 1 do begin
    Result[i * 5 + 1] := '0';
    Result[i * 5 + 2] := 'x';
    Result[i * 5 + 3] := BytesHex[bytes[i] shr 4];
    Result[i * 5 + 4] := BytesHex[bytes[i] and $0F];
    Result[i * 5 + 5] := ' ';
  end;
end;

Результат: 0x01 0x02 0x03 0x04 0x05 0x0A 0x19 0x23 0x63 0x7A 0xAD 0xFF

"Сишные" строки: менее наглядно, но быстрее

В предыдущем примере мы использовали паскалевские строки, хотя известно, что управление ими менее эффективно, чем, например, прямые манипуляции указателями PChar в сишном стиле. Например, если открыть дизассемблер, то при каждом обращении к строке будет вызываться функция UniqueString. Это добавит несколько тактов процессора на каждое такое присвоение.

Зато первый пример был наглядный. Не зря же Вирт придумал язык! Поэтому приведем и более оптимальный по скорости, чтобы можно было сравнить.

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char =
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, len: integer;
  s: PChar;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  s := StrAlloc(len * 5 + 1);
  for i := 0 to len - 1 do begin
    StrLCopy(@s[i * 5], '0x', 2);
    StrLCopy(@s[i * 5 + 2], @BytesHex[bytes[i] shr 4], 1);
    StrLCopy(@s[i * 5 + 3], @BytesHex[bytes[i] and $0F], 1);
    StrLCopy(@s[i * 5 + 4], ' ', 1);
  end;
  Result := s;
  SysUtils.StrDispose(s);
end;

Прямое управление указателем в ассемблерном стиле: максимальная эффективность

function ByteToStr(bytes: array of byte): string;
const
  BytesHex: array[0..15] of char =
    ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');
var
  i, j, len: integer;
  s: PChar;
begin
  len := Length(bytes);
  SetLength(Result, len * 5);
  s := StrAlloc(len * 5 + 1);
  j := 0;
  for i := 0 to len - 1 do begin
    s[j] := '0';
    Inc(j);
    s[j] := 'x';
    Inc(j);
    s[j] := BytesHex[bytes[i] shr 4];
    Inc(j);
    s[j] := BytesHex[bytes[i] and $0F];
    Inc(j);
    s[j] := ' ';
    Inc(j);
  end;
  Result := s;
  SysUtils.StrDispose(s);
end;