PAnsiString — Тип Delphi


Содержание

Delphi Ansistrings

У меня есть случай здесь, я собираюсь перейти на delphi 2011 XE из Delphi 7, и к моему удивлению многие компоненты будут иметь проблемы из-за регистрации, в delphi xe они выглядят как японские/китайские персонажи, теперь подразделение Я использую разъем PCSC и, кажется, прекращен/оставлен от оригинального разработчика.

в основном то, что я хочу, это простой способ прочитать строки снова с минимальной модификацией исходного кода.

Также, если есть хорошие учебные пособия о том, как готовые компоненты makae, готовые к 2009 году, и новее помогли бы мне также

@Plastkort, Delphi >= 2009 отлично умеет читать и обрабатывать AnsiString. Вы получаете только бессмысленные символы, если вы каким-то образом загружаете данные ANSI в Unicode, возможно, с помощью hard-casting указателя на PChar.

Если бы мне пришлось преобразовать код другого пользователя в Unicode, я бы начал с поиска PChar, Char и String и, в частности, глядя на те места, где другие типы были жестко заданы для этих типов. Это потому, что эти типы изменили значение: в не-Unicode delphi Char был 1 байт, теперь он 2 байта.

Само преобразование не является сложным, вам просто нужно понять проблему, с которой вы столкнулись, и вам нужно хорошо понимать код, который вы конвертируете. И это большая работа, особенно при работе с кодом, который делал «умные вещи со строками».

Большое изменение в Delphi (до Delphi 2009, я думаю) — это псевдонимы строк; строка, Char, PChar и т.д., которые в 2009 году были типами ANSI, теперь являются типами WideChar.

Самый простой способ миграции из Ansi Delphi в Unicode Delphi — это глобальный поиск и замена для псевдонимов, которые заменяют их явными 8-битными ANSI-эквивалентами (т.е. заменяют всю строку AnsiString, PChar с PAnsiChar и т.д..)

Это должно получить вам 90% пути.

Обновление Прочитав комментарии к моему ответу и статью, на которую ссылается @O.D Я думаю, что совет, чтобы укусить пулю и пойти с Unicode, является более разумным вариантом.

Работа со строковыми типами данных в Delphi

Строковый тип данных — один из самых часто используемых в программах тип. Действительно, без него не обходится практически ни один алгоритм. Даже программы, выполняющие исключительно математические операции, порой, написаны с использованием строковых типов данных.

Строка — это последовательность символов. В Object Pascal существует несколько строковых типов. Вот основные из них:

Для большинства целей подходит тип AnsiString (иногда называется Long String ).

Стандартные функции обработки строк:

1) Функция Length(Str: String) — возвращает длину строки (количество символов). Пример:

var
Str: String; L: Integer;
< . >
Str := ‘Hello!’ ;
L := Length(Str);

2) Функция SetLength(Str: String; NewLength: Integer) позволяет изменить длину строки. Если строка содержала большее количество символов, чем задано в функции, то «лишние» символы обрезаются. Пример:

var Str: String;
< . >
Str := ‘Hello, world!’ ;
SetLength(Str, 5);

3) Функция Pos(SubStr, Str: String) — возвращает позицию подстроки в строке. Нумерация символов начинается с единицы (1). В случае отсутствия подстроки в строке возращается 0. Пример:

var Str1, Str2: String; P: Integer;
< . >
Str1 := ‘Hi! How do you do?’ ;
Str2 := ‘do’ ;
P := Pos(Str2, Str1);

4) Функция Copy(Str: String; Start, Length: Integer) — возвращает часть строки Str, начиная с символа Start длиной Length. Ограничений на Length нет — если оно превышает количество символов от Start до конца строки, то строка будет скопирована до конца. Пример:

var Str1, Str2: String;
< . >
Str1 := ‘This is a test for Copy() function.’ ;
Str2 := Copy(Str1, 11, 4);

5) Процедура Delete(Str: String; Start, Length: Integer) — удаляет из строки Str символы, начиная с позиции Start длиной Length. Пример:

var Str1: String;
< . >
Str1 := ‘Hello, world!’ ;
Delete(Str1, 6, 7);

6) Функции UpperCase(Str: String) и LowerCase(Str: String) преобразуют строку соответственно в верхний и нижний регистры:

var Str1, Str2, Str3: String;
< . >
Str1 := ‘hELLo’ ;
Str2 := UpperCase(Str1); < Str2 = "HELLO" >
Str3 := LowerCase(Str1);

Строки можно сравнивать друг с другом стандартным способом:

var Str1, Str2, Str3: String; B1, B2: Boolean;
< . >
Str1 := ‘123’ ;
Str2 := ‘456’ ;
Str3 := ‘123’ ;
B1 := (Str1 = Str2); < B1 = False >
B2 := (Str1 = Str3);

Если строки полностью идентичны, логическое выражение станет равным True.

Дополнительные функции обработки строк:

В модуле StrUtils.pas содержатся полезные функции для обработки строковых переменных. Чтобы подключить этот модуль к программе, нужно добавить его имя ( StrUtils ) в раздел Uses .

1) PosEx(SubStr, Str: String; Offset: Integer) — функция аналогична функции Pos() , но позволяет задать отступ от начала строки для поиска. Если значение Offset задано (оно не является обязательным), то поиск начинается с символа Offset в строке. Если Offset больше длины строки Str, то функция возратит 0. Также 0 возвращается, если подстрока не найдена в строке. Пример:

uses StrUtils;
< . >
var Str1, Str2: String; P1, P2: Integer;
< . >
Str1 := ‘Hello! How do you do?’ ;
Str2 := ‘do’ ;
P1 := PosEx(Str2, Str1, 1); < P1 = 12 >
P2 := PosEx(Str2, Str1, 15);

2) Функция AnsiReplaceStr(Str, FromText, ToText: String) — производит замену выражения FromText на выражение ToText в строке Str. Поиск осуществляется с учётом регистра символов. Следует учитывать, что функция НЕ изменяет самой строки Str, а только возвращает строку с произведёнными заменами. Пример:

uses StrUtils;
< . >
var Str1, Str2, Str3, Str4: String;
< . >
Str1 := ‘ABCabcAaBbCc’ ;
Str2 := ‘abc’ ;
Str3 := ‘123’ ;
Str4 := AnsiReplaceStr(Str1, Str2, Str3);

3) Функция AnsiReplaceText(Str, FromText, ToText: String) — выполняет то же самое действие, что и AnsiReplaceStr(), но с одним исключением — замена производится без учёта регистра. Пример:

uses StrUtils;
< . >
var Str1, Str2, Str3, Str4: String;
< . >
Str1 := ‘ABCabcAaBbCc’ ;
Str2 := ‘abc’ ;
Str3 := ‘123’ ;
Str4 := AnsiReplaceText(Str1, Str2, Str3);

4) Функция DupeString(Str: String; Count: Integer) — возвращает строку, образовавшуюся из строки Str её копированием Count раз. Пример:

uses StrUtils;
< . >
var Str1, Str2: String;
< . >
Str1 := ‘123’ ;
Str2 := DupeString(Str1, 5);

5) Функции ReverseString(Str: String) и AnsiReverseString(Str: AnsiString) — инвертируют строку, т.е. располагают её символы в обратном порядке. Пример:

uses StrUtils;
< . >
var Str1: String;
< . >
Str1 := ‘0123456789’ ;
Str1 := ReverseString(Str1);

6) Функция IfThen(Value: Boolean; ATrue, AFalse: String) — возвращает строку ATrue, если Value = True и строку AFalse если Value = False. Параметр AFalse является необязательным — в случае его отсутствия возвращается пустая строка.

uses StrUtils;
< . >
var Str1, Str2: String;
< . >
Str1 := IfThen(True, ‘Yes’ ); < Str1 = "Yes" >
Str2 := IfThen(False, ‘Yes’ , ‘No’ );

Мы рассмотрели функции, позволяющие выполнять со строками практически любые манипуляции. Как правило, вместо строки с указанным типом данных, можно использовать и другой тип — всё воспринимается одинаково. Но иногда требуются преобразования. Например, многие методы компонент требуют параметр типа PChar , получить который можно из обычного типа String функцией PChar(Str: String) :

uses ShellAPI;
< . >
var FileName: String;
< . >
FileName := ‘C:\WINDOWS\notepad.exe’ ;
ShellExecute(0, ‘open’ , PChar(FileName), » , » , SW_SHOWNORMAL);

Тип Char представляет собой один-единственный символ. Работать с ним можно как и со строковым типом. Для работы с символами также существует несколько функций:

Chr(Code: Byte) — возвращает символ с указанным кодом (по стандарту ASCII):

Ord(X: Ordinal) — возвращает код указанного символа, т.е. выполняет противоположное действие функции Chr() :

var X: Integer;
< . >
X := Ord( ‘F’ );

Из строки можно получить любой её символ — следует рассматривать строку как массив. Например:

var Str, S: String; P: Char;
< . >
Str := ‘Hello!’ ;
S := Str[2]; < S = "e" >
P := Str[5];

В этой статье описаны основные приёмы работы со строковыми типами данных. Как правило, этих данных достаточно для написания любого алгоритма.

Ссылки по теме

Популярные статьи
Информационная безопасность Microsoft Офисное ПО Антивирусное ПО и защита от спама Eset Software


Бестселлеры
Курсы обучения «Atlassian JIRA — система управления проектами и задачами на предприятии»
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год. Электронный ключ
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2020. Все языки. Электронный ключ
Курс «Oracle. Программирование на SQL и PL/SQL»
Курс «Основы TOGAF® 9»
Microsoft Windows Professional 10 Sngl OLP 1 License No Level Legalization GetGenuine wCOA (FQC-09481)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Windows Server 2020 Standard
Курс «Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации»
Антивирус ESET NOD32 Antivirus Business Edition
Corel CorelDRAW Home & Student Suite X8

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

PAnsiString — Тип Delphi

В выражениях Delphi поддерживает три физических строковых формата: короткий (ShortString), длинный (LongString) и широкий (WideString). Их можно комбинировать в операторах присваивания и выражениях (все необходимые преобразования Delphi выполняет автоматически).
Переменные типов AnsiString и WideString — это динамически распределяемые массивы символов, максимальная длина которых ограничивается только наличием памяти. Разница между ними состоит в том, что в AnsiString знаки записываются в формате char, а в WideString— в формате WideChar. Обычно вполне достаточно одного типа AnsiString, однако при работе с международными наборами символов, такими как UNICODE, удобнее использовать WideString.
Тип ShortString—это, по существу, массив Array [0..255] of char. Первый его элемент задает динамическую длину строки, которая может принимать значения от 0 до 255 символов. Символы, составляющие строку, занимают места от 1 до 255. Тип ShortString предназначен, в основном, для обеспечения совместимости с ранними версиями Delphi и Borland Pascal.
Логический строковый тип именуется просто String. Отнесение его к типу AnsiString или ShortString задается командой $Н. По умолчанию задается < $Н+>, и String совпадает с AnsiString. Если задать команду <$Н- >, то String будет совпадать с ShortString и иметь максимальную длину, равную 255 символам.
Для совместимости с другими языками программирования в Delphi поддерживается класс строк с конечным нулем. Зарезервированных слов или идентификаторов для этого класса не существует.
Строки с конечным нулем состоят из ненулевых символов и оканчиваются символом с порядковым номером 0 (#0). В отличие от типов AnsiString, ShortString и WideString, строки с нулевым окончанием не имеют указателя длины. Конец в этих стооках обозначается нулем.
Физически строки с нуль-окончанием подобны массивам символов с нумерацией элементов от нуля, наподобие array [ 0 . . X] of char, где Х — некоторое положительное целое, большее нуля, хотя никаких объявлении подобного рода не происходит. Вместо этого определяется переменная-указатель PChar и распределяется необходимый объем памяти. При необходимости строке AnsiString можно присвоить тип PChar.
В табл. 1.7 перечислены некоторые процедуры и функции обработки данных строковых типов.

Совет: Программисты, работающие на С, привыкли записывать все строки в массивы с нуль-окончанием. Фактически они применяют в выражениях не строковые переменные, а указатели на них. Программисты, работающие на Basic, привыкли использовать строку как одно целое. Для типа AnsiString из Delphi годятся оба подхода.

Что происходит, когда AnsiString отливают PAnsiString?

У меня есть метод (Delphi 2009):

Это абстрактный метод в базовом классе, где «Значение: Указатель» ожидает указатель соответствующих данных, как:

Поэтому я стараюсь передать значение, как это:

Но бросок из AnsiString в PAnsiString не работает, я могу понять, почему, но я хочу знать, что результат литья. Так что я написал простой тест:

Каково значение P1 и P4?

AnsiString Реализуется как указатель. AnsiString Переменная не имеет ничего , кроме адреса. Адрес является адресом первого символа в строке, или nil если строка пуста.

PAnsiString Является указателем на AnsiString переменную . Это указатель на указатель на первый символ строки. Когда вы говорите PAnsiString(Buffer) , вы говорите компилятор рассматривать указатель на Buffer как будто это указатель на AnsiString вместо указателя на символьные данные. Адрес 5006500 является расположение первого символа строки, C .

У вас есть запись в памяти, которая представляет строку:

Buffer содержит адрес байта с C в нем. Вы набираете-актерский иметь тип PAnsiString вместо AnsiString . Вы сказали компилятору , что вы имели этот макет:

Когда я рассуждал об указателях, я рисовать диаграммы так же, как это. Если вы не держите бумагу рядом с вами на вашем столе, вы вредите себе.

AnsiString Delphi XE7 [TEncoding]

Вопрос

Satellite 13

Satellite 13

  • Продвинутый пользователь
  • Пользователи
  • 13
  • 52 сообщения

На Windows тип AnsiString присутствует, на Android отсутствует.

Имеется код (функция):

На Windows результат отображается нормально, потому что ss:=AnsiString;

А вот на Android’e нету значения AnsiString, поэтому для теста я просто присвоил String; Отсюда пошли кракозябры, как можно получить результат в нормальной читаемой кодировке?

Почитав немного, наткнулся на TEncoding, но не разобрался.

Заметки о Pascal, Delphi и Lazarus

Следует ожидать переводов разделов справочной системы Delphi, компиляций из учебников, переводы статей, «путевые заметки» и прочие интересности. Блог прежде всего ориентирован на студентов, но опытных людей я тоже буду рад видеть;-)

четверг, 28 апреля 2011 г.

Строковые типы

О строковых типах

Строка представляет собой последовательность символов. Delphi поддерживает следующие предопределенные строковые типы:

2^31 символов

2^30 символов

2^30 символов

Тип Максимальная длина Требует памяти Используется для
ShortString 255 символов От 2 до 256 байт Обратной совместимости
AnsiString От 4 байт до 2GB 8-битные (ANSI) символы, DBCS ANSI, MBCS ANSI, символы Unicode и т.д.
UnicodeString От 4 байт до 2GB Символы Unicode, 8-битные (ANSI) символы, многопользовательские серверы и приложения с поддержкой нескольких языков
WideString От 4 байт до 2GB Символы Unicode; многопользовательские серверы и приложения с поддержкой нескольких языков. Использование UnicodeString обычно более предпочтительно, чем использование WideString (кроме приложений COM).

Замечание: Строковый тип по умолчанию — UnicodeString. Тип WideString предназначен для совместимости с типом COM BSTR. В общем случае следует использовать UnicodeString для не-COM приложений. Для большей части задач тип UnicodeString является предпочтительным. Тип string является псевдонимом для типа UnicodeString.

Строковые типы могут смешиваться при присваивании и построении выражений, — компилятор автоматически производит необходимые преобразования. Однако строки, передаваемые по ссылке в функцию или процедуру (как параметры var и out) должны соответствовать по типу формальным параметрам. Строки могут быть явным образом преобразованы в строки других типов. Тем не менее, преобразование мультибайтовой строки в однобайтовую строку может привести к потере данных.

Есть несколько особых строковых типов, о которых следует упомянуть:

  • Тип AnsiString зависит от кодовой страницы и объявлен следующим образом Это тип AnsiString, который имеет возможность обрабатывать свои внутренние данные в определенной кодовой странице.
  • Тип RawByteString это тип AnsiString($FFFF). RawByteString позволяет передавать строковые данные любой кодовой страницы без выполнения преобразований кодовой страницы. RawByteString должен использоваться только как параметр const, параметр-значение или значение, возвращаемое функцией. Никогда не следует передавать его по ссылке и создавать экземпляры этого типа путем объявления переменных.
  • UTF8String представляет собой строку, закодированную при помощи UTF-8 (переменное количество байт Unicode). Это тип зависит от кодовой страницы и представляет собой AnsiString с кодовой страницой UTF-8.

Зарезервированное слово string действует как общий идентификатор строковых типов. Например:

создает переменную S, которая содержит строку. На платформе Win32, компилятор интерпретирует тип string (когда он встречается в коде без числа в квадратных скобках) как UnicodeString.

На платформе Win32, вы можете использовать директиву <$H->для переключения от типа string к ShortString. Потенциально это полезный прием для работы со старым 16-битным кодом Delphi или Turbo Pascal в ваших новых программах.

Следует учесть, что ключевое слово string так же используется для объявления типов ShortString с заранее определенной длинной.

Сравнение строк выполняется путем определения порядковых номеров элементов стоящих на соответствующих позициях. При сравнении строк неравной длины, каждый символ в более длинной строке при отсутствии соответствующего символа в более короткой строке считается за большее значение. Например, ‘AB’ больше, чем ‘A’; то есть выражение ‘AB’ > ‘A’ возвращает значение True. Строки с нулевой длинной представляют собой наименьшие значения при сравнении.

Вы можете индексировать строковую переменную так, как будто вы обрабатываете массив. Если S – это строка типа, отличного от UnicodeString, а i – это целочисленное выражение, то S[i] представляет i-ый байт в S, который может не быть i-ым символом или вообще не быть целым символом в том случае, если вы вы работаете с мультибайтовой строкой (MBCS). Таким же образом индексирование переменной типа UnicodeString может не обеспечить доступ к конкретному символу. Если строка содержит символы в Базовом Многоязыковом Плане (Basic Multilingual Plane (BMP)), все символы являются двухбайтовыми, а индексирование обеспечит доступ к символам строки. Однако, если некоторые символы не присутствуют в BMP, индексируемый элемент может не оказаться суррогатной парой – целым символом.

Стандартная функция Length возвращает количество элементов в строке. Как было указано, количество элементов не обязательно равно количеству символов. Процедура SetLength устанавливает длину строки. Следует учесть, что функция SizeOf возвращает количество байт, необходимых для представления переменной или типа, и только для типов ShortString это число будет равно количеству символов. Для всех остальных типов строк функция SizeOf возвращает количество байт в указателе (поскольку эти типы являются указателями).

Для типов ShortString или AnsiString S[i] является значением типа AnsiChar. Для W второму символу в MyString. В следующем примере используется стандартная функция UpCase для преобразования значения MyString в заглавные буквы:

Будьте внимательны индексируя строки таким образом, поскольу перезапись конца строки может привести к ошибкам доступа (access violation). Кроме того, следует избегать передачи индексов строк как параметров var, поскольку это снижает эффективность кода.

Вы можете присвоить переменной значение строковой константы или любого другого выражения, возвращающего строку. Длина строки динамически изменяется при выполнении присваивания. Примеры:

Короткие строки

Переменные типа ShortString могут хранить от 0 до 255 однобайтовых символов. Поскольку длина ShortString может динамически изменяться, в памяти статически выделяется 256 байт, при этом первый байт содержит длину строки, а оставшиеся 255 доступны для хранения символов. Если переменная S имеет тип ShortString, то Ord(S[0]), как и Length(S), возвращаяет длину S; присваивание значения S[0], как и вызов SetLength, изменяет длину S. ShortString поддерживается только для обратной совместимости.

Язык Delphi поддерживает короткострочные типы — фактически, подтипы типа ShortString – в которых максимальная длина строки может быть от 0 до 255 символов. Количество символов определяется по числу, указываемому в квадратных скобках и идущему после зарезервированного слова string. Например:

создает переменную с именем MyString, максимальная длина строки у которой – 100 символов. Это эквивалентно объявлениям:

Переменные, объявленные таким образом, занимают в памяти столько места, сколько в точности требуется для хранения их значений. То есть указанная максимальная длина строки плюс один байт. В нашем примере MyString использует 101 байт по сравнению с 256 байтами для переменной предопределенного типа ShortString.

Если при присваивании значения переменной типа ShortString, длина значения превышает максимальную длину строки для типа, — значение обрезается.

Стандартные функции High и Low работают с идентификаторами короткострочных типов и переменными этих типов. High возвращает максимальную длину строки, а Low возвращает ноль.

Тип AnsiString

Тип AnsiString представляет собой динамически изменяемые строки, максимальная длина которых ограничивается только доступной памятью.

Переменная типа AnsiString – это структура, содержащая строковую информацию. Когда переменная пуста (то есть содержит строку нулевой длины), указатель имеет значение nil и строка не использует никакого дополнительной памяти для хранения. Когда переменная непуста, она указатель указывает на динамически выделенный блок памяти, содержащий строковое значение. Эта память динамически выделена в куче, но управление ею происходит полностью автоматически и не требует дополнительного пользовательского кода. Структура типа AnsiString содержит 32-битный индикатор, 32-битный счетчик ссылок, 16-битный индикатор для количества байт на символ и 16 бит на кодовую страницу.

Тип AnsiString представляет собой однобайтовую строку. С однобайтовыми наборами символов (single-byte character set (SBCS)) каждый байт в строке представляет собой один символ. В мультибайтовых наборах символов (multibyte character set (MBCS)) элементы являются однобайтовыми, при этом одни символы представляются одним байтом, а другие – более чем одним байтом. Мультибайтовые наборы символов (в особенности двухбайтовые (double-byte character sets (DBCS))) широко используются для азиатских языков. Строка типа AnsiString может содержать символы MBCS.

Доступ к символам AnsiString по индексу начинается с единицы. Индексирование многобайтовых строк ненадежно, поскольку S[i], представляет i-ый байт (не обязательно i-ый символ) в S. Тем не менее, стандартные функции для работы с AnsiString имеют мультибайтовые компоненты, которые так же реализуют сортировку символов по региональному стандарту.(Имена мультибайтовых функций обычно начинаются с префикса Ansi, например, мультибайтовая версия StrPos — AnsiStrPos.) Поддержка мультибайтовых символов зависит от операционной системы и базируется на локальных настройках.

Поскольку переменные типа AnsiString имеют указатели, две или более переменных может ссылаться на одно и то же значение без потребления дополнительной памяти. Компилятор функционирует таким образом для экономии ресурсов и более быстрого выполнения инструкций присваивания. При разрушении значения переменной или при выполнении присваивания, счетчик ссылок для предыдущего значения переменной уменьшается, а счетчик ссылок для нового значения – увеличивается (если таковое существует) – увеличивается. Когда счетчик ссылок для строки становится равным нолю, память высвобождается. Этот процесс называется подсчетом ссылок. Когда индексирование используется для изменения значения единичного символа в строке, производится копирование строки (но только в том случае, когда счетчик ссылок больше единицы). Это называется семантикой копирования при записи.

Тип UnicodeString

Тип UnicodeString представляет собой динамически изменяемые строки Unicode, максимальная длина которых ограничивается только доступной памятью.

В наборе символов Unicode каждый символ представлен одним или несколькими байтами. Unicode имеет несколько форматов преобразования (Unicode Transformation Formats) которые используют разные, но эквивалентные друг другу кодировки символов, которые могут быть легко преобразованы друг в друга.

  • В UTF-8, например, символы могут занимать от 1 до 4 байт. В UTF-8 первые 128 символов Unicode предназначены для символов US-ASCII.
  • UTF-16 – еще одна широко используемая кодировка Unicode, в которой символы занимают 2 или 4 байта.Большая часть мировых символов входит в Basic Multilingual Plane и может быть представлена 2 байтами. Остальные символы, которые требуют 2 байта, известны как суррогатные пары (surrogate pairs).
  • В UTF-32 для представления каждого символа требуется 4 байта.

Платформа Win32 поддерживает как однобайтовые и мультибайтовые наборы символов, так и Unicode. Операционная система Windows поддерживает UTF-16.

Тип UnicodeString имеет точно такую же структуру, как и AnsiString. Данные UnicodeString кодируются в UTF-16.

Поскольку UnicodeString и AnsiString имеют одинаковую структуру, они работают похожим образом. Когда переменная UnicodeString непуста, она указывает на динамически выделенный блок памяти, который содержит строковое значение, и работа с памятью для этого – прозрачна для пользователя. Для переменных UnicodeString ведется подсчет ссылок. Две и более переменные могут ссылаться на одно и тоже значение, не потребляя дополнительной памяти.

Экземпляры UnicodeString могут индексировать символы. Индексирование начинается с 1, так же как и для AnsiString.

Тип UnicodeString совместим по присваиванию с остальными строковыми типами. Тем не менее, при присваивание между AnsiString и UnicodeString выполняется соответствующие преобразования. Следует учесть, что присваивание значения типа UnicodeString переменной типа AnsiString не рекомендовано и может повлечь за собой потерю данных.

Delphi так же поддерживает символы и строки Unicode для типов WideChar, PWideChar и WideString.

Тип WideString

Тип WideString представляет собой динамически изменяемую строку, состоящую из 16-битных символов Unicode. В некотором смысле он схож с AnsiString. На платформе Win32, WideString совместим с типом COM BSTR.

WideString подходит для использования в приложениях COM. Тем не менее, WideString не имеет счетчиков ссылок, что для остальных типов приложений тип UnicodeString является более гибким и эффективным.

Индексирование WideString ненадежно, поскольку S[i] представляет iый элемент (не обязательно iый символ) в S.

Для Delphi типы Char и PChar аналогичны типам WideChar и PWideChar, соответственно.

Работа со строками, завершающимися нулевым символом

Множество языков программирования, включая C и C++, не имеют специальных типов для работы со строками. Эти языки и среды, которые разработаны с их помощью, работают со строками, заканчивающимися нулевым символом. Строки, завершающиеся нулевым символом – это массивы символов (индексация начинается с нулевого символа), последний элемент в которых — NUL (#0). Поскольку массив не имеет индикатора длины, первый символ NUL помечает конец строки. Вы можете воспользоваться конструкциями Delphi и специальными подпрограммы в модуле SysUtils для работы с такими строками в тех случаях, когда вы хотите работать с системами, которые их используют.

Следующие объявления типов могут быть использованы для хранения строк, завершающихся нулевым символом:

При включенной директиве расширенного синтаксиса (<$X+>) вы можете присваивать значения строковых констант статическим символьным массивам с индексацией от нуля. (Динамические массивы для этой цели не подойдут.) Если вы инициализируете константу-массив, содержащую строку, которая короче, чем объявленная длина массива, оставшиеся символы устанавливаются в #0.

Использование указателей, массивов и строковых констант

Для работы со строками, завершающихся нулевым символом, часто бывает необходимо использовать указатели. Строковые константы совместимы по присваиванию с типами PChar и PWideChar, которые представляются указателями на массивы значений Char и WideChar, завершающиеся нулевым символом. Например:

P указывает на область памяти, которая содержит завершающуюся нулевым символом копию ‘Hello world!’ Это эквивалентно:

Вы можете также передавать строковые константы в любую функцию, принимающую параметры-значения или параметры-константы типа PChar или PWideChar – например, StrUpper(‘Hello world!’). Как и при присваивании типу PChar, компилятор генерирует копию строки, оканчивающуюся нулевым символом, и передает функции указатель на эту копию. Наконец, вы можете инициализировать константы (отдельно взятые или в составе структурированных типов) типа PChar или PWideChar строковыми литералами. Примеры:

Индексируемые от ноля символьные массивы совместимы с типами PChar и PWideChar. Когда вы используете массив символов вместо указателя, компилятор преобразует массив в константу указательного типа, значение которой соответствует адресу первого элемента массива. Например:

Этот код дважды вызывает SomeProcedure, передавая ей одно и то же значение.

Символьный указатель может быть проиндексирован как массив. В предыдущем примере MyPointer[0] возвращает H. Индекс указывает на смещение, добавляемое к указателю перед получением значения. (Для переменных типа PWideChar индекс автоматически умножается на два.) Таким образом, если P – это символьный указатель, то P[0] эквивалентно P^ и определяет первый символ в массиве, P[1] определяет второй символ и так далее. P[-1] определяет ‘символ’ находящийся слева от P[0]. Компилятор не производит проверки диапазона на таких индексах.


Функция StrUpper иллюстрирует индексирование указателей для проходу по строке, заканчивающейся нулевым символом:

Смешивание строк Delphi и строк, оканчивающихся нулевым символом

Вы можете перемешивать строки (значения типов AnsiString и UnicodeString) и строки, оканчивающиеся нулевым символом (значения типа PChar) в выражениях и инструкциях присваивания, а так же можете передавать значения типа PChar в функции или процедуры, принимающие строковые параметры. Присваивание S := P, где S – строковая переменная, а P – выражение типа PChar, копирует строку, оканчивающуюся нулевым символом в обычную строку.

В двоичной операции, если один операнд является строкой, а второй – значением типа PChar, операнд типа PChar преобразуется в UnicodeString.

Вы можете преобразовывать значение PChar в UnicodeString. Это может быть полезно, если вы хотите выполнить строковую операцию с двумя значениями PChar. Например:

Вы можете также преобразовать строку типа UnicodeString или AnsiString в строку, оканчивающуюся на нулевой символ. При этом будут действовать следующие правила:

  • Если S – это UnicodeString, PChar(S) преобразует S в строку, оканчивающуюся нулевым символом и возвращает указатель на первый символ в S. Такие преобразования используются для Windows API. Например, если Str1 и Str2 – это UnicodeString, вы можете вызвать функцию Win32 API MessageBox следующим образом: Используйте PAnsiChar(S) в том случае, когда S – это строка типа AnsiString.
  • Вы также можете использовать Pointer(S) для преобразования строки в нетипизированный указатель. Но, если S пуста, такое преобразование вернет значение nil.
  • PChar(S) всегда возвращает указатель на блок памяти; если S пуста, возвращается указатель на символ #0.
  • Когда вы преобразуете значение переменной типа UnicodeString или AnsiString в указатель, он действует до тех пор, пока переменной не присваивается новое значение или не выходит из области видимости. Если вы преобразуете любое другое строковое выражение в указатель, он действует только внутри инструкции, в которой выполняется преобразование.
  • Когда вы преобразуете выражение типа UnicodeString или AnsiString в указатель, он должен рассматриваться как доступный только для чтения.

Аналогичные правила действуют при смешивании значений типов WideString и PWideChar.

Delphi Ansistrings

У меня есть случай, я собираюсь перейти на Delphi 2011 XE из Delphi 7, и, к моему удивлению, многие компоненты будут иметь проблемы из-за рассогласования, в delphi xe они выглядят как символы японского языка / китайского языка, теперь единица, которую я использую, это разъем PCSC и, кажется, прекращено / отказались от первоначального разработчика.

в основном то, что я хочу, — это простой способ снова читать строки с минимальным изменением исходного кода, насколько это возможно ..

также, если есть какие-нибудь хорошие учебники о том, как сделать компоненты, готовые к выпуску в 2009 году, и более новые, мне тоже помогут

@Plastkort, Delphi> = 2009 прекрасно умеет читать и обрабатывать AnsiString. Вы получаете только бессмысленные символы, если вы каким-то образом жестко приводите данные ANSI в Unicode, возможно, путем жесткого указания указателя на PChar.

Если бы мне пришлось конвертировать чей-то код в Unicode, я бы начал с поиска PChar, Char и String и специально искал места, где другие типы жестко приводятся к этим типам. Это потому, что эти типы изменили значение: в не-Unicode delphi CHAR был 1 байт, теперь он 2 байта.

Само преобразование не обязательно сложно, вам просто нужно понять проблему, с которой вы сталкиваетесь, и вы должны хорошо понимать код, который вы конвертируете. И это большая работа, особенно когда дело касается кода, который делает «умные вещи со строками».

Большое изменение в Delphi (думаю, до Delphi 2009) — это псевдонимы строковых типов; string, Char, PChar и т. д., к которым в 2009 году относились строковые типы ANSI, теперь все типы WideChar.

Самый простой способ перехода с Ansi Delphi на Unicode Delphi — это глобальный поиск и замена псевдонимов строковых типов, которые заменяют их явными 8-битными эквивалентами ANSI (то есть заменяют всю строку на AnsiString, PChar на PAnsiChar и т. Д.)

Это должно дать вам 90% пути.

Обновление После прочтения комментариев к моему ответу и статье, на которую ссылается @OD, я думаю, что совет откусить пулю и перейти на Unicode — более разумный вариант.

3.3.1. Виды строк в Delphi

3.3.1. Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк: AnsiString , ShortString и PChar . Различие между ними заключается в способе хранения строки, а также выделения и освобождения памяти для нее. Зарезервированное слово string по умолчанию означает тип AnsiString , но если после нее следует число в квадратных скобках, то это означает тип ShortString , а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>, которая по умолчанию включена, но если ее выключить, то слово string станет эквивалентно ShortString ; или, что то же самое, string[255] . Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать ее нет нужды. Внутреннее устройство этих типов данных иллюстрирует рис. 3.2.

Рис. 3.2. Устройство различных строковых типов Delphi

Наиболее просто устроен тип ShortString . Это массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — это символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байтов.

Ограничения типа ShortString очевидны: на хранение длины отводится только один байт, поэтому такая строка не может содержать больше 255 символов. Кроме того, такой способ записи длины не совпадает с принятым в Windows, поэтому ShortString несовместим с системными строками.

В системе приняты так называемые нуль-терминированные строки: строка передается указателем на ее первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0 . Длина таких строк ограничена только доступной памятью и способом адресации (т. е. в Windows теоретически это 4 294 967 295 символов). Для работы с такими строками предусмотрен тип PChar . Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar = ^Сhar , однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar , то допустимо обращение к отдельным символам строки с помощью конструкции P[N] , где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString , здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее число байтов вверх или вниз (здесь речь идет только об операторах «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar ).

При работе с PChar программист целиком и полностью отвечает за выделение памяти для строки и за ее освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString , надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew , StrDispose и т. п. Их преимущество заключается в том, что менеджер памяти выделяет чуть больше места, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байтов было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0 , уменьшающий длину строки.

Компилятор также позволяет рассматривать статические массивы типа Char , начинающиеся с нулевого индекса, как нуль-терминированные строки. Такие массивы совместимы с типом PChar , что позволяет обойтись без использования динамической памяти при работе со строками.

Тип AnsiString объединяет достоинства типов ShortString и PChar : строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0 , что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, мы рассматривали в разд. 1.1.13.).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar . Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счетчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределявший и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших строки типа ShortString .

Счетчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1 , S2 типа AnsiString , присваивание вида S1:= S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2 , а счетчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется модифицировать, она сначала будет скопирована (а счетчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это не затрагивало остальные переменные.

Далее мы рассмотрим, какие проблемы могут возникнуть при использовании строк разного вида.

Тонкости работы со строками в Delphi

Written on 07 Января 2009 . Posted in Delphi

ОГЛАВЛЕНИЕ

Виды строк в Delphi

Для работы с кодировкой ANSI в Delphi существует три вида строк — AnsiString, ShortString и PChar. Между собой они различаются тем, где хранится строка и как выделяется и освобождается память для неё. Зарезервированное слово string по умолчанию означает тип AnsiString, но если после неё стоит число в квадратных скобках, то это означает тип ShortString, а число — ограничение по длине. Кроме того, существует опция компилятора Huge strings (управляется также директивами компилятора <$H+/->и <$LONGSTRINGS ON/OFF>), которая по умолчанию включена, но если её выключить, то слово string станет эквивалентно ShortString или, что то же самое, string[255]. Эта опция введена для обратной совместимости с Turbo Pascal, в новых программах отключать её нет нужды.

Наиболее просто устроен тип ShortString. Это — массив символов с индексами от 0 до N, где N — число символов, указанное при объявлении переменной (в случае использования идентификатора ShortString N явно не указывается и равно 255). Нулевой элемент массива хранит текущую длину строки, которая может быть меньше или равна объявленной (эту длину мы будем далее обозначать M), элементы с индексами от 1 до M — символы, составляющие строку. Значения элементов с индексами M+1..N не определены. Все стандартные функции для работы со строками игнорируют эти символы. В памяти такая переменная всегда занимает N+1 байт.

Ограничения типа ShortString очевидны: так как на хранение длины отводится только один байт, такая строка не может содержать больше 255-ти символов. Кроме того, такой способ записи длины не совпадает с принятым в Windows, поэтому ShortString несовместим с системными строками.

В системе приняты так называемые нуль-терминированные строки: строка передаётся указателем на её первый символ, длина строки отдельно нигде не хранится, признаком конца строки считается встретившийся в цепочке символов #0. Длина таких строк ограничена только доступной памятью и способом адресации (т.е. в Windows это 4294967295 символов). Для работы с такими строками предусмотрен тип PChar. Переменная такого типа является указателем на начало строки. В литературе нередко можно встретить утверждение, что PChar=^Char, однако это неверно: тип PChar встроен в компилятор и не выводится из других типов. Это позволяет выполнять с ним операции, недопустимые для других указателей. Во-первых, если P — переменная типа PChar, то допустимо обращение к отдельным символам строки с помощью конструкции P[N], где N — целочисленное выражение, определяющее номер символа (в отличие от типа ShortString, здесь символы нумеруются с 0, а не с 1). Во-вторых, к указателям типа PChar разрешено добавлять и вычитать целые числа, смещая указатель на соответствующее количество байт вверх или вниз (здесь речь идёт только об использовании операторов «+» и «-«; адресная арифметика с помощью процедур Inc и Dec доступна для любых типизированных указателей, а не только для PChar).

При использовании PChar программист целиком и полностью отвечает за выделение памяти для строки и за её освобождение. Именно это и служит основным источником ошибок у новичков: они пытаются работать с такими строками так же, как и с AnsiString, надеясь, что операции с памятью будут выполнены автоматически. Это очень грубая ошибка, способная привести к самым непредсказуемым последствиям.

Хотя программист имеет полную свободу выбора в том, как именно выделять и освобождать память для нуль-терминированных строк, в большинстве случаев самыми удобными оказываются специально предназначенные для этого функции StrNew, StrDispose и т.п. Их удобство заключается в том, что менеджер памяти выделяет памяти чуть больше, чем требуется для хранения строки, и в эту дополнительную память записывается, сколько байт было выделено. Благодаря этому функция StrDispose удаляет ровно столько памяти, сколько было выделено, даже если в середину выделенного блока был записан символ #0, уменьшающий длину строки.

Компилятор также позволяет рассматривать статические массивы типа Char, начинающиеся с нулевого индекса, как нуль-терминированные строки. Такие массивы совместимы с типом PChar, что позволяет обойтись без использования динамической памяти при работе со строками.

Тип AnsiString объединяет достоинства типов ShortString и PChar: строки имеют фактически неограниченную длину, заботиться о выделении памяти для них не нужно, в их конец автоматически добавляется символ #0, что делает их совместимыми с системными строками (впрочем, эта совместимость не абсолютная; как и когда можно использовать AnsiString в функциях API, можно прочитать здесь).

Переменная типа AnsiString — это указатель на первый символ строки, как и в случае PChar. Разница в том, что перед этой строкой в память записывается дополнительная информация: длина строки и счётчик ссылок. Это позволяет компилятору генерировать код, автоматически выделяющий, перераспределяющий и освобождающий память, выделяемую для строки. Работа с памятью происходит совершенно прозрачно для программиста, в большинстве случаев со строками AnsiString можно работать, вообще не задумываясь об их внутреннем устройстве. Символы в таких строках нумеруются с единицы, чтобы облегчить перенос старых программ, использовавших ShortString.

Счётчик ссылок позволяет реализовать то, что называется copy-on-demand, копирование по необходимости. Если у нас есть две переменные S1, S1 типа AnsiString, присваивание вида S1 := S2 не приводит к копированию всей строки. Вместо этого в указатель S1 копируется значение указателя S2, а счётчик ссылок строки увеличивается на единицу. В дальнейшем, если одну из этих строк потребуется изменить, она сначала будет скопирована (а счётчик ссылок оригинала, естественно, уменьшен) и только потом изменена, чтобы это изменение не затрагивало остальные переменные.

Ниже мы рассмотрим, какие проблемы могут возникнуть при использовании строк разного вида.

Хранение констант

Для работы с этим примером нам понадобится на форму положить пять кнопок и написать следующие обработчики для них (пример Constants):
В этом примере только нажатие на третью и четвёртую кнопку приводит к появлению надписи Test., Первые два обработчика вызывают исключение Access violation в строках, отмеченных звёздочками, а при нажатии пятой кнопки программа обычно работает без исключений (хотя в некоторых случаях оно всё же может возникнуть), но к слову «Test» добавляется какой-то мусор. Разберёмся, почему так происходит.

Все строковые константы, встречающиеся в программе, компилятор размещает в сегменте кода, в области, управление которой никогда не передаётся. Встретив в первом обработчике константу ‘Test’ и определив, что она относится к типу PChar, компилятор выделяет в этой области пять байт (четыре значащих символа и один завершающий ноль), а в указатель P заносится адрес этой константы. Сегмент кода доступен только для чтения, прав на его изменение система программе в целях безопасности не даёт, поэтому попытка изменить то, что находится в этом сегменте, приводит к закономерному результату — Access violation.

В обработчике второй кнопки происходит почти то же самое, с той лишь разницей, что для константы выделяется на восемь байт больше: т.к. в данном случае константа имеет тип AnsiString, ей нужны ещё 4 байта для хранения длины и 4 — для счётчика ссылок. В переменную S записывается указатель на эту константу. Приводя эту переменную к типу PChar, мы, по сути, просто копируем этот указатель в переменную P, а дальше происходит то же самое — попытка изменить страницу памяти, доступную программе только для чтения с тем же самым результатом.

В третьем случае константа, как и раньше, размещается в сегменте кода. Счётчик ссылок у таких констант всегда равен -1 — это значение указывает менеджеру памяти, что это константа, которая не может быть изменена и память для которой не нужно освобождать. Поэтому при любой попытке изменить переменную, которой присвоена такая константа, срабатывает механизм копирования по необходимости: для строки выделяется место в динамической памяти, затем значение константы копируется в эту область, обновляется значение указателя S, а затем выполняется изменение копии, находящейся в динамической памяти. Так как эта память доступна и для чтения, и для записи, исключение не возникает и всё работает так, как и было задумано.

В четвёртом случае константа также хранится в сегменте кода, но работы с указателем уже нет. Эта константа занимает там пять байт — один байт на длину и четыре — на символы. Переменная S размещается в стеке, занимая там 256 байт, а присваивание ей константы — это копирование значения константы в эту область памяти. Таким образом, в дальнейшем мы работаем не с константой в сегменте кода, а с её копией в стеке, которую можно без проблем модифицировать.

В пятом случае мы получаем указатель на этот участок стека. Обратите внимание, что приведение типов в данном случае не работает: для записи в P адреса первого символа строки приходится использовать оператор получения адреса @. Модификация строки проходит, как и в предыдущем случае, успешно, но при присваивании выражения типа PChar свойству типа AnsiString длина строки определяется по правилам, принятым для PChar, т.е. строка сканируется до обнаружения нулевого символа. Но так как ShortString «не отвечает» за то, что будет содержаться в неиспользуемых символах, там может остаться всякий мусор от предыдущего использования стека. Никакой гарантии, что сразу после последнего символа будет #0, нет. Отсюда и появление непонятных символов на экране.

Общий вывод таков: пока мы не вмешиваемся в работу компилятора с типами ShortString и AnsiString, получаем ожидаемый результат. Работа с этими же строками через PChar в обход стандартных механизмов приводит к появлению проблем. Кроме того, при работе со строками PChar необходимо чётко представлять, где и как выделяется для них память, иначе можно получить неожиданную ошибку.

Сравнение строк

Для типов PChar и AnsiString, которые являются указателями, понятие равенства двух строк может толковаться двояко: либо как равенство указателей, либо как равенство содержимого памяти, на которую эти указатели указывают. Второй вариант предпочтительнее, т.к. он ближе к интуитивному понятию равенства строк. Для типа AnsiString реализован именно этот вариант, т.е. сравнивать такие строки можно, ни о чём не задумываясь. Более сложные ситуации мы проиллюстрируем примером Comparisons. В нём девять кнопок, и обработчик каждой из них иллюстрирует одну из возможных ситуаций.
В данном примере мы увидим надпись «Не равно». Это происходит потому, что в этом случае сравниваются указатели, а не содержимое строк, а указатели здесь будут разные. Попытка сравнить строки PChar с помощью оператора сравнения — весьма распространённая ошибка у начинающих. Для сравнения таких строк следует использовать специальную функцию — StrComp.

Следующий пример, на первый взгляд, в плане сравнения ничем не отличается от только что рассмотренного:
Разница только в том, что строки хранятся не в динамической памяти, а в сегменте кода. Тем не менее, на экране появится надпись «Равно». Это происходит, разумеется, не потому, что сравнивается содержимое строк, а потому, что в данном случае два указателя оказываются равны. Компилятор поступает достаточно интеллектуально: видя, что в разных местах используется одна и та же константа, он выделяет для неё место только один раз, а потом помещает в разные указатели один адрес. Поэтому сравнение даёт правильный (с интуитивной точки зрения) результат.

Такое положение дел только запутывает ситуацию со сравнением PChar: написав подобный тест, человек может сделать вывод, что строки PChar сравниваются не по указателю, а по значению, и действовать под руководством этого заблуждения.

Раз уж мы столкнулись с такой особенностью компилятора, немного отвлечёмся от сравнения строк и копнём этот вопрос немного глубже. В частности, распространяется ли интеллект компилятора на константы типа AnsiString.
В этом примере на экран будет выведено «Равно». Как мы видим, указатели равны, т.е. и здесь компилятор проявил интеллект.

Рассмотрим чуть более сложный случай:
В этом случае указатели окажутся не равны. Действительно, с формальной точки зрения константа типа AnsiString отличается от константы типа PChar: в ней есть счётчик ссылок (равный -1) и длина. Однако если забыть о существовании этой добавки, эти две константы одинаковы: четыре значащих символа и один #0, т.е. компилятор, в принципе, мог бы обойтись одной константой. Тем не менее, на это ему интеллекта уже не хватило.

Но вернёмся к сравнению строк. Как мы знаем, строки AnsiString сравниваются по значению, а PChar — по указателю. А что будет, если сравнить AnsiString с PChar?
Этот код выдаст «Равно». Как мы знаем из предыдущего примера, значения указателей не будут равны, следовательно, производится сравнение по содержанию, т.е. именно то, что и требуется. Если исследовать код, который генерирует компилятор, то можно увидеть, что сначала неявно создаётся строка AnsiString, в которую копируется содержимое строки PChar, а потом сравниваются две строки AnsiString. Сравниваются, естественно, по значению.

Для строк ShortString сравнение указателей невозможно, две таких строки всегда сравниваются по значению. Правила хранения констант и сравнения с другими типами следующие:

1. Константы типа ShortString также размещаются в сегменте кода только один раз, сколько бы раз они ни повторялись в тексте.
2. При сравнении строк ShortString и AnsiString первая сначала конвертируется в тип AnsiString, а потом выполняется сравнение.
3. При сравнении строк ShortString и PChar строка PChar конвертируется в ShortString, затем эти строки сравниваются.

Последнее правило таит в себе подводный камень, который иллюстрируется следующим примером:
Здесь формируется строка типа PChar, состоящая из 299 символов «A». Затем формируется строка ShortString, состоящая из 255 символов «А». Очевидно, что эти строки не равны, потому что имеют разную длину. Тем не менее, на экране появится «Равно».

Происходит это вот почему: строка PChar оказывается больше, чем максимально допустимый размер строки ShortString. Поэтому при конвертировании лишние символы просто отбрасываются. Получается строка длиной 255 символов, которая совпадает со строкой ShortString, с которой мы её сравниваем. Отсюда вывод: если строка ShortString содержит 255 символов, а строка PChar — более 255 символов, и её первые 255 символов совпадают с символами строки ShortString, операция сравнения ошибочно даст положительный результат, хотя эти строки не равны.

Избежать этой ошибки поможет либо явное сравнение длины перед сравнением строк, либо приведение одной из сравниваемых строк к типу AnsiString (второй аргумент при этом также будет приведён к этому типу). Следующий пример даёт правильный результат «Не равно»:
Учтите, что конвертирование в AnsiString — операция дорогостоящая в смысле процессорного времени (в этом примере будут выделены, а потом освобождены два блока памяти), поэтому там, где нужна производительность, лучше вручную сравнить длину, а ещё лучше — вообще по возможности избегать сравнения строк разных типов, так как без конвертирования это в любом случае не обходится.

Теперь зададимся глупым, на первый взгляд, вопросом: если мы приведём строку AnsiString к PChar, будут ли равны указатели? Проверим:
Вполне ожидаемый результат — «Равно». Можно, например, перенести строку из сегмента кода в динамическую память с помощью UniqueString — результат не изменится. Однако выводы делать рано. Рассмотрим следующий пример:
От предыдущего он отличается только тем, что строка S имеет пустое значение. Тем не менее, на экране мы увидим «Не равно». Связано это с тем, что приведение строки AnsiString к типу PChar на самом деле не является приведением типов. Это — скрытый вызов функции _LStrToPChar, и сделано это для того, чтобы правильно обрабатывать пустые строки.

Значение » (пустая строка) для строки AnsiString означает, что память для неё вообще не выделена, а указатель имеет значение nil. Для типа PChar пустая строка — это ненулевой указатель на символ #0. Нулевой указатель также может рассматриваться как пустая строка, но не всегда — иногда это рассматривается как отсутствие какого бы то ни было значения, даже пустого (аналог NULL в базах данных). Чтобы решить это противоречие, функция _LStrToPChar проверяет, пустая ли строка хранится в переменной, и, если не пустая, возвращает этот указатель, а если пустая, то возвращает не nil, а указатель на символ #0, который специально для этого размещён в сегменте кода. Таким образом, в случае пустой строки PChar(S) <> Pointer(S), потому что приведение строки AnsiString к указателю другого типа — это нормальное приведение типов без дополнительной обработки значения.

Побочное изменение

Из-за того, что две одинаковые строки AnsiString разделяют одну область памяти, на неожиданные эффекты можно натолкнуться, если модифицировать содержимое строки в обход стандартных механизмов. Следующий код (пример SideChange) иллюстрирует такую ситуацию:
В этом примере требует комментариев процедура UniqueString. Она обеспечивает то, что счётчик ссылок на строку будет равен единице, т.е. для этой строки делается уникальная копия. Здесь это понадобилось для того, чтобы строка S1 хранилась в динамической памяти, а не в сегменте кода, иначе мы получили бы Access violation, как и во втором случае рассмотренного ранее примера Constants.

В результате работы этого примера на экран будет выедено не «Test», а «Fest», хотя значение S2, казалось бы, не должно меняться, потому что мы изменения, которые мы делаем, касаются только S1. Но более внимательный анализ подсказывает объяснение: после присваивания S2 := S1 счётчик ссылок строки становится равным двум, а сама строка разделяется двумя указателями: S1 и S2. Если бы мы попытались изменить непосредственно S1, сначала была бы создана копия этой строки, а потом были бы сделаны изменения в этой копии, а оригинал, на который указывала бы S2, остался бы без изменений. Но, использовав PChar, мы обошли механизм копирования, поэтому строка осталась в единственном экземпляре, и изменения затронули не только S1, но и S2.

В данном примере всё достаточно очевидно, но в более сложных случаях разработчик программы может и не подозревать, что строка, с которой он работает, разделяется несколькими переменными. Справка Delphi советует сначала обеспечить уникальность копии строки с помощью UniqueString и только потом работать с ней через PChar, если в этом есть необходимость.

Нулевой символ в середине строки

Хотя символ #0 и добавляется в конец каждой строки AnsiString, он уже не является признаком её конца, т.к. длина строки хранится отдельно. Это позволяет размещать символы #0 и в середине строки. Но нужно учитывать, что полноценное преобразование такой строки в PChar невозможно — это иллюстрируется примером Zero (в этом примере на форме одна кнопка и две метки):
В первую метку будет выведено число 9 (длина исходной строки), во вторую — 4. Мы видим, что при копировании одной строки AnsiString в другую символ #0 в середине строки — не помеха (вызов UniqueString добавлен для того, чтобы обеспечить реальное копирование строки, а не только копирование указателя). А вот как только мы превращаем эту строку в PChar, информация о её истинной длине теряется, и при обратном преобразовании компилятор ориентируется на символ #0, и строка обрубается.

Потеря куска строки после символа #0 происходит всегда, когда есть преобразование ShortString или AnsiString в PChar, даже неявное. Например, все API-функции работают с нуль-терминированными строками, а визуальные компоненты — просто обёртки над этими функциями, поэтому вывести с их помощью на экран строку, содержащую #0, целиком невозможно.

Но главный подводный камень, связанный с символом #0 в середине строки, заключается в том, что целый ряд стандартных функций для работы со строками AnsiString на самом деле используют API-функции (или даже библиотечные функции Delphi, предназначенные для работы с PChar), что приводит к игнорированию «хвоста» после #0. Следующий код (пример ZeroFind) иллюстрирует эту проблему:
Хотя символ «Z» присутствует в строке, в которой производится поиск, на экран будет выеден «0», что означает отсутствие искомой подстроки. Это связано с тем, что функция AnsiPos использует функции StrPos и CompareString, предназначенные для работы со строками PChar, поэтому поиск за символом #0 не производится. Если заменить в этом примере функцию AnsiPos на Pos, которая работает с типом AnsiString должным образом, на экран будет выведено правильное значение «3».

Описанные проблемы заставляют очень осторожно относиться к использованию символа #0 в середине строк AnsiString — это может стать источником неожиданных проблем.

Строки в записях

Поля в записях могут иметь любой строковый тип без дополнительных ограничений. Однако следует учитывать, что, в отличие от полей простых типов, значения полей типа PChar и AnsiString лежат вне пределов структуры, причем в случае AnsiString это не так бросается в глаза, т.к. вручную выделять и освобождать память не приходится. Это может подложить неприятный сюрприз, если работать со структурой как цельным блоком данных. Чаще всего проблема появляется при записи структуры в поток, файл и т.п. В этом случае записывается только значение указателя, которое не имеет никакого смысла для того, кто потом эти данные читает: такой указатель указывает либо в никуда, либо на данные, никакого отношения к строке не имеющие.

Для иллюстрации этой проблемы, а также методов её решения нам понадобятся два проекта: RecordRead и RecordWrite (лежат в папке RecordReadWrite). Обойтись одним проектом здесь нельзя — указатель, переданный в пределах проекта, остаётся корректным, поэтому проблема маскируется. В проекте RecordWrite три кнопки, соответствующие трём методам сохранения записи в поток TFileStream (в файлы Method1.stm, Method2.stm и Method3.stm соответственно). В три целочисленных поля заносятся текущие час, минута, секунда и сотая доля секунды, строка — произвольная, введённая пользователем в Edit1. Файлы пишутся в текущую папку. В проекте RecordRead три кнопки соответствуют трём методам чтения (каждый — из своего файла). Сначала рассмотрим первый метод — как делать ни в коем случае нельзя.

В проекте RecordWrite:
В проекте RecordRead:
Примечание: В проекте RecordRead объявлена такая же запись TMethod1Record, описание которой во втором случае для краткости опущено.

Запись в файл происходит нормально, но при чтении в строке, отмеченной звёздочкой, скорее всего, возникает исключение Access violation (в некоторых случаях исключения может не быть, но вместо сообщения будет выведен мусор). Причину этого мы уже обсудили выше — указатель Msg, действительный в контексте процесса RecordWrite, не имеет смысла в процессе RecordRead, а сама строка передана не была. Без ошибок этим методом можно передать только пустую строку, потому что пустой строке соответствует указатель nil, имеющий одинаковый смысл во всех процессах. Однако метод передачи строк, умеющий передавать только пустые строки, имеет весьма сомнительную ценность с практической точки зрения.

Самый простой способ исправить ситуацию — изменить тип поля Msg на ShortString. Больше ничего в приведённом коде менять не придётся. Однако использование ShortString имеет два недостатка. Во-первых, длина строки в этом случае ограничена 255-ю символами. Во-вторых, если длина строки меньше максимально возможной, часть памяти, выделенной для структуры, не будет использована. Если средняя длина строки существенно меньше максимальной, то таких неиспользуемых кусков в потоке будет много, т.е. файл окажется неоправданно раздут. Это всегда плохо, а в некоторых случаях — вообще недопустимо, поэтому ShortString можно посоветовать только в тех случаях, когда строки имеют примерно одинаковую длину (напомним, что ShortString позволяет ограничить длину строки меньшим, чем 255, числом символов — в этом случае поле будет занимать меньше места).

С одним из этих недостатков можно бороться: если использовать в записи не ShortString, а статический массив типа Char, можно передавать строки большей, чем 255 символов, длины. Второй метод демонстрирует этот способ.

В проекте RecordWrite:
В проекте RecordRead:
Константа MsgLen задаёт максимальную (вместе с завершающим нулём) длину строки. В приведённом примере она взята достаточно маленькой, чтобы наглядно продемонстрировать, что данный метод имеет ограничения на длину строки. Переделки по сравнению с кодом предыдущего метода минимальны: при записи для копирования значения Edit1.Text вместо присваивания нужно использовать функцию StrPLCopy. В коде RecordRead изменений (за исключением описания самой структуры) вообще нет — это достигается за счёт того, что массив Char считается компилятором совместимым с PChar, а выражения типа PChar могут быть присвоены переменным типа AnsiString — конвертирование выполнится автоматически.

Однако проблему неэффективного использования памяти мы таким образом не решили. Более того, мы до конца не решили и проблему максимальной длины: хотя ограничение на длину строки теперь может быть произвольным, всё равно оно должно быть известно на этапе компиляции. Чтобы полностью избавиться от этих проблем, необходимо вынести строку за пределы записи и сохранять её отдельно, вместе с длиной, чтобы при чтении сначала читалась длина строки, затем выделялась для неё память, и в эту память читалась строка. Именно так работает третий метод.

В проекте RecordWrite:
В проекте RecordRead:
Наконец-то мы получили код, который безошибочно передаёт строку, не имея при этом ограничений длины (кроме ограничения на длину AnsiString) и не расходуя понапрасну память. Правда, сам код получился сложнее. Во-первых, из записи исключено поле типа string, и теперь эту запись можно без проблем читать и писать в поток. Во-вторых, в поток после неё записывается длина строки. В-третьих — сама строка.

Параметры вызова методов ReadBuffer и WriteBuffer для чтения/записи строки требуют дополнительного комментария. Метод WriteBuffer пишет в поток ту область памяти, которую занимает указанный в качестве первого параметра объект. Если бы мы указали саму переменную Msg, то записалась бы та часть памяти, которую занимает эта переменная, т.е. сам указатель. А нам не нужен указатель, нам нужна та область памяти, на которую он указывает, поэтому указатель нужно разыменовать с помощью оператора «^». Но просто взять и применить этот оператор к переменной Msg нельзя — с точки зрения синтаксиса она не является указателем. Поэтому приходится сначала приводить её к указателю (здесь подошёл бы любой указатель, не обязательно нетипизированный). То же самое относится и к ReadBuffer: чтобы прочитанные данные укладывались не туда, где хранится указатель на строку, а туда, где хранится сама строка, приходится использовать такую же конструкцию. И обратите внимание, что прежде чем читать строку, нужно зарезервировать для неё память с помощью SetLength.

Вместо приведения строки к указателю с последующим его разыменованием можно было бы использовать другие конструкции:
и
Это даёт нужный результат и даже является более наглядным: действительно, при чтении и записи мы используем ту область памяти, которая начинается с первого символа строки, т.е. ту, где хранится сама строка. Но этот способ менее производителен: когда мы обращаемся к отдельным символам строки, производится неявный вызов UniqueString, потому что компилятор не знает, что мы будем делать с этим символом и на всякий случай подстраховывается, чтобы не было побочных изменений других строк. В нашем случае мы и так защищены от побочных изменений (при записи строка не меняется, при чтении она и так уникальна — это обеспечивает SetLength), поэтому мы вполне можем обойтись без этой в данном случае излишней опеки со стороны компилятора.

Примечание: Если сделать MsgLen не независимой переменной, а полем записи, можно сэкономить на одном вызове ReadBuffer и WriteBuffer.

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

Выше мы говорили о том, что копирование записей, содержащих поля типа AnsiString, в рамках одного процесса маскирует проблему, т.к. указатель остаётся допустимым и даже (какое-то время) правильным. Но сейчас с помощью приведённого ниже кода (пример RecordCopy) мы увидим, что проблема не исчезает, а просто становится менее заметной.
На экране вместо ожидаемого «Hello. » появится «Good bye». Это происходит вот почему: процедура Move осуществляет простое побайтное копирование одной области памяти в другую, механизм изменения счётчика ссылок при этом не срабатывает. В результате менеджер памяти не будет знать, что после завершения локальной процедуры CopyRecord остаются ссылки на строку «Hello. «. Память, выделенная этой строке, освобождается. Но Rec.Str продолжает ссылаться на эту уже освобождённую память. Для строки S выделяется свободная память — та самая, где раньше была строка LocalRec.Str. А так как Rec.Str продолжает ссылаться на эту область памяти, поэтому обращение к ней даёт строку «Good bye», которая теперь там размещена.

Обратите внимание на последнюю строку — приведение Rec.Str к типу Pointer и обнулению. Это сделано для того, чтобы менеджер памяти не пытался финализировать строку Rec.Str после завершения процедуры, иначе он попытается освободить память, которая уже освобождена, и возникнет ошибка.

Чтобы показать, насколько коварна эта ошибка, рассмотрим следующий код (из того же примера):
От предыдущего случая он отличается только тем, что в нём нет вызовов UniqueString, и строки указывают на константы в сегменте кода, которые никогда не удаляются. На экране получаем вполне ожидаемое «Привет!». Обнулять указатель здесь уже нет смысла, потому что освобождать константу менеджер памяти не будет. Так ошибка оказалась скрытой.

Продолжим наши эксперименты. Запустим пример RecordCopy и понажимаем попеременно кнопки Button1 и Button2. Мы видим, что результат не зависит от порядка, в котором мы нажимаем кнопки.

Модифицируем код: в локальной процедуре обработчика Button1Click уберём из строки «Hello. » восклицательные знаки, сократив её до «Hello». Теперь можно наблюдать интересный эффект: если после запуска нажать сначала Button1, то никаких изменений мы не заметим. А вот если кнопка Button2 будет нажата раньше, чем Button1, то при последующих нажатиях Button1 никаких видимых эффектов не будет. Это связано с тем, что теперь строка «Hello» не равна по длине строке «Good bye», поэтому разместится ли «Good bye» в том же месте памяти, где раньше была «Hello», или в каком-то другом, зависит от истории выделения и освобождения памяти. Если мы начинаем «с чистого листа», память после строки «Hello» остаётся свободной, поэтому туда можно поставить более длинную строку. А вот если раньше память уже выделялась и освобождалась (внутри методов TLabel), то тот кусочек свободной памяти, который достаточен для «Hello», слишком мал для «Good bye», и эта строка размещается в другом месте. А там, куда указывает Rec.Str, остаётся мусор, работать с которым нормально невозможно, поэтому при попытке присвоить это свойству Label1.Caption последнее не меняется.

Примечание: Если увеличить длину строки «Привет!» хотя бы на один символ, чтобы она была не короче, чем «Good bye» (или наоборот, сократить его так, чтобы оно стало короче «Hello»), мы снова увидим, что порядок нажатия кнопок не влияет на результат. Это происходит потому, что строка «Hello» размещается там, где раньше была строка «Привет!», а вот «Good bye» там уже не помещается. Если же обе строки там помещаются (или обе не помещаются), они снова оказываются в одной области памяти. Внимательный читатель может спросить: а при чём здесь длина строки «Привет!», если эта строка хранится в сегменте кода и никогда не освобождается? Дело в том, что когда мы присваиваем эту строку свойству Label1.Caption, внутри методов TLabel происходит её перенос в динамическую память для внутренних нужд этого класса.

Даже на таком простом примере видно, насколько коварна эта ошибка и как незначительные изменения в коде могут кардинально изменить её проявления. Между тем приведённый здесь код — плод долгого «приручения» этой ошибки, чтобы она всегда проявлялась предсказуемым образом. Но даже сейчас мы не можем дать полной гарантии, что у кого-то из читателей из-за какой-то неучтённой мелочи не возникнет ситуация, когда эта ошибка проявляется как-то по-другому (хотя вероятность этого мала). В реальных проектах всё гораздо сложнее, и поведение программы из-за этой ошибки может стать таким неожиданным, а проявление этой ошибки — настолько далёким от того места, где она сделана, что впору будет прыгать вокруг компьютера с бубном, изгоняя бесов. Чтобы не оказаться в таком положении, нужно очень аккуратно работать со строками (а также с другими автоматически финализируемыми типами: динамическими массивами, интерфейсами, вариантами), чтобы тот код, который неявно генерирует компилятор, не оказался в тупике. Чаще всего проблемы возникают при побайтном копировании переменной типа AnsiString (не обязательно в составе записи) или работе с ней как с указателем другого типа. Это не значит, что приводить AnsiString к другим указателям категорически нельзя — выше мы уже делали это, и вполне успешно. Но применяя любой низкоуровневый механизм к таким строкам, разработчик должен чётко представлять, как это отразится на внутренних механизмах работы с ними. Иначе — вот такая непонятная ошибка.

Использование ShareMem

То, что мы сейчас рассмотрим — это даже не подводный камень, это то, что в форумах обычно называется «грабли». Всё новые и новые программисты с завидным упорством наступают на эти грабли и получают по лбу, хотя, казалось бы, таблички, предупреждающие об опасности, вокруг этих граблей стоят, только не ленись читать.

Итак, создаём новую динамически компонуемую библиотеку (DLL). Delphi делает нам следующую заготовку:
Самое важное здесь — комментарий. Его следует внимательно прочитать и осознать, а главное — выполнить эти советы, иначе при передаче строк AnsiString между DLL и программой вы будете получать Access violation в самых неожиданных местах. Почему-то многие им пренебрегают, а потом бегут с вопросами в разные форумы, хотя минимум внимательности и отсутствие снобизма по отношению «к этим, из Borland’а, которые навставляли тут никому не нужных комментариев» могли бы уберечь от ошибки.

Для начала разберёмся, что вообще является источником ошибки. Менеджер памяти Delphi работает следующим образом: он берёт у системы большие блоки памяти, а потом по мере необходимости выделяет их по частям. Это позволяет избежать частых выделений памяти системными средствами и тем самым повышает производительность. Следствием этого становится то, что менеджер памяти должен иметь информацию о том, какими блоками он распределил полученную от системы память между различными потребителями.

Менеджер памяти реализуется модулем System. Так как DLL компонуется отдельно от использующего её exe-файла, у неё будет своя копия кода System и, следовательно, свой менеджер памяти. И если объект, память для которого была выделена в коде основного модуля программы, попытаться освободить в коде DLL, получится, что освобождать память будет совсем не тот менеджер, который её выделил. А сделать он этого не сможет, т.к. не обладает информацией о выделенном блоке. Результат — ошибка (скорее всего, Access violation при выходе из процедуры). А при работе со строками AnsiString память постоянно выделяется и освобождается, поэтому, попытавшись работать с одной и той же строкой и в главном модуле, и в DLL, мы получим ошибку.

Теперь, когда мы поняли, почему возникает проблема, разберёмся, как ShareMem её решает. Delphi предоставляет возможность заменить стандартный менеджер памяти своим: для этого нужно написать функции выделения, освобождения и перераспределения памяти и сообщить их адреса через процедуру SetMemoryManager — после этого все функции для работы с динамической памятью будут работать через эти функции. Именно это и делает ShareMem: в секции инициализации этого модуля содержится код, заменяющий функции работы с памятью своими, причём эти функции находятся во внешней библиотеке BORLNDMM.DLL. Получается, что и библиотека, и главный модуль работают с одним менеджером памяти, что решает описанные выше проблемы.

Если менеджер памяти попытаться поменять не в самом начале программы, ему придётся освобождать память, которую успел выделить предыдущий менеджер памяти, что приведёт к той же самой проблеме. Поэтому заменить менеджер памяти нужно до того, как будет выполнена первая операция по её выделению. Отсюда возникает требование вставлять ShareMem первым модулем в dpr-файлах главного модуля и DLL — чтобы его секция инициализации была первым выполняемым программой кодом.

Кстати, к совету использовать вместо AnsiString PChar, чтобы избавиться от необходимости использования ShareMem, данному в комментарии, следует относится осторожно: если мы попытаемся, например, вызвать StrNew в основной программе, а StrDispose — в DLL, то получим ту же проблему. Вопрос не в типах данных, а в том, как манипулировать памятью.

Необходимость распространять со своей программой ещё и библиотеку BORNDMM.DLL иногда воспринимается как неудобство (хотя речь идёт только о тех ситуациях, когда программа и так вынуждена использовать другие DLL), поэтому существуют менеджеры памяти сторонних разработчиков, которые решают ту же проблему, что и ShareMem, но без использования библиотеки. Эти менеджеры памяти просто все запросы по работе с памятью передают системному менеджеру памяти, что избавляет их от необходимости хранить какую-либо информацию о выделенных блоках. Такое решение вполне работоспособно, но менее эффективно, особенно если нужно многократно выделять и освобождать небольшие блоки памяти.

Следует также упомянуть о ещё одной альтернативе передачи строк в DLL — типе WideString. Этот тип хранит строку в кодировке Unicode и является, по сути, обёрткой над системным типом BSTR. Работать с WideString так же просто, как и с AnsiString, перекодирование из ANSI в Unicode и обратно выполняется автоматически при присваивании значения одного типа переменной другого. В целях совместимости с COM и OLE для работы с памятью для строк WideString используется специальный системный менеджер памяти (через API-функции SysAllocString, SysFreeString и т.п.), поэтому передавать эти строки из DLL в главный модуль и обратно можно совершенно безопасно даже без ShareMem. Правда, при этом не стоит забывать о расходовании процессорного времени на перекодировку, если основная работа идёт не с Unicode, а с ANSI.

Отметим одну ошибку, которую делают новички, прочитавшие комментарий про ShareMem, но не умеющие работать с PChar. Они пишут, например, такой код для функции, находящейся в DLL и возвращающей строку:
Такой код компилируется и даже, за редким исключением, даёт ожидаемый результат. Но тем не менее, в этом коде грубая ошибка. Указатель, возвращаемый функцией, указывает на область памяти, которая считается свободной — после того как переменная S вышла за пределы области видимости, память, которую занимала эта строка, освободилась. Менеджер памяти может в любой момент вернуть эту память системе (тогда обращение к ней вызовет Access violation) или задействовать для других целей (тогда новая информация перетрёт содержащуюся там строку). Проблема маскируется тем, что обычно результат используется немедленно, до того как менеджер памяти что-то сделает с этим блоком. Тем не менее, полагаться на это и писать такой код не стоит.

Под использованием PChar в комментарии имеется ввиду использование его таким образом, как он используется в API-функциях: программа выделяет память для буфера, указатель на этот буфер передаёт в DLL как PChar, а DLL только заносит в этот буфер требуемое значение.

Класс TStrings — операции со строковыми данными в Делфи

Класс TStrings является базовым классом для операций со строковыми данными. Этот класс представляет собой контейнер для строк (коллекцию или массив строк). Для операций со строками класс TStrings предоставляет соответствующие свойства и методы. От класса TStrings происходит большое количество производных классов, например, TStringList, которые могут использоваться для задания различных типов строк.

Визуальные компоненты, способные работать со списком строк, имеют свойства, которые являются массивами строк, содержащихся в этих компонентах. Например, для списков ListBox и DBListBox и для групп зависимых переключателей RadioGroup и DBRadioGroup таким свойством является Items, а для многострочных редакторов Memo и DbMemoLines.

Указанные свойства для визуальных компонентов ListBox и Memo доступны при разработке и при выполнении приложения, а для визуальных компонентов DBListBox и DBMemo, связанных с данными, — только при выполнении приложения.

Особенности класса TStrings

Рассмотрим особенности и использование класса TStrings на примере свойства Items списков. Работа с другими объектами типа TStrings происходит аналогично.

Каждый элемент списка является строкой, к которой можно получить доступ по ее номеру в массиве строк Items. Отсчет элементов списка начинается с нуля. Для обращения к первому элементу нужно указать Items[0], ко второму— Items[1], к третьему — Items[2] и т. д. При операциях с отдельными строками программист должен контролировать номера строк в списке и не допускать обращения к несуществующему элементу. Например, если список содержит три строки, то попытка работы с десятой строкой приведет к исключению.

Свойство Count

Свойство Count типа Integer задает число элементов в списке. Поскольку первый элемент списка имеет нулевой номер, то номер последнего элемента равен Count-1.

Например, присваивание элементам списка ListBox1 новых значений может быть реализовано так:

Методы Add и Insert

Методы Add и Insert служат для добавления/вставки строк в список. Функция Add (const S: string): integer добавляет заданную параметром S строку в конец списка, а в качестве результата возвращает положение нового элемента в списке. Процедура Insert (Index: Integer; const S: String) вставляет строку S в позицию с номером, определяемым параметром index. При этом элементы списка, находившиеся до операции вставки в указанной позиции и ниже, смещаются вниз.

В приводимой далее процедуре к комбинированному списку СomboBox1 добавляется строка Нажата кнопка Button1:

Заполнение списка с помощью методов AddStrings и AddObject

Для заполнения списка можно использовать методы AddStrings и AddObject. Метод AddStrings позволяет при вызове увеличить содержимое списка более чем на один элемент.

Процедура AddStrings (strings: TStrings) добавляет в конец списка группу строк, определяемую параметром Strings. Класс TStrings позволяет хранить строки и ссылки на объектыпроизвольного типа.

Функция AddObject (const S: String; AObject: TObject): Integer добавляет в конец списка строку S и связанную с ней ссылку на объект, указываемую параметром AObject.

Рассмотрим следующий пример заполнения списка:

Здесь список TS заполняется перечнем названий страниц блокнота PageControl1 при создании формы Form1. Вместе с названиями запоминаются ссылки на страницы. Страницы блокнота имеют тип TTabSheet. Знание типа необходимо при последующей работе с задаваемыми посредством ссылок объектами, в частности, для корректного выполнения операции, такой как программное переключение страниц управляющего элемента PageControl1. Вместо переменной TS можно использовать другой список подходящего типа, например, список компонента ListBox, доступный через свойство Items.

В процессе создания приложений иногда необходимо, чтобы один список содержал те же данные, что и другой. Такое согласование списков достаточно просто выполняется с помощью методов AddStrings и Assign. Последний из методов также позволяет при вызове увеличить содержимое списка более чем на один элемент. Проверить, требуется операция согласования списков или нет, можно с помощью метода Equals.

Процедура Assign

Процедура Assign (Source: TPersistent) присваивает один объект другому, при этом объекты должны иметь совместимые типы. В результате выполнения процедуры информация копируется из одного списка в другой с заменой содержимого. Если размеры списков (число элементов) не совпадают, то после замены число элементов заменяемого списка становится равным числу элементов копируемого списка.

Функция Equals

Функция Equals (Strings: TStrings): Boolean используется для определения, содержат ли два списка строк одинаковый текст. Если содержимое списков совпадает, то функция возвращает значение True, в противном случае— значение False. Содержимое списков одинаково, если списки равны по длине и совпадают все их соответствующие элементы.

В приведенном далее примере производится согласование двух списков по содержанию:

В случае, если списки не совпадают, содержимое списка ListBox1 копируется в список ListBox2, в результате чего содержимое списков становится одинаковым.

Удаление элементов списка с помощью методов Delete и Сlear

Для удаления элементов списка используются методы Delete и Сlear. Метод Delete (index: integer) удаляет элемент с номером, заданным параметром index. При попытке удаления несуществующей строки сообщение об ошибке не выдается, но метод Delete не срабатывает.

при нажатии кнопки Button2 из комбинированного списка ComboBox1 удаляется пятая строка.

Метод Сlear очищает список, удаляя все его элементы в следующей процедуре:

при нажатии кнопки btnClearPersonalList очищается список lbPersonal.

Процедура Move

Процедура Move (Curindex, NewIndex: integer) перемещает элемент из позиции с номером CurIndex в новую позицию с номером NewIndex. Если указанный номер выходит за пределы списка, то возникает исключение.

Поиск элементов в списке процедурой IndexOf

Поиск элемента в списке можно выполнить с помощью метода IndexOf. Процедура IndexOf (const S: string): integer определяет, содержится ли строка S в списке. В случае успешного поиска процедура возвращает номер позиции найденной строки в списке; если строковый элемент не найден, то возвращается значение −1.

Работа с текстовыми файлами с помощью методов SaveToFile и LoadFromFile

У класса TStrings есть методы SaveToFile и LoadFromFile, позволяющие непосредственно работать с текстовыми файлами. Эти методы предоставляют возможность сохранения строк списка в текстовом фате на диске и последующего чтения строк из этого файла. Символы файла кодируются в системе ANSI.

Процедура SaveToFile (const FileName: String) сохраняет строковые элементы списка в файле FileName. Если заданный файл отсутствует на диске, то он создается. В последующем сохраненные строки можно извлечь из файла, используя метод LoadFromFile. Например:

Здесь содержимое списка ListBox3 записывается в файл names.txt каталога C:\COMPANY.

Процедура LoadFromFile (const FileName: String) заполняет список содержимым указанного текстового файла, при этом предыдущее содержимое списка стирается. Если заданный файл отсутствует на диске, то возникает исключение.

Пример заполнения списка содержимым файла:

Файл personal.txt содержит фамилии сотрудников организации. При запуске приложения содержимое этого файла загружается в комбинированный список ComboВох2.

При конструировании приложения изменение списка строк выполняется с помощью строкового редактора String List Editor.

Его можно вызвать из окна Инспектора объектов двойным щелчком мыши в области значения свойства типа TStrings (например, в области значения свойства Items списка ListBox).

Строковый редактор позволяет добавлять строки в список, удалять их из списка и изменять содержимое имеющихся строк.

Илон Маск рекомендует:  Информационные блоки предупреждений
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL