Можно ли в строке AnsiString хранить произвольные двоичные данные, которые в общем случае могут в середине содержать символы #0?
Собственно, сохранить-то их там не проблема, а будут ли правильно работать строковые функции? Вдруг где-то внутри функции она приведет мою строку к промежуточному PChar и обломается на первом же #0?
Где-то тут уже упоминались смутные сведения, что StringReplace у кого-то не работает с #0.
Речь идет о принципах построения некоей коммуникационной библиотечки для общения с железякой в ее собственном бинарном формате. Как правильно работать с данными в этом случае?
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
28-09-2006 05:29
Теперь уже не вспомню. Написал свою. Однако, и AnsiPos то же не всегда работает.
В общем, пока что я пришел к выводу, что для хранения данных можно пользоваться типом строк AnsiString, а для обработки и анализа - только операции ' + = <> < > ' и обычные Pos, Length, Copy. Не работают Ansi-функции. Не работает StringReplace. Не работает RegExpr. В общем, поупражняюсь на уровне школьного Turbo-Pascal. IMHO, дороговатую цену заплатил Borland за совместимость с null-terminated string.
>>>Но это светит переписыванием всех вкусностей за которые мы любим строки, включая - о ужас - мои любимые регулярные выражения. Может, уже кто-то занимался этим?
Я думаю, это единственно правильный путь. Никаких опасностей преобразования строк и, в качестве бонуса для всех нас, регулярные выражения для произвольной последовательности вайт от Fisher-а ;)
24-11-2005 05:20 | Комментарий к предыдущим ответам
Вообще-то при перекодировании из String в WideString (или в другую сторону) двоичные данные имеют тенденцию теряться (или, наоборот, появляются лишние данные). Например, символы #0.
добавлю немного к ответу Антона Григорьева: Все равно проблема в тех же самых Ansi*строковых-функциях. Ведь они же предназначены для работы не только с AnsiString, как можно было бы подумать, но и с WideString это не правильно. когда ты передаешь widestring в функцию требующую в качестве параметра string, то неявно вызывается функция конвертации string->widestring и функция работает с string! а вообще бы хорошо бы что бы компилятор выдавал хотябы warning, а лучше бы error- несовпадение типов.
Multibyte и Unicode - это разные вещи. В Unicode каждый символ занимает ровно два байта, и к ANSI это не имеет никакого отношения. Multibyte - это часть стандарта ANSI: для некоторых кодовых страниц, в которых слишком много символов, предусмотрены специальные последовательности, состоящие из двух байт, но кодирующие один символ. Получается, что разные символы кодируются разным количеством байт. Так что функции с префиксом Ansi работают с multibyte, но не работают с WideString, т.к. WideString - это Unicode, а не Multibyte.
WideString хорош тем, что это, по сути, обёртка над системным типом BSTR. В этом типе, как и в string, длина определяется не нулём, а хранится по отрицательному смещению (а вот счётчика ссылок у BSTR и, соответственно, у WideString, нет). Вот только не все системные Unicode'ные функции это осознают - некоторые работают только с PWIDECHAR, т.е. игнорируют длину по отрицательному смещению и считают строку до первого нуля. Поэтому использование WideString - это тоже не очень хорошо, тут тоже есть шанс нарваться. Хотя, я посмотрел навскидку коды - кажется, эти места успешно обойдены.
А чем же это лучше? Все равно проблема в тех же самых Ansi*строковых-функциях. Ведь они же предназначены для работы не только с AnsiString, как можно было бы подумать, но и с WideString:
Delphi Language Reference /
About extended character sets:
Names of multibyte functions usually start with Ansi-. For example, the multibyte version of StrPos is AnsiStrPos.
Могу предложить написанные давно функции. Кажется в рабочем состоянии (мне не критична скорость в разборе строки). Некоторые, например pos, можо оптимизировать. Опять же кажется, они не должны реагировать на #0. Если хочешь разбирайся.
type Str32 = String; //WideString;
//т.к. мне нужны были функции, раборающие с widestring.
function ComparePString(Str1: Pointer; Str1Length: Longint; Str2: Pointer;
Str2Length: Longint; CaseSensitive: Boolean = False): Longint;
const
_FlagCase: array[Boolean] of Longint = (NORM_IGNORECASE, 0);
CompareResult: array[0..2] of Longint = (-1, 0, 1);
begin
Result :=
CompareResult[CompareString(LOCALE_USER_DEFAULT,
_FlagCase[CaseSensitive], Str1, Str1Length, Str2, Str2Length) - 1]
end;
function CompareString(const Str1, Str2: Str32;
CaseSensitive: Boolean = False): Longint;
begin
Result := ComparePString(Pointer(Str1), Length(Str1), Pointer(Str2), Length(Str2), CaseSensitive);
end;
function Pos(const SubStr, Str: Str32; FromPos: Integer = 0; CaseSensitive: Boolean = True): Longint;
var
Bool: Boolean;
SubStrLength, StrLength, CurPos: Longint;
TmpStr: Pointer;
begin
Bool := False;
if FromPos < 0 then FromPos := 0;
CurPos := FromPos;
if (Str = '') or (SubStr = '') then
else begin
SubStrLength := Length(SubStr);
StrLength := Length(Str);
if (SubStrLength > StrLength) then
else begin
TmpStr := Pointer(Integer(Pointer(Str)) + FromPos);
while (not Bool) and (CurPos <= StrLength - SubStrLength) do
begin
Bool := (ComparePString(Pointer(SubStr), SubStrLength, TmpStr, SubStrLength, CaseSensitive) = 0);
TmpStr := Pointer(Integer(TmpStr) + 1);
Inc(CurPos);
end;
end
end;
if Bool then Result := CurPos else Result := 0;
end;
function StringReplace(const Str, OldPattern, NewPattern: Str32;
Flags: TReplaceFlags): Str32;
var
Offset, LengthOldPattern: Integer;
NewStr: Str32;
begin
NewStr := Str;
LengthOldPattern := Length(OldPattern);
Result := '';
while NewStr <> '' do
begin
Offset := Pos(OldPattern, NewStr, 0, not (rfIgnoreCase in Flags));
if Offset = 0 then
begin
Result := Result + NewStr;
Break;
end;
Result := Result + Copy(NewStr, 1, Offset - 1) + NewPattern;
Delete(NewStr, 1, Offset + LengthOldPattern - 1);
if not (rfReplaceAll in Flags) then
begin
Result := Result + NewStr;
Break;
end;
end;
end;
to Григорий Цуканов: А почему только AnsiPos? А что еще нужно переписать? А что можно не переписывать? Где бы это узнать?
Можно я отвечу?
AnsiPos - потому что, если посмотреть её исходники, видно, что там аргумент приводится к PChar. Надо смотреть исходники каждой функции (модули SysUtils и StrUtils) и разбираться, где идёт преобразование к PChar, а где без этого обходятся.
to Orinoko: в пилотном проекте у меня тоже уже все вроде бы заработало на string. Но, во-первых, такого рода проьлемы могут начать проявляться в будущем при каком-то сочетании данных - например приедет от железки несколько #0 подряд в какой-нибудь контрольной сумме блока. Во-вторых, сейчас речь идет уже о "расширении и углублении" библиотеки, наращивании на нее дополнительной функциональности, в частности прикладные обработчики кусков летящих данных, которые будут парсить их и извлекать нужную информацию. И страшно оставлять в ядре системы не до конца проясненный вопрос.
to Алексей Румянцев: если бы они выдавали 'фи', все было бы хорошо. Но ведь они просто будут тихонько работать неправильно при некотором наборе входных данных. И это печально.
to Григорий Цуканов: А почему только AnsiPos? А что еще нужно переписать? А что можно не переписывать? Где бы это узнать?
to panda: я сам чувствую что лучше. Но это светит переписыванием всех вкусностей за которые мы любим строки, включая - о ужас - мои любимые регулярные выражения. Может, уже кто-то занимался этим?
Переменная типа String может хранить в себе все коды символов включая #0. У меня прекрасно всё работает в задаче, аналогично твоей (работаю с нестандартными железяками через COM-порт).
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.