PWideString — Тип Delphi

Delphi WideString и Delphi 2009+

Я пишу класс, который сохранит широкие строки в двоичном файле. Я использую Delphi 2005 для этого, но позже приложение будет перенесено на Delphi 2010. Я чувствую себя очень неуверенным здесь, может кто-то подтвердить это:

Delphi 2005 WideString — это тот же тип, что и Delphi 2010 String

Delphi 2005 WideString char, а также Delphi 2010 String char гарантируется всегда размером 2 байта.

При использовании всех форматов Unicode я не хочу, чтобы один из символов в моей строке внезапно попадал в 3 байта или что-то в этом роде.

Изменить: Нашел это: «Я действительно сказал UnicodeString, а не WideString. WideString все еще существует и не изменяется. WideString выделяется диспетчером памяти Windows и должен использоваться для взаимодействия с объектами COM WideString сопоставляет непосредственно с BSTR-типом в COM.» на http://www.micro-isv.asia/2008/08/get-ready-for-delphi-2009-and-unicode/

Теперь я еще более смущен. Итак, Delphi 2010 WideString отличается от Delphi 2005 WideString ? Должен ли я использовать UnicodeString вместо этого?

Изменить 2: Нет типа UnicodeString в Delphi 2005. FML.

Для вашего первого вопроса: WideString не совсем тот же тип, что и D2010 string. WideString — это тот же тип COM BSTR, что и всегда. Он управляется Windows, без подсчета ссылок, поэтому он копирует весь BSTR каждый раз, когда вы его передаете.

UnicodeString , который по умолчанию является строкой в D2009 и включен, является в основном версией AnsiString UTF-16, которую все мы знаем и любим. Он получил счетчик ссылок и управляется компилятором Delphi.

Во втором случае тип char по умолчанию теперь WideChar , который является тем же самым символом, который всегда использовался в WideString . Это кодировка UTF-16, 2 байта на char. Если вы сохраните данные WideString в файл, вы можете без проблем загрузить его в UnicodeString . Разница между этими двумя типами связана с управлением памятью, а не с форматом данных.

Как упоминалось ранее, тип данных string (фактически UnicodeString) в Delphi 2009 и выше не эквивалентен типу данных WideString в предыдущих версиях, но формат содержимого данных одинаков. Оба они сохраняют строку в UTF-16. Поэтому, если вы сохраняете текст с помощью WideString в более ранних версиях Delphi, вы должны иметь возможность правильно его читать, используя строковый тип данных в последних версиях Delphi (2009 и выше).

Следует отметить, что производительность UnicodeString намного превосходит WideString. Поэтому, если вы собираетесь использовать один и тот же исходный код как в Delphi 2005, так и в Delphi 2010, я предлагаю вам использовать псевдоним типа string с условной компиляцией в вашем коде, чтобы вы могли использовать лучшее из обоих миров:

Теперь вы можете использовать MyStringType в качестве типа строки в исходном коде. Если компилятор является Unicode (Delphi 2009 и выше), то ваш тип строки будет алиасом типа UnicodeString, который представлен в Delphi 2009 для хранения строк Unicode. Если компилятор не является unicode (например, Delphi 2005), тогда ваш тип строки будет псевдонимом для старого типа данных WideString. И поскольку оба они являются UTF-16, данные, сохраненные в любой из версий, должны быть правильно прочитаны другим.

  • Delphi 2005 WideString имеет тот же тип, что и строка Delphi 2010

Это неверно — ex-строка Delphi 2010 содержит скрытое внутреннее поле кодовой страницы, но, вероятно, для вас это не важно.

  • Delphi 2005 WideString char, а также строка Delphi 2010 char гарантированно будет иметь размер 2 байта.

Это верно. В Delphi 2010 SizeOf (Char) = 2 (Char= WideChar).

Для строк unicode не может быть другой кодовой страницы — было введено поле кодовой страницы для создания общего двоичного формата для строк Ansi (для которых требуется поле кодовой страницы) и строки Unicode (это не нужно).

Если вы сохраняете данные WideString для потока в Delphi 2005 и загружаете одни и те же данные в строку в Delphi 2010, все должно работать нормально.

@Marco — строки Ansi и Unicode в Delphi 2009+ имеют общий двоичный формат (12-байтовый заголовок).

Кодовая страница UnicodeString CP_UTF16 = 1200;

  • Если вы хотите работать только с строками unicode внутри вашего модуля — используйте UnicodeString type (*).
  • Если вы хотите общаться с COM или с другими кросс-модульными целями, используйте тип WideString .

Вы видите, WideString — особый тип, поскольку он не является родным типом Delphi. Это псевдоним/оболочка для BSTR — тип системной строки, предназначенный для использования с COM или межмодульными сообщениями. Будучи юникодом — это просто побочный эффект.

С другой стороны, AnsiString и UnicodeString — являются родными типами Delphi, которые не имеют аналога на других языках. String является просто псевдонимом для AnsiString или UnicodeString .

Итак, если вам нужно передать строку в другой код — используйте WideString , в противном случае используйте либо AnsiString , либо UnicodeString . Простой.

(*) Для старого Delphi — просто место

где-то в вашем коде. Это исправление позволит вам написать тот же код для любой версии Delphi.

В то время как D2010 char всегда и ровно 2 байта, в символах UTF-16 присутствуют те же проблемы сложения и комбинирования символов, что и символы UTF-8. Вы не видите этого с узкими строками, потому что они основаны на кодировке, но с помощью строк unicode возможно (и в некоторых ситуациях общее) иметь аффективные, но невидимые символы. Примеры включают в себя знак порядка байтов (BOM) в начале файла или потока unicode, символы слева направо/справа налево и огромный диапазон сочетания акцентов. Это в основном затрагивает вопросы о том, «сколько пикселей будет шириной этой строки на экране» и «сколько букв находится в этой строке» (в отличие от «количества символов в этой строке» ), но также означает, t случайным образом измельчают символы из строки и предполагают, что они пригодны для печати. Такие операции, как «удалить последнюю букву из этого слова», становятся нетривиальными и зависят от используемого языка.

Вопрос о том, что «один из символов в моей строке внезапно имеет длину 3 байта», отражает небольшое недоверие к тому, как работает UTF. Возможно (и действительно) взять три байта в строке UTF-8, чтобы представить один печатный символ, но каждый байт будет действительным символом UTF-8. Скажем, письмо плюс два сочетания акцентов. Вы не получите символ в UTF-16 или UTF-32 длиной 3 байта, но может иметь длину 6 байтов (или 12 байтов), если он представлен с использованием трех кодовых точек в UTF-16 или UTF-32. Это приводит нас к нормализации (или нет).

Как преобразовать String в PWideString в Delphi для использования JNA

Я создаю DLL в Delphi и метод выглядит так:

Затем я пытаюсь вызвать этот метод, используя JNA lib в JAVA . Код, который вызывает метод в JAVA, выглядит следующим образом:

Но когда ответ на звонок является только символом, остальная часть текста не приходит.

Как я могу преобразовать String в PWideString без потери всех символов?

Obs : Я тоже пытался вернуть PWideChar, но результат тот же.

Тип PWideString Delphi является указателем на WideString . Если вы вернете такой указатель на Java, Java не будет знать, что с ним делать. PWideChar этого вам нужно будет вернуть необработанный PAnsiChar или PWideChar (предпочтительнее последний, так как строки Java являются Unicode), но тогда у вас PWideChar с управлением памятью.

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

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

Чтобы обойти это, вы должны перепроектировать функцию DLL так, чтобы она принимала буфер памяти в качестве входных данных, а затем вы можете использовать класс Memory JNA на стороне Java. Пусть DLL заполнит буфер памяти, а затем Java сможет вызвать метод Memory.getString() для чтения выходных данных в собственную string Java:

При этом вы могли бы вместо этого переключиться на JNI (см. Программирование JNI с помощью Delphi ), потому что тогда DLL может использовать собственный менеджер памяти Java для динамического выделения и возврата фактического string объекта Java, а затем Java может обычно освобождать его при необходимости:

Строковые типы в Delphi. Особенности реализации и использования

В этой статье будут освещены следующие вопросы:

  1. Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга
  2. Преобразование строк из одного типа в другой
  3. Некоторые приемы использования строк типа AnsiString:
    1. Функции для работы со строками о которых многие часто забывают или вовсе не знают
    2. Передача строк в качестве параметров
    3. Использование строк в записях
    4. Запись в файл и чтение из файла
    5. Использование строк в качестве параметров и результатов функций размещенных в DLL.

Ну что, интересно? Тогда поехали.

Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?

В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения, о которых я расскажу позднее. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции <$LONGSTRINGS ON/OFF>(короткая форма <$H+/->) или из окна настроек проекта – вкладка «Compiler» -> галочка «Huge strings». Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.

Поскольку, как вы узнаете в дальнейшем, типы ShortString и AnsiString имеют принципиальное отличие в реализации, то я вообще не рекомендую пользоваться логическим типом String без указания размера, если Вы, конечно, не пишете программ под Delphi 1. Если же Вы все-таки используете тип String, то я настоятельно рекомендую прямо в коде Вашего модуля указывать директиву компиляции, устанавливающую подразумеваемый Вами режим работы компилятора. Особенно если Вы используете особенности реализации соответствующего строкового типа. Если этого не сделать, то однажды, когда Ваш код попадет в руки другого программиста, не будет никакой гарантии того, что его компилятор будет настроен, так же как и Ваш.

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

Сразу же упомяну о различии между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.

Тоже самое касается и разницы между pChar и pWideChar.

Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк. Я не буду подробно останавливаться на этом способе кодирования, поскольку он применяется крайне редко. Лично я, ни разу не встречал программ использующих данный способ кодирования.

Знатоки Delphi вероятно мне сразу напомнят еще и о типах pChar (pWideChar) и array [. ] of Char. Однако, я считаю, что это не совсем строковые типы, но я расскажу и о них, поскольку они очень часто используются в сочетании со строковыми типами.

Итак, приведу основные характеристики строковых типов:

Тип Максимальный размер строки Размер переменной Объем памяти, требуемый для хранения строки
String[n] где 0 0, поэтому то Delphi и не освобождает память, занятую строкой.

Еще, этот счётчик используется и для разрешения проблем, связанных со следующей ситуацией:

Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки ‘abc123’. Однако, что же произойдёт когда выполниться оператор s2[1] := ‘X’? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на ‘X’. И как следствие, обе строки станут равными ‘Xbc123’. s1 то за что «страдает»? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: ‘abc123’ и ‘Xbc123’. Причем, s1 будет ссылаться на первую, а s2 на вторую.

При работе со строками определенными как константы, алгоритм работы несколько отличается. Приведу пример:

Казалось бы, при завершении работы процедуры, экземпляр строки ‘Вася’ должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку ‘Вася’. Для этого, ещё при компиляции, Delphi размещает экземпляр строки ‘Вася’ в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка ‘Вася’ – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, «выключает» нормальный алгоритм работы со «счётчиком ссылок». Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:=’X’), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.

К сожалению, этот алгоритм срабатывает не всегда. Но об этом, мы поговорим позже, при рассмотрении вопросов преобразования строковых типов.

Где же Delphi хранит «счётчик ссылок»? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки ‘abc’:

Байты с 1 по 4 Счётчик ссылок равный -1
Байты с 5 по 8 Длина строки равная 3
Байт 9 Символ ‘a’
Байт 10 Символ ‘b’
Байт 11 Символ ‘c’
Байт 12 Символ с кодом 0 (#0)

Для удобства работы с такой структурой, когда строковой переменной присваивается ссылка на эту строку, в переменную заносится адрес не начала этой структуры, а адрес её девятого байта. Т.е. адрес начала реальной строки (прямо как pChar). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру:

Смещение Размер Значение Назначение
-8 4 -1 Счётчик ссылок
-4 4 3 Длина строки
1 ‘a’
1 1 ‘b’
2 1 ‘c’
3 1 #0

С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – «-1», и положительные значения. 0 не используется.

Теперь, обратите внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке «нарастить» строку: s1 := s1 + ‘d’, компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Надеюсь, мы не скоро упрёмся в это ограничение. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.

Осталось рассказать ещё о нескольких особенностях переменных AnsiString. Важнейшей особенностью значений этого типа является возможность приведения их к типу Pointer. Это впрочем, естественно, ведь в «душе» они и есть указатели, как бы они этого не скрывали. Например, если описаны переменные: s :AnsiString и p :Pointer. То выполнение оператора p := Pointer(s) приведет к тому, что переменная p станет указывать на экземпляр строки. Однако, при этом, очень важно знать: счетчик ссылок этой строки не будет увеличен. Но об этом, мы поговорим чуть позднее.

Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в «никуда». Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.

Вот теперь, кажется, Вы знаете о строках все. Настала пора переходить к более интересной части статьи – как с этим всем жить?

Преобразование строк из одного типа в другой

Здесь, все как обычно, и просто и сложно.

Преобразование между «настоящими» строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:

В результате выполнения этого кода, в переменной s3 окажется строка ‘abc’, а не ‘abcdef’. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.

Сложности начинаются тогда, когда мы начинаем преобразовывать «настоящие» строковые типы в pChar. Непосредственное присваивание переменным типа pChar значений строк не допускается компилятором. На оператор p := s где p имеет тип pChar, а s :AnsiString, компилятор выдаст сообщение: «Incompatible types: ‘String’ and ‘PChar'» — несовместимые типы ‘String’ и ‘PChar’. Чтобы избежать такой ошибки, надо применять явное приведение типа: p := pChar(s). Так рекомендуют разработчики Delphi. В общем, они правы. Но, если вспомнить, как хранятся динамические строки — с нулем в конце, как и pChar. А еще и то, что к AnsiString применимо преобразование в тип Pointer. Станет очевидным, что всего, возможно целых три способа преобразования строки в pChar:

Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:

Чтобы Вы понимали причину такого явления, я опишу, как Delphi выполняет каждое из этих преобразований. В начале напомню, что переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:

Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.

Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.

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

Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес «персонального» экземпляра строки.

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

вызывает ошибку доступа к памяти при выполнении строки, помеченной 1. Вот и приходится применять эту функцию.

Здесь, поскольку параметр передаётся по значению, при вызове будет создана локальная копия переменной (указателя) Msg. Об этом я ещё расскажу ниже. Эта переменная, при завершении процедуры будет уничтожаться, что приведёт к освобождению «персональной» копии экземпляра переданной и преобразованной строки.

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

>0 если S1 > S2
255 символов приходится платить.

Если же тебе достаточно и 255 символов, то используй ShortString, или String[n].

Есть еще одно, на мой взгляд, замечание. Если Вы обратили внимание на тип переменной Len в моем примере, то возможно у Вас возник вопрос: А почему LongInt, а не Integer? Жаль если у Вас вопрос не возник – либо вы все знаете, либо ничего :). Для остальных поясню: дело в том, что тип LongInt фундаментальный тип, размер которого (4 байта) не будет меняться в последующих версиях Delphi. А тип Integer, это универсальный тип, размерность которого может меняться от версии к версии. Например, для 64-разрядных компьютеров он наверняка «вырастет» до 8-ми байт (64 бита). Лично мне, хочется, что бы файлы данных записанные моей старой версией программы могли быть нормально прочитаны более поздними версиями, возможно скомпилированными уже под 64-разрядной OS.

Использование строк в записях

Проблема возникает тогда, когда одно поле (или несколько полей) имеют тип динамической строки. В этом случае, часто возникает проблема схожая с проблемой записи динамической строки в файл – не все данные лежат в записи, динамические строки представлены в ней лишь указателями. Решается проблема также как и с записью строк в файл. Жаль только что тогда нельзя будет оперировать (записать/прочитать) целиком всей записью. Строки придется обрабатывать особо. Можно сделать, например, так:

Обратите внимание на то, что перед записью в поток я делаю так, что бы в поле f3 попал указатель Nil. Если этого не сделать, то в поток попадет адрес текущего экземпляра динамической строки. При чтении, он будет прочитан в поле f3. Т.е. поле f3 станет указывать на какое-то место в памяти. При выполнении SetLength, поскольку Delphi сочтет что текущее значение f3 лежит по указанному адресу, будет попытка интерпретировать лежащую там информацию как динамическую строку. Если же в поток записать Nil, то SetLength, никуда лезть не будет – экземпляра-то нет.

Использование строк в качестве параметров и результатов функций размещенных в DLL.

Многие, наверное, пытались хоть раз написать собственную Dll. Если при этом Вы использовали соответствующего мастера Delphi, то наверняка видели комментарий который он вставляет в начало файла библиотеки:

Общий смысл этого эпоса в том, что если Ваша Dll экспортирует хотя бы одну процедуру или функцию с типом параметра соответствующим любой динамической строке (AnsiString например), или функцию, возвращающую результат такого типа. Вы должны обязательно и в Dll, и в использующей ее программе, первым модулем в списке импорта (uses) указать модуль ShareMem. И как следствие, поставлять со своей программой и Dll еще одну стандартную библиотеку BORLNDMM.DLL.

Вы не задумывались над вопросами: «Зачем все эти сложности?»; «Что будет если этого не сделать?» и «Можно ли этого избежать?»; «Если да, то как?» Если не задумывались, то самое время сделать это.

Попробуем разобраться что будет происходить с экземплярами динамических строк в следующем примере:

Сначала, при выполнении процедуры X, функция IntToStr(100) создаст экземпляр динамической строки ‘100’, и ее адрес будет помещен в переменную Str. Затем, адрес этой переменной будет передан процедуре Y. В ней, при выполнении оператора s := s+’$’, будет создан экземпляр новый строки ‘100$’, Экземпляр старой строки ‘100’ станет не нужным и память, выделенная для него при создании, будет освобождена. Кроме того, при завершении процедуры X, будет освобождена и память, выделенная для строки ‘100$’, так как перестанет существовать единственная ссылка на нее — переменная Str.

Всё вроде бы хорошо. Но до тех пор, пока обе процедуры располагаются в одном исполняемом модуле (EXE-файле). Если например поместить процедуру Y в Dll, а процедуру X оставить в EXE, то будет беда.

Дело в том, что выделением и освобождением памяти для экземпляров динамических строк занимается внутренний менеджер памяти Delphi-приложения. Использовать стандартный менеджер Windows очень накладно. Он слишком универсален, и потому медленный, а строки очень часто требуют перераспределения памяти. Вот разработчики Delphi и создали свой. Он ведет списки распределенной и свободной памяти своего приложения. Так вот, вся беда в том, что Dll будет использоваться свой менеджер памяти, а EXE свой. Друг о друге они ничего не знают. Поэтому, попытка освобождения блока памяти выделенного не своим менеджером приведёт к серьезному нарушению в его работе. Причем, это нарушение может проявиться далеко не сразу, и довольно необычным образом.

В нашем случае, память под строку ‘100’ будет выделена менеджером EXE-файла, а освобождаться она будет менеджером DLL. То же произойдет и с памятью под строку ‘100$’, только наоборот.

Для преодоления этой проблемы, разработчики Delphi создали библиотеку BORLNDMM.DLL. Она включает в себя еще один менеджер памяти :). Использование же модуля ShareMem, приводит к тому, что он заменяет встроенный в EXE (DLL) менеджер памяти на менеджер расположенный в BORLNDMM.DLL. Т.е., теперь и EXE-файл и DLL, будут использовать один, общий менеджер памяти.

Здесь важно отметить то, что если какой-либо из программных модулей (EXE или DLL) не будут иметь в списке импорта модуля ShareMem, то вся работа пойдет насмарку. Опять будут работать несколько менеджеров памяти. Опять будет бардак.

Можно обойтись и без внешнего менеджера памяти (BORLNDMM.DLL). Но для этого, надо например заменить встроенный в DLL менеджер памяти, на менеджер, встроенный в EXE. Такое возможно. Есть даже соответствующая реализация от Emil M. Santos, называемая FastShareMem. Найти ее можно на сайте http://www.codexterity.com. Она тоже требует обязательного указания ее модуля FastShareMem в списках используемых модулей EXE и DLL. Но, она по крайней мере не требует таскать за собой ни каких дополнительных DLL’лек.

Ну вот, наконец-то и все. Теперь, Вы знаете о строках почти столько же как я :).

Конечно, этим тема не исчерпывается. Например, я ничего не рассказал о мультибайтовых строках (MBCS) используемых для мультиязыковых приложений. Может и еще что-то забыл рассказать. Но, не расстраивайтесь. Я свои знания получал, изучая книги, тексты своих и чужих программ, код сгенерированный компилятором, и т.п. Т.е., все из открытых источников. Значит это все доступно и Вам. Главное, чтобы Вы были любознательными, и почаще задавали себе вопросы «Как?», «Почему?», «Зачем?». Тогда во всем сможете разобраться и сами.

PWideString — Тип 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 годятся оба подхода.

Типы данных Delphi

Теперь обсудим типы данных Delphi, которые программист использует при написании программы. Любая программа на Delphi может содержать данные многих типов:

  • целые и дробные числа,
  • символы,
  • строки символов,
  • логические величины.

Целый тип Delphi

Библиотека языка Delphi включает в себя 7 целых типов данных: Shortint, Smallint, Longint, Int64, Byte, Word, Longword, характеристики которых приведены в таблице ниже.

Вещественный тип Delphi

Кроме того, в поддержку языка Delphi входят 6 различных вещественных типов (Real68, Single, Double, Extended, Comp, Currency), которые отличаются друг от друга, прежде всего, по диапазону допустимых значений, по количеству значащих цифр, по количеству байт, которые необходимы для хранения некоторых данных в памяти ПК (характеристики вещественных типов приведены ниже). Также в состав библиотеки языка Delphi входит и наиболее универсальный вещественный тип — тип Real, эквивалентный Double.

Символьный тип Delphi

Кроме числовых типов, язык Delphi располагает двумя символьными типами:

Тип Ansichar — символы c кодировкой ANSI, им ставятся в соответствие числа от 0 до 255;

Тип Widechar — символы с кодировкой Unicode, им ставятся в соответствие числа от 0 до 65 535.

Строковый тип Delphi

Строковый тип в Delphi обозначается идентификатором string. В языке Delphi представлены три строковых типа:

Тип Shortstring — присущ статически размещаемым в памяти ПК строкам, длина которых изменяется в диапазоне от 0 до 255 символов;

Тип Longstring — этим типом обладают динамически размещаемые в памяти ПК строки с длиной, ограниченной лишь объемом свободной памяти;

Тип WideString — тип данных, использующийся для того, чтобы держать необходимую последовательность Интернациональный символов, подобно целым предложениям. Всякий символ строки, имеющей тип WideString, представляет собой Unicode-символ. В отличие от типа Shortstring, тип WideString является указателем, который ссылается на переменные.

Логический тип Delphi

Логический тип соответствует переменным, которые могут принять лишь одно из двух значений: true, false. В языке Delphi логические величины обладают типом Boolean. Вам были представлены основные типы данных Delphi. Движемся дальше.

Delphi WideString и Delphi 2009+

Я пишу класс, который будет сохранять широкие строки в двоичный файл. Я использую Delphi 2005 для этого, но приложение будет позже портирована на Delphi 2010. Я чувствую себя очень уверены здесь, кто-то может подтвердить, что:

Delphi 2005 WideString является точно таким же типом , как Delphi 2010 String

Delphi 2005 WideString голец, а также Delphi 2010 String полукокс гарантированно всегда быть 2 байта.

Со всем Unicode форматирует там я не хочу, чтобы ударить с одним из символов в моей струне вдруг быть 3 байта в ширине или что-то подобное.

Изменить: Найдено это: «Я сказал UnicodeString, не WideString WideString до сих пор существует, и остается неизменным WideString выделяется менеджером памяти Windows , и следует использовать для взаимодействия с COM — объектами WideString карты непосредственно к типу BSTR в COM. «. в http://www.micro-isv.asia/2008/08/get-ready-for-delphi-2009-and-unicode/

Теперь я еще больше запутался. Таким образом, Delphi 2010 WideString это не то же самое , как Delphi 2005 WideString ? Должен ли я использовать UnicodeString вместо этого?

Изменить 2: Там нет UnicodeString типа в Delphi 2005. FML.

Для вашего первого вопроса: WideString не точно такой же тип , как D2010 в строке . WideString тот же тип COM BSTR , что он всегда был. Это управляется ОС Windows, без подсчета ссылок, поэтому он делает копию всей BSTR каждый раз , когда вы передаете его куда — нибудь.

UnicodeString , Который по умолчанию строка типа в D2009 и, в основном версия UTF-16 из AnsiString все мы знаем и любим. У него есть счетчик ссылок и управляется компилятором Delphi.

Во — вторых, по умолчанию char тип теперь WideChar , что одни и те же символы , которые всегда были использованы в WideString . Это UTF-16 кодирования, 2 байта на символ. Если сохранить данные WideString в файл, вы можете загрузить его в UnicodeString без проблем. Разница между этими двумя типами имеет дело с управлением памятью, а не формат данных.

Как уже упоминалось другие, строка (на самом деле UnicodeString) тип данных в Delphi 2009 и выше не является эквивалентом WideString типа данных в предыдущих версиях, но формат содержимого данных является то же самое. Оба из них сохранить строку в UTF-16. Так что если вы сохраните текст, используя WideString в более ранних версиях Delphi, вы должны быть в состоянии прочитать его правильно, используя строковый тип данных в последних версиях Delphi (2009 и выше).

Вы должны принять к сведению, что выполнение UnicodeString является способом выше, чем WideString. Так что если вы собираетесь использовать один и тот же исходный код как в Delphi 2005 и Delphi 2010, я предлагаю вам использовать тип строки псевдоним с помощью условной компиляции в коде, так что вы можете иметь лучшее из обоих миров:

Теперь вы можете использовать MyStringType в качестве типа строки в исходном коде. Если компилятор Unicode (Delphi 2009 и выше), то ваш тип строки будет псевдонимом типа UnicodeString, который вводится в Delphi 2009 для хранения строк Unicode. Если компилятор не Юникод (например, Delphi 2005), то ваш тип строки будет псевдонимом для старого типа данных WideString. И так как они оба являются UTF-16, данные, сохраненные на любой из версий должна быть прочитана другой правильно.

  1. Delphi 2005 WideString точно такой же тип, как Delphi 2010 строки

Это не так — бывший Delphi 2010 строки имеет скрытую внутреннее поле кодовой страницы — но, вероятно, это не имеет значения для вас.

  1. Delphi 2005 WideString голец, а также Delphi 2010 Строка полукокс гарантированно всегда 2 байта.

Это правда. В Delphi 2010 SizeOf (Char) = 2 (Char = WideChar).

Там не может быть другими кодовой юникод строк — поле кодового было введено, чтобы создать общий двоичный формат для обоего строк ANSI (что поле необходимо кодовое) и строку Unicode (которые не нужно).

Если сохранить данные WideString на поток в Delphi 2005 и загружать одни и те же данные в строку в Delphi 2010 все должно работать нормально.

@Marco — Ansi и Unicode строки в Delphi 2009+ имеют общий двоичный формат (заголовок 12 байт).

UnicodeString кодовая CP_UTF16 = 1200;

Я пишу класс, который будет сохранять широкие строки в двоичный файл.

Когда вы пишете класс в D2005 вы будете использовать WideString При переходе на D2010 WideString все еще будет действовать и работать должным образом. WideString в D2005 такое же, как WideString в D2010.

Тот факт, что String = WideString в D2010 нет необходимости рассматривать, так как компилятор имеет дело с этими вопросами легко.

Ваш вклад рутина, чтобы сохранить с (AString: String) нужна только одна линия, входящего в прок

В то время как D2010 символ всегда и ровно 2 байта, то же складной характер и сочетающие проблемы присутствуют в UTF-16 символов, как в UTF-8 символов. Вы не видите это с узкими строками, потому что они основаны кодовым, но с юникод строками возможно (и в некоторых случаях общих) иметь аффективные, но не видимые символы. Примеры включают в себя метку порядка байтов (BOM) в начале файла Юникода или потока, слева направо / справа осталось символов индикатора, а огромный диапазон сочетания акцентов. Это главным образом влияет на вопросы «сколько пикселей будет эта строка будет на экране» и «сколько букв в этой строке» (в отличии от «сколько символов в этой строке»), но также означает, что вы можете» т случайным образом измельчить символы из строки и предположит, что они для печати.

Вопрос о «один из символов в моей строке внезапно будучи длиной 3 байта» отражает немного confustion о том, как работает UTF. Возможно (и действует) принять три байта в строке UTF-8, чтобы представить один печатный знак, но каждый байт будет допустимым UTF-8 символов. Скажем, письмо плюс два сочетающие акцентов. Вы не получите символ в UTF-16 или UTF-32 быть длиной 3 байта, но это может быть 6 байт (или 12 байт) длиной, если она представлена ​​с помощью трех кодовых точек в кодировке UTF-16 или UTF-32. Что приводит нас к нормализации (или нет).

Но если вы дело только со строками в виде целых вещей, все это очень просто — вы просто строку, записать его в файл, а затем прочитать его обратно в Вам не придется беспокоиться о тонкой печати струнного дисплея. и манипуляции, что это все обрабатывается операционной системы и библиотек. Strings.LoadFromFile (имя) и Listbox.Items.Add (строка) работают точно так же в D2010 как в D2007, то юникода материал все прозрачно для вас в качестве программиста.

  • Если вы хотите работать с юникод строк внутри модуля только — использовать UnicodeString тип (*).
  • Если вы хотите общаться с COM или с другими целями кросс-модуль — использование WideString типа.

Вы видите, WideString это особый тип, так как это не родной тип Delphi. Это псевдоним / оболочка для BSTR — типа системы струн, intendent для использования с COM или кросс-модуля связи. Будучи юникода — это просто побочный эффект.

С другой стороны, AnsiString и UnicodeString — являются носителями типа Delphi, которые не имеют аналогов в других языках. String это всего лишь псевдоним либо AnsiString или UnicodeString .

Так что , если вам нужно передать строку в какой — то другой код — использование WideString , в противном случае — использовать либо AnsiString или UnicodeString . Просто.

(*) Для старого Delphi — просто место

где-то в вашем коде. Это исправление позволит вам написать один и тот же код для любой версии Delphi.

PWideString — Тип Delphi

цитата:
The representation of the classic AnsiString type was the following:
-8: Ref count
-4: length
String reference address: First char of string

In Delphi 2009 the representation for reference-counted strings becomes:
-12: Code page
-10: Elem size
-8: Ref count
-4: length
String reference address: First char of string

Так что вы неправы дважды — 1) что это было «в сильно раньшие времена»; 2) про ссылку на heap. Всё хранится вместе, никаких ссылок.

цитата: bislomet:
olivenoel
а какие из string (http://docwiki.embarcadero.com/RADStudio/XE3/en/String_Types) -ов Вы при этом имеете в виду?
Если речь идет о null-terminated — то они вполне себе user managable, а не automatic. И вполне могут утекать куда угодно.
Уточните!

FastMM (или кто там в ХЕ2), говорит unexpected memory leak UnicodeString x1.

Погуглила немного, оказывается такое имеет место быть, если строка является частью структуры (record). Но есть подозрение на одно место, где в массив AnsiChar присвается литеральное выражение (корое вроде по сути UnicodeString)

5. olivenoel , 26.06.2013 10:09
6. bislomet , 26.06.2013 11:46
Spirit-1
Вы заблуждаетесь. Лучше почитайте help к delphi xe3 (по ссылке, которю я давал выше)
Вы говорите об эпохе «Delphi до XE» — а это таки «сильно раньшие времена»

[pre|AnsiString represents a dynamically allocated string whose maximum length is limited only by available memory.

An AnsiString variable is a structure containing string information. When the variable is empty — that is, when it contains a zero-length string, the pointer is nil and the string uses no additional storage. When the variable is nonempty, it points to a dynamically allocated block of memory that contains the string value. This memory is allocated on the heap.
[/pre]

Добавление от 26.06.2013 11:47:

olivenoel
не путайте строку с array of char, это же не «C»

olivenoel
не путайте строку с array of char, это же не «C»

не буду Но вопрос остается, может ли «потеря» младших (старших) байтов при автоконвертации UnicodeString в AnsiString (такая конвертация тоже имеет место быть в виде

) вести к утечкам?

проблему вроде бы решила. Для одного из классов с Р. в поле и New (для поля) в конструкторе, не был переопределен деструктор, в котором должен быть Dispose для Р. -поля.

7. olivenoel , 26.06.2013 12:09
8. Spirit-1 , 26.06.2013 14:42
bislomet
Причём тут AnsiString? Я вам привёл кусок описания для нового юникодного (!) String. Обратите внимание, что на самом деле подход практически не поменялся. Добавились поля Code Page и Elem Size по сравнению со стрингами до D2009 (ссылаюсь на Канту опять же). Ну и если сравнивать с AnsiString, то, конечно, поле длинны стало четырёхбайтовым.
9. MBo , 26.06.2013 15:19
Spirit-1

Ну и если сравнивать с AnsiString, то, конечно, поле длинны стало четырёхбайтовым.

В Ansistring оно тоже было четырехбайтовым. Возможно, имелись в виду ShortString?

10. Spirit-1 , 26.06.2013 15:23
Да, насчёт длины ерунду сказал. Остальное всё в силе.
11. bislomet , 26.06.2013 15:52
Spirit-1
Вы опять-таки заблуждаетесь. Чуть дальше в том же help написано:
The UnicodeString type has exactly the same structure as the AnsiString type.

Т.е. все, что не является ShortString — устроено именно таким образом — по мнению разработчиков
12. Spirit-1 , 26.06.2013 16:14
bislomet
Попробуйте смотреть чуть дальше хэлпа от XE3.

Повторюсь, что начиная с версии D2009 эти два типа суть одно и то же, поскольку тип AnsiString определен как UnicodeString с фиксированной Code Page.
Я же сравниваю с AnsiString-ом, который был до 2009-ой Delphi. И привёл выдержку из книги — какая структура стринга была до 2009-й версии и какая стала после.

На всякий случай повторю:

цитата:
The representation of the classic AnsiString type was the following:
-8: Ref count
-4: length
String reference address: First char of string

In Delphi 2009 the representation for reference-counted strings becomes:
-12: Code page
-10: Elem size
-8: Ref count
-4: length
String reference address: First char of string

Читайте внимательно, если кроме дойча английским разумеете.

13. bislomet , 26.06.2013 17:38
Spirit-1
Повторюсь, что начиная с версии D2009 эти два типа суть одно и то же, поскольку тип AnsiString определен как UnicodeString с фиксированной Code Page.
Это утверждение никоим образом не противоречит моему и не подтверждает Ваше.
Они таки одинаковые — но обе структуры имеют указатель на heap..

Я как-то больше верю хелпу от разработчиков.
А вот с чтением проблемы скорее у Вас — иначе Вы бы заметили, что я Вам именно что английские ссылки приводил.

Добавление от 26.06.2013 17:50:

Spirit-1
таки вот что Вы имеете в виду: а вот то, что я имею в виду:Здесь: Long String Types (http://docwiki.embarcadero.com/RADStudio/XE4/en/Internal_Data_Formats)
Т.е. Вы говорите о том, как выглядит та структура, об указателе на которую говорю я

14. Imlb , 26.06.2013 19:32
bislomet
об указателе на которую говорю я

Так было всегда или с очень давних времен.

«перед строкой» было в сильно раньшие времена,

И продолжается до сих пор.

15. bislomet , 27.06.2013 08:50
Imlb
Так было всегда или с очень давних времен.

Не всегда, а только после появления в Delphi длинных строк
16. Spirit-1 , 27.06.2013 15:02
А разве в дельфях длинные строки не были с самой первой версии?
17. bislomet , 27.06.2013 15:05
Spirit-1
нет, долгое время макс. длина строки в delphi была 255 символов — то, что впоследствии стало называться ShortString. И вот там как-раз в нулевом байте хранилась длина строки.
Именно для совместимости с «теми еще» строками все нынешние строки в Delphi индексируются с 1.
18. Spirit-1 , 27.06.2013 17:37
bislomet
Посмотрел. Действительно, в первых строки были короткими. Длинные пошли со второй версии. Так что фраза «долгое время макс. длина строки в delphi была 255 символов» не соответствует действительности.
19. bislomet , 27.06.2013 17:54
Spirit-1
Не совсем так — дело в том, что тип string все еще оставался 255-байтным.
а появился длинный AnsiString, который не был синонимом string ни в коем случае!

А сам тип string перестал быть ShortString куда позже, где-то в районе 6й версии.

20. Spirit-1 , 27.06.2013 18:21
Ничего подобного. String в Delphi 2.0 стал = AnsiString, старый String (короткий) стал называться ShortString’ом.

цитата (Spirit-1): А разве в дельфях длинные строки не были с самой первой версии?

Нет, обычные борланд-паскалевской реализации (байт с индексом 0 — длинна строки).

bislomet
Меня больше интересует слово «ANSI», чем 32 битный «длинн». Анси типа означает АНСИ и не хухры-мухры ? Или же нормально в ногу со временем там реально живет UTF8 ?

А от это паноптикум.
-Труп умер 50 лет назад
-Да, но продлим ресурс его креслу еще на 1000 лет. А то и должность завхоза была вакантна.
-Только у нас ANSI завхоз в ANSI кресле.

21. vertur , 28.06.2013 03:49
22. Spirit-1 , 28.06.2013 09:52
vertur
Мы уже этот вопрос отработали . Короткие строки были в первой версии, начиная со второй пошли длинные (по дефолту).
23. AzikAtom , 05.07.2013 11:54
olivenoel

А не в этом ли дело? В помощи написано The New procedure creates a new dynamic variable and sets a pointer variable to point to it. P is a variable of any pointer type. The size of the allocated memory block corresponds to the size of the type that P points to. The newly created variable can be referenced as P^. If there isn’t enough memory available to allocate the dynamic variable, an EOutOfMemory exception is raised. For more information on handling run-time library exceptions, see Handling RTL Exceptions

When an application is finished using a dynamic variable created with New, it should dispose of the memory allocated for the variable using the Dispose standard procedure.
Т.е., при объявлении переменной типа TMyRecType память уже была выделена, а вы её ещё раз выделили и старый указатель потеряли.

24. Spirit-1 , 05.07.2013 13:13
AzikAtom
Не могу утверждать точно (проверить прямо сейчас не могу), но вроде бы если TMyRecType не является указателем, то New(FMyRec) не скомпилируется.

цитата: Spirit-1:
AzikAtom
Не могу утверждать точно (проверить прямо сейчас не могу), но вроде бы если TMyRecType не является указателем, то New(FMyRec) не скомпилируется.

скорее всего там таки был PMyRecType и я ошиблась при перепечатке кода

25. olivenoel , 05.07.2013 13:42
26. AzikAtom , 05.07.2013 14:03
Spirit-1
Написано:

Значит, это не указатель и вручную не надо память брать.

olivenoel
скорее всего там таки был PMyRecType и я ошиблась при перепечатке кода
Вот если бы у вас было:

тогда да, и NEW и последующий DISPOSE, а сейчас можно попробовать убрать этот NEW().
Правда, если вы эти переменные потом собираете в списки, то надо бы сначала сохранить указатель на самую первую переменную, которая создаётся автоматически, и затем восстановить его. Правда, тогда нет смысла объявлять саму переменную и лучше сделать изначально переменную типа ^TMyRecType.

27. CyberStorm , 05.07.2013 17:53
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

то же справедливо для размещенных в записях массивах,
утечка будет:
TMyRecord=record
A:array of integer;
End;

утечки не будет:
TMyRecord=record
A:array[0..1] of integer;
End;

Применение SetLength( ,0) для строковых переменных и открытых массивов записей позволяет избежать утечки памяти.

28. olivenoel , 08.07.2013 19:03
что делает Move, кроме переноса данных? Возвращет память из-под перенесенных данных менеджеру памяти?
29. AzikAtom , 08.07.2013 21:31
olivenoel
Нет. Просто копирует.
30. olivenoel , 09.07.2013 10:26
тогда MoveMemory и CopyMemory делают одно и то же? Зачем тогда две ф-ции?
31. Spirit-1 , 09.07.2013 12:15
Посмотрите здесь (http://www.delphimaster.net/view/2-1167297116) , всё станет понятно.

нет, еще больше непонятно. В частности

куда смотрит Move?

32. olivenoel , 09.07.2013 12:30
33. AzikAtom , 09.07.2013 12:44
olivenoel
тогда MoveMemory и CopyMemory делают одно и то же? Зачем тогда две ф-ции?
Видать, для удобства программистов. Щелчок правой кнопкой мыши на MoveMemory и выбрать «Find Declaration».
Открывается реализация:

Добавление от 09.07.2013 12:47:

olivenoel
Кстати, вопрос с утечками закрыт? В чём было дело?

34. olivenoel , 09.07.2013 12:53
вроде закрыт. Где-то был непарный вызов New в конструкторе (без Dispose в диструкторе). Сейчас заменила все указатели на структуры на сами структуры. Осталась где-то одна утечка Unknown x1.

Я попыталась уйти вообще от неуправляемых строк внутри библиотеки. Они есть только в теле экспортируемых ф-ций, где преобразуются в нормальные (alias) string и дальше уже работается с ними. Надо только оттестировать с ComInterop из-под C#

35. Spirit-1 , 09.07.2013 13:07
olivenoel
куда смотрит Move?
По указанной ссылке не всё истина. Фильтруйте немного. Там просто топик обсуждения. Конкретно выражение «Ну как же — процедура Move смотрит, откуда ее вызывают, в зависимости от этого
выполняет разные действия.» в той теме — полный бред, конечно.

AzikAtom
Так что, пользуйтесь move() и не парьтесь — она написана на асме.
Я бы не был столь категоричен. Там под IFDEF либо паскалевская реализация, либо на асме под x86.

36. AzikAtom , 09.07.2013 14:08
Spirit-1
В Delphi 4 такая реализация:

В Delphi 7 ещё не смотрел.

olivenoel
куда смотрит Move?
Он только смотрит, чтобы копирование при наложении было корректным. Т.е., то, что было в источнике гарантированно оказывается в приёмнике.

цитата: Spirit-1:
olivenoel
куда смотрит Move?
По указанной ссылке не всё истина. Фильтруйте немного. Там просто топик обсуждения. Конкретно выражение «Ну как же — процедура Move смотрит, откуда ее вызывают, в зависимости от этого
выполняет разные действия.» в той теме — полный бред, конечно.

Добавление от 09.07.2013 14:13:

В Delphi XE2 реализация посложнее:

37. olivenoel , 09.07.2013 14:11
38. Spirit-1 , 09.07.2013 14:39
AzikAtom
Delphi2010:
39. olivenoel , 09.07.2013 16:49
как правильно организовать обмен строками в длл (с учетом СОМ)? Сигнатура метода такая

в метод приходим с именем xls-файла для конвертации. Уходим — с именем полученного после конвертации файла (т.е. фактически изменяется только расширение, потому длина строки не меняется).

сдается мне подобные методы первые кандидаты на утечки.

40. AzikAtom , 09.07.2013 17:00
Если я правильно понимаю концепцию, то в библиотеке память строки не освобождается, и передаётся в неё только указатель, значит, просто там же поменять символы и при выходе из процедуры строка будет изменённая.

цитата: CyberStorm:
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

так же посмотрела исходники FastMM — если тип утечки не ссылочный (т.е. класс или строка или юникодстрока), то она сообщается как Unknown.

41. olivenoel , 09.07.2013 19:07
42. CyberStorm , 09.07.2013 23:47
Я что-то не понял вашего термина «не зачищаются»
Перечислимые типы в Delphi по умолчанию имеют размер byte т.е. статический размер и утечек из-за них не может быть, при уничтожении класса память, выделенная под переменные при создании класса, высвобождается автоматически.
В конце Destroy я обычно всегда добавляю inherited — правило хорошего тона

цитата: CyberStorm:
Я что-то не понял вашего термина «не зачищаются»
Перечислимые типы в Delphi по умолчанию имеют размер byte т.е. статический размер и утечек из-за них не может быть, при уничтожении класса память, выделенная под переменные при создании класса, высвобождается автоматически.
В конце Destroy я обычно всегда добавляю inherited — правило хорошего тона

если остаются строки, то остается и енум-поле. Сообщение от FastMM гласит: UnicodeString x2 Unknown x1. Или кратно, если объектов было несколько.

Насчет inherited. Вот смотрю я на деструктор TObject (а вроде от него наследуют все классы, даже если это не официально прописано при объявлении класса)

Добавление от 10.07.2013 10:46:

будет ли утечка в случае

Добавление от 10.07.2013 10:47:

и изменится ли что-то, если стрки сделать короткими?

43. olivenoel , 10.07.2013 10:09
44. CyberStorm , 10.07.2013 10:58
Два раза вызов конструктора?

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

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

цитата: CyberStorm:
Два раза вызов конструктора?

знаю, что тащить из других языков дурная привычка, но в шарпе, например, можно такое:

цитата: CyberStorm:
Инициализацию переменных надо вынести в отдельный метод класса, например Init и вызывать его из конструктора

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

Добавление от 10.07.2013 11:24:

как грамотно делать этот Init «сквозным» во всех классах иерархии? В базовом сделать его виртуальным, а потом переопределять во всех наследниках, вызывая при этом родительский? Он нигде не потеряется?

45. olivenoel , 10.07.2013 11:11
46. Spirit-1 , 10.07.2013 11:40
olivenoel
как грамотно делать этот Init «сквозным» во всех классах иерархии? В базовом сделать его виртуальным, а потом переопределять во всех наследниках, вызывая при этом родительский? Он нигде не потеряется?
В родительском классе virtual. В дочерних, при необходимости перекрытия метода, override. Нигде не потеряется.

цитата: CyberStorm:
Утечка в Delphi может быть если используется string в структуре record.

например утечка будет:
TMyRecord=record
S:string;
End;

утечки не будет:
TMyRecord=record
S:shortstring; // или S:string[100]
End;

если применить все это к моей Персоне:

создается три экземпляра Персоны. Утечки: UnicodeString x2 TPerson x3 UnicodeString x4

меняю длинную строку на короткую в

создаю опять три экземпляра. Получаю «чистые» утечки TPerson x3. Куда деваются строки, а точнее откуда они повляются в случае длинных строк?

47. olivenoel , 10.07.2013 12:09
48. Spirit-1 , 10.07.2013 14:03
Создание UnicodeString есть вызов GetMem (см. _NewUnicodeString в system.pas).
Ссылки на созданные строки хранятся в некоторой таблице, формат которой в D2010 примерно такой:

У меня есть подозрение, что в некоторый момент срабатывает что-то вроде GC, который проходит по этой таблице и освобождает память из-под тех строк, у которых RefCount == 0. В тестовом примере вполне может случиться так, что «строковой GC» не успевает сработать, а FastMM рапортует о неосвобождённой памяти. Для чистоты эксперимента я бы насоздавал несколько сотен тысяч объектов с большими/длинными строками и сравнил отчеты об утечках на выходе из программы с теоретически рассчитанными. Если «строковой GC» успел сработать, то количество UnicodeString утечек будет отличаться от общего количества созданных строк.

P.S. Тяжело разобраться, когда полных исходников внутреннуй кухни нет. Остаётся только гадать .

49. CyberStorm , 10.07.2013 17:00
olivenoel Взял ваш код, скомпилировал на XE2, запустил — утечек памяти нет.

Даже убрал из Destroy присвоение:
FName.FFName := »;
FName.FLName := »;
утечек все равно нет, компилятор Delphi достаточно интеллектуальный, чтобы освободить из памяти строки в record при уничтожении экземпляра класса

Давайте посмотрим как вы создаете 3 экземпляра класса вариантов других не вижу. И какая версия Delphi у вас?

PS. Это не .NET, освобождение памяти строки происходит сразу при выходи из области видимости переменной или при вызове деструктора для строк определенных в заголовке класса.

цитата: CyberStorm:
olivenoel Взял ваш код, скомпилировал на XE2, запустил — утечек памяти нет.

Даже убрал из Destroy присвоение:
FName.FFName := »;
FName.FLName := »;
утечек все равно нет, компилятор Delphi достаточно интеллектуальный, чтобы освободить из памяти строки в record при уничтожении экземпляра класса

Давайте посмотрим как вы создаете 3 экземпляра класса вариантов других не вижу. И какая версия Delphi у вас?

у меня тоже ХЕ2. Оно заработало без утечек сегодня после 3-ей или 4-ой перекомпиляции. То ли он компайлер старые дцу-шки подхватывал, то ли звезды не сошлись.

50. olivenoel , 10.07.2013 19:32
51. olivenoel , 11.07.2013 16:50
нашла езе одно место, где появляется Unknown утечка (а точнее строка опять же)

Добавление от 11.07.2013 16:58:

и еще, правильно ли я понимаю, что любое присваивание чего либо переменной типа WideString, автоматично вызывает ф-ции аллокации памяти в системном менеджере памяти? Т.е. после ф-ции

тоже надо вызывать StrDispose/SysFreeString?

52. AzikAtom , 11.07.2013 17:33
olivenoel
можно ли побороть утечку, вызывая StrDispose на стороне хост-приложения?
Да. А можно в функцию передавать готовый буфер для заполнения? В winapi так и делают. Тогда контроль за памятью принадлежит вызывающей стороне.

WideString
Так ведь, WideString это как string только для 16 битных символов.

53. Spirit-1 , 11.07.2013 17:38
olivenoel
Вызов Result := »; при типе функции PWideChar выглядит несколько подозрительно.

цитата: AzikAtom:
olivenoel
можно ли побороть утечку, вызывая StrDispose на стороне хост-приложения?
Да. А можно в функцию передавать готовый буфер для заполнения? В winapi так и делают. Тогда контроль за памятью принадлежит вызывающей стороне.

WideString
Так ведь, WideString это как string только для 16 битных символов.

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

По поводу буффера. Где-то читала, что компилятор дельфи передает Result в виде out параметра. Что-то вроде

эквивалентно (точнее преобразуется не то парсером кода, не то самим компайлером)

Добавление от 11.07.2013 17:56:

цитата: Spirit-1:
olivenoel
Вызов Result := »; при типе функции PWideChar выглядит несколько подозрительно.

чем? Надо что ли nil возвращать?

54. olivenoel , 11.07.2013 17:56
55. Spirit-1 , 11.07.2013 18:15
Я бы Nil возвращал. Все-таки результат есть указатель, и это было бы логичнее.

Добавление от 11.07.2013 18:17:

olivenoel
И тогда все пользователи этой длл должны будут сильно вдаваться в подробности реализации
Кстати, правильно ли я понимаю, что память выделяется в dll, а освобождаться должна вызывающей стороной?

56. olivenoel , 11.07.2013 18:22
правильно Кто-то же ее должен освобождать. Или?

может все же дешевле переименовать все PWideChar в BStr и не морочить разные места?

Добавление от 11.07.2013 18:32:

то что получится на стороне хоста? WideString будет существовать только в scope как временная переменная. При выходе и scope она уничтожится (освободится ли. ). Т.е. Result по выходе из scope будет показывать в небо?

57. Spirit-1 , 11.07.2013 18:44
olivenoel
правильно Кто-то же ее должен освобождать. Или?
Реально это сильно неправильно. Аллоцирование памяти внутри осуществляется средствами конкретного memory manager’а в пуле памяти самой DLL. Освободить такое может только программа с аналогичным MM, и то она будет рыться в чужом огороде. Попробуйте скомпилировать вызывающую программу из другой версии Delphi (чтобы она использовала другой memory manager). И удивитесь, поскольку нифига работать не будет. Или другой вариант — вызывающая программа вообще, к примеру, на MS VC. Как будете память освобождать? Какими вызовами? (За доп. информацией можно поискать, для чего раньше в дельфях использовали borlandmm.dll)
В Вашем случае правильно делать именно так, как посоветовал AzikAtom. Вызывающая программа сама выделяет буфет. Функции DLL заполняют его (буфер) данными. Освобождение памяти делает тоже вызывающая программа. Вот это будет кошерно.
58. AzikAtom , 11.07.2013 18:58
olivenoel
При выходе и scope она уничтожится (освободится ли. )
Да.

Т.е. Result по выходе из scope будет показывать в небо?
Не в небо, а на освобождённую область памяти, где ещё какое-то время будет находится результат, потому что не успел затереться новой переменной.
Лучше всего делать как в winapi: выделить память под строку и передать вместе с переменной, где содержится размер буфера. Функция заполняет этот буфер и в переменной возвращает реальный размер строки.

цитата: Spirit-1:
olivenoel
правильно Кто-то же ее должен освобождать. Или?
Реально это сильно неправильно. Аллоцирование памяти внутри осуществляется средствами конкретного memory manager’а в пуле памяти самой DLL. Освободить такое может только программа с аналогичным MM, и то она будет рыться в чужом огороде.

цитата: Spirit-1:
olivenoel
Попробуйте скомпилировать вызывающую программу из другой версии Delphi (чтобы она использовала другой memory manager). И удивитесь, поскольку нифига работать не будет. Или другой вариант — вызывающая программа вообще, к примеру, на MS VC. Как будете память освобождать? Какими вызовами? (За доп. информацией можно поискать, для чего раньше в дельфях использовали borlandmm.dll)
В Вашем случае правильно делать именно так, как посоветовал AzikAtom. Вызывающая программа сама выделяет буфет. Функции DLL заполняют его (буфер) данными. Освобождение памяти делает тоже вызывающая программа. Вот это будет кошерно.

это ВинАпи стиль. Есть еще СОМ стиль. И там все «играют в одной песочнице» вызовами SysAllocString/SysFreeString/Sys. . Для этого надо все вызовы строковые заставить работать с BStr. Или?

59. olivenoel , 11.07.2013 19:03
60. AzikAtom , 11.07.2013 20:14
olivenoel
ну так WideString и аллоцируется OS’ью в системном ММ
Так вы возвращаете не WideString, а PWideString — указатель на строку. Как вы его дальше используете?
61. Spirit-1 , 11.07.2013 21:52
Я вижу в Вашем коде StrAlloc. Он вызывает GetMem, который выделяет память из СВОЕЙ кучи.

цитата: Spirit-1:
Я вижу в Вашем коде StrAlloc. Он вызывает GetMem, который выделяет память из СВОЕЙ кучи.

Добавление от 12.07.2013 10:31:

цитата: AzikAtom:
olivenoel
ну так WideString и аллоцируется OS’ью в системном ММ
Так вы возвращаете не WideString, а PWideString — указатель на строку. Как вы его дальше используете?

Добавление от 12.07.2013 11:37:

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

с областями видимости возвращаемых переменных у меня все намного проще — они глобальные переменные в модуле library — т.е. существуют пока загружена длл. Многие ф-ции, возвращающие строки были описаны как:

и все работало (не знаю правда сколько процентов «повезло» в этом). Если поменять это на

Работа со строковыми типами данных в 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];

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

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

62. olivenoel , 12.07.2013 10:03
Популярные статьи
Информационная безопасность 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 предлагает широкий спектр услуг информационных технологий и ПО.

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

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

Delphi W >

Я пишу класс, который будет сохранять широкие строки в двоичном файле. Я использую Delphi 2005 для этого, но позже приложение будет портировано на Delphi 2010. Я чувствую себя здесь неуверенно, может кто-нибудь подтвердить, что:

Delphi 2005 WideString точно такого же типа, как Delphi 2010 String

Символ Delphi 2005 WideString а также символ Delphi 2010 String гарантированно всегда будут иметь размер 2 байта.

Со всеми форматами Unicode, я не хочу, чтобы меня ударяли по одному из символов в моей строке, вдруг размером 3 байта или чем-то вроде этого.

Редактировать: Найдено это: «Я действительно сказал UnicodeString, а не WideString. WideString все еще существует и остается неизменным. WideString выделяется диспетчером памяти Windows и должен использоваться для взаимодействия с COM-объектами. WideString отображается непосредственно в тип BSTR в COM «. по адресу http://www.micro-isv.asia/2008/08/get-ready-for-delphi-2009-and-unicode/

Теперь я еще больше запутался. То есть Delphi 2010 WideString — это не то же самое, что Delphi 2005 WideString ? Должен ли я использовать вместо UnicodeString ?

Редактировать 2: в Delphi 2005 нет типа UnicodeString . FML.

6 ответов

Для вашего первого вопроса: WideString не совсем тот же тип, что и строка D2010. WideString — это тот же тип COM BSTR, которым он всегда был. Он управляется Windows без подсчета ссылок, поэтому он делает копию всего BSTR каждый раз, когда вы его где-то передаете.

UnicodeString , который является строковым типом по умолчанию в D2009 и далее, по сути является версией AnsiString в формате UTF-16, которую мы все знаем и любим. Он имеет счетчик ссылок и управляется компилятором Delphi.

Во-вторых, типом char по умолчанию теперь является WideChar , это те же символы, которые всегда использовались в WideString . Это кодировка UTF-16, 2 байта на символ. Если вы сохраняете данные WideString в файл, вы можете без проблем загрузить их в UnicodeString . Разница между этими двумя типами связана с управлением памятью, а не с форматом данных.

Как уже упоминалось, строковый (на самом деле UnicodeString) тип данных в Delphi 2009 и выше не эквивалентен типу данных WideString в предыдущих версиях, но формат содержимого данных тот же. Они оба сохраняют строку в UTF-16. Поэтому, если вы сохраняете текст с помощью WideString в более ранних версиях Delphi, вы сможете правильно его прочитать, используя строковый тип данных в последних версиях Delphi (2009 и выше).

Обратите внимание, что производительность UnicodeString намного выше, чем WideString. Поэтому, если вы собираетесь использовать один и тот же исходный код как в Delphi 2005, так и в Delphi 2010, я предлагаю вам использовать псевдоним строкового типа с условной компиляцией в вашем коде, чтобы вы могли получить лучшее из обоих миров:

Теперь вы можете использовать MyStringType в качестве типа строки в исходном коде. Если компилятор Unicode (Delphi 2009 и выше), тогда ваш тип строки будет псевдонимом типа UnicodeString, который введен в Delphi 2009 для хранения строк Unicode. Если компилятор не является Unicode (например, Delphi 2005), тогда ваш тип строки будет псевдонимом для старого типа данных WideString. И поскольку они оба имеют формат UTF-16, данные, сохраненные в любой из версий, должны быть правильно прочитаны другой.

  1. Delphi 2005 WideString точно такого же типа, как Delphi 2010 String

Это не так — ex строка 2010 Delphi имеет скрытое поле внутренней кодовой страницы — но, вероятно, это не имеет значения для вас.

  1. Символ Delphi 2005 WideString, а также символ Delphi 2010 String гарантированно всегда будут иметь размер 2 байта.

Это правда. В Delphi 2010 SizeOf (Char) = 2 (Char = WideChar).

Для строк Unicode не может быть другой кодовой страницы — поле кодовой страницы было введено для создания общего двоичного формата как для строк Ansi (для которых требуется поле кодовой страницы), так и для строки Unicode (для которых это не нужно).

Если вы сохраняете данные WideString в поток в Delphi 2005 и загружаете те же данные в строку в Delphi 2010, все должно работать нормально.

@Marco — Строки Ansi и Unicode в Delphi 2009+ имеют общий двоичный формат (12-байтовый заголовок).

Кодовая страница UnicodeString CP_UTF16 = 1200;

  • Если вы хотите работать со строками Unicode только внутри вашего модуля — используйте тип UnicodeString (*).
  • Если вы хотите общаться с COM или с другими кросс-модульными целями — используйте тип WideString .

Видите ли, WideString — это особый тип, так как это не нативный тип Delphi. Это псевдоним / оболочка для BSTR — системный тип строки, предназначенный для использования с COM или межмодульной связью. Быть юникодом — это просто побочный эффект.

С другой стороны, AnsiString и UnicodeString — это нативные типы Delphi, аналогов которым нет в других языках. String — это просто псевдоним AnsiString или UnicodeString .

Поэтому, если вам нужно передать строку в другой код — используйте WideString , в противном случае — используйте либо AnsiString либо UnicodeString . Просто.

(*) Для старого Delphi — просто место

где-то в вашем коде. Это исправление позволит вам написать одинаковый код для любой версии Delphi.

Хотя символ D2010 всегда равен 2 байтам, в символах UTF-16 присутствуют те же проблемы свертывания и объединения символов, что и в символах UTF-8. Вы не видите этого с узкими строками, потому что они основаны на кодовых страницах, но с помощью строк Юникода возможно (и в некоторых ситуациях часто) иметь аффективные, но невидимые символы. Примеры включают в себя метку порядка байтов (BOM) в начале файла или потока Юникода, символы индикатора слева направо / справа налево и огромный диапазон сочетания акцентов. Это в основном касается вопросов «сколько пикселей в ширине будет эта строка на экране» и «сколько букв в этой строке» (в отличие от «сколько символов в этой строке»), но также означает, что вы можете ‘ t случайно вырезать символы из строки и предполагать, что они пригодны для печати. Такие операции, как «удалить последнюю букву из этого слова», становятся нетривиальными и зависят от используемого языка.

Вопрос о том, что «один из символов в моей строке вдруг имеет длину 3 байта», отражает небольшое недоумение по поводу того, как работает UTF. Можно (и допустимо) взять три байта в строке UTF-8 для представления одного печатаемого символа, но каждый байт будет действительным символом UTF-8. Скажем, письмо плюс два сочетания акцентов. Вы не получите символ в UTF-16 или UTF-32 длиной 3 байта, но он может иметь длину 6 байтов (или 12 байтов), если он представлен с использованием трех кодовых точек в UTF-16 или UTF-32. Что приводит нас к нормализации (или нет).

Но при условии, что вы имеете дело только со строками как с целыми вещами, все очень просто — вы просто берете строку, записываете ее в файл, затем читаете ее обратно. Вам не нужно беспокоиться о мелком шрифте отображения строки и манипуляции, которые все обрабатываются операционной системой и библиотеками. Strings.LoadFromFile (name) и Listbox.Items.Add (string) работают точно так же в D2010, как и в D2007, все вещи в Unicode прозрачны для вас, как для программиста.

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

Когда вы пишете класс в D2005, вы будете использовать Widestring. При переходе на D2010 Widestring все еще будет работать и работать правильно. Widestring в D2005 такой же, как WideString в D2010.

Тот факт, что String = WideString в D2010 не нужно учитывать, так как компилятор легко справляется с этими проблемами.

Ваша входная подпрограмма для сохранения с помощью (AString: String) требует только одной строки, входящей в процесс

Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?

В Delphi 1.0 существовал лишь единственный строковый тип String, полностью эквивалентный одноименному типу в Turbo Pascal и Borland Pascal. Однако, этот тип имеет существенные ограничения. Для обхода этих ограничений, в Delphi 2, разработчики из Borland устроили небольшую революцию. Теперь, начиная с Delphi 2, имеются три фундаментальных строковых типа: ShortString, AnsiString, и WideString. Кроме того, тип String теперь стал логическим. Т.е., в зависимости от настройки соответствующего режима компилятора (режим больших строк), он приравнивается либо к типу ShortString (для совместимости со старыми программами), либо к типу AnsiString (по умолчанию). Управлять режимом, можно используя директиву компиляции <$LONGSTRINGS ON/OFF>(короткая форма <$H+/->) или из окна настроек проекта – вкладка «Compiler» -> галочка «Huge strings». Если режим включен, то String приравнивается к AnsiString, иначе String приравнивается ShortString. Из этого правила есть исключение: если в определении типа String указан максимальный размер строки, например String[25], то, вне зависимости от режима компилятора, этот тип будет приравнен к ShortString соответствующего размера.

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

Существуют различия между типами AnsiString и WideString. Эти типы имеют практически одинаковую реализацию, и отличаются лишь тем, что WideString используется для представления строк в кодировке UNICODE использующей 16-ти битное представление каждого символа (WideChar). Эта кодировка используется в тех случаях когда необходима возможность одновременного присутствия в одной строке символов из двух и более языков (помимо английского). Например, строк содержащих одновременно символы английского, русского и европейских языков. За эту возможность приходится платить – размер памяти, занимаемый такими строками в два раза больше размера, занимаемого обычными строками. Использование WideString встречается не часто, поэтому, я буду в основном рассказывать о строках типа AnsiString. Но, поскольку они имеют одинаковую реализацию, почти все сказанное относительно AnsiString будет действительно и для WideString, естественно с учетом разницы в размере каждого символа.

Тоже самое касается и разницы между pChar и pWideChar.

Строковый тип AnsiString, обычно используется для представления строк в кодировке ANSI, или других (например OEM) в которых для кодирования одного символа используется один байт (8 бит). Такой способ кодирования называется single-byte character set, или SBCS. Но, очень многие не знают о существовании еще одного способа кодирования многоязычных строк (помимо UNICODE) используемого в системах Windows и Linux. Этот способ называется multibyte character sets, или MBCS. При этом способе, некоторые символы представляются одним байтом, а некоторые, двумя и более. В отличие от UNICODE, строки, закодированные таким способом, требуют меньше памяти для своего хранения, но требуют более сложной обработки. Так вот, строковый тип AnsiString может использоваться для хранения таких строк.

Также существуют еще типы pChar (pWideChar) и array [. ] of Char, они очень часто используются в сочетании со строковыми типами.

Итак, основные характеристики строковых типов:

Тип Максимальный размер строки Размер переменной Объем памяти, требуемый для хранения строки
String[n] где 0

Есть ещё – оператор @ (получение указателя) для переменной такого типа возвращает значение типа pChar. Это очень удобно, поскольку переменные этого типа очень часто используются как буфер при работе с функциями Windows API. Например:

var a :array[0..20] of Char;. GetModuleFileName(GetModuleFileName(HInstance,@a,SizeOf(a));

Здесь, функция GetModuleFileName возвращает результат в массив a.

PChar

Этот тип широко используется в языках C и C++. В Delphi, это не фундаментальный тип, а производный. Его определение выглядит так:

Т.е. переменные этого типа являются указателем, поэтому и имеют размер 4 байта. Формально, значение pChar может указывать как на один символ, так и на строку символов. Однако, общепринято что значения pChar указывают на строки, завершающиеся символом с кодом 0 (#0). В DOSе, такие строки назывались ASCIIZ, но чаще можно встретить название null-terminated string. Наличие такого «концевика» позволяет легко определить реальный размер строки на которую указывает значение pChar.

Не смотря на то, что формально pChar это указатель на Char (^Char), как это часто бывает в Delphi, тип pChar имеет несколько особенностей по сравнению с другими указателями. Таких особенностей несколько.

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

const pc :pChar =’abc’;var pv :pChar =’abc’;

Эти строки, определяют константу pc и переменную pv типа pChar. При этом, и pc и pv указывают на разные области памяти, но содержащие одинаковые значения, состоящие из трех символов: ‘a’, ‘b’, ‘c’, и символа #0. Завершающий символ с кодом 0 компилятор добавил автоматически.

Вторая особенность в том, что к переменным типа pChar применимо обращение как к массиву символов. Например, если есть приведенные выше определения, тогда:

C := pv^; // C будет присвоен символ ‘a’. Это обычное обращениеC := pv[0]; // необычно, но С станет равным ‘a’C := pv[1]; // С станет равным ‘b’C := pv[2]; // С станет равным ‘c’C := pv[3]; // С станет равным #0C := pv[4]; // ОШИБКА!

Символ с индексом 3 отсутствует в строке, однако, там есть завершающий ее символ с кодом 0. Именно он будет результатом pv[3]. О pv[4] тоже стоит сказать особо. Дело в том, что компилятор не даст ошибки при компиляции, поскольку на этапе компиляции он, в общем случае, не известен реальный размер строки, на которую указывает переменная pv. Однако, на этапе выполнения программы, такое обращение может вызвать ошибку нарушения доступа к памяти (Access Violation). А может и не вызвать, но результатом будет неопределённое значение. Все зависит от «расклада» в памяти. Поэтому, при таком способе обращения необходимо быть внимательным, и выполнять все необходимые проверки, исключающие выход за размеры строки.

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

function StringLength (p :pChar) :Cardinal;begin Result := 0; if p = nil then Exit; while p^ <> #0 do begin Inc(Result); Inc(p); end;end;

Здесь важно обратить внимание на два нюанса.

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

Второй, это оператор Inc(p) – он «продвигает» указатель на следующий символ. Можно было бы записать его и так: p := p + 1.

Что бы продемонстрировать вычитание указателей pChar, приведем еще один вариант реализации той же функции:

function StringLength (p :pChar) :Cardinal;var pp :pChar;begin Result := 0; pp := p; if pp <> nil then while pp^ <> #0 do Inc(pp); Result := (pp-p);end;

Здесь, выражение pp-p дает «расстояние» между указателями, т.е. число символов между символом, на который указывает указатель p (начало строки) и символом, на который указывает указатель pp (завершающий строку #0).

ShortString, и String[n]

ShortString является частным случаем String[n], а если быть более точным, он полностью эквивалентен String[255].

Если посмотреть на таблицу, где приведены характеристики переменных этого типа, то мы увидим что их размер на один байт больше чем размер хранимой в них строки. Но в данном случае, это не для завершающего символа, как в pChar. Здесь в дополнительном байте хранится текущий размер строки. Кроме того, этот байт располагается не в конце строки, а наоборот, в ее начале. К примеру, если имеется следующее определение:

Оно означает, что для переменной s будет статически выделена область памяти размером 5 байт.

Теперь, выполнение оператора s := ‘abc’, приведёт к тому, что содержимое этих пяти байт станет следующим: байт 1 = 3, байт 2 = ‘a’, байт 3 = ‘b’, байт 4 = ‘c’, а значение байта 5 будет неопределённо – оно будет зависеть от «расклада» в памяти. Т.е., первый символ строки будет находиться во втором байте. Это неудобно, поэтому к символам строк ShortString принято индексироваться, начиная с 1. Следовательно:

s[1] = ‘a’ – первый символ строкиs[2] = ‘b’ – второй символ строкиs[3] = ‘c’ – третий символ строкиs[4] = все что угодно :).

А как же байт длины? Да все очень просто, к нему можно обратиться как s[0]. Только вот есть маленькая проблемка. Поскольку элементами строки являются символы, то и тип значения s[0] тоже будет символ. Т.е., если Вы хотите получить длину строки в виде целого числа, как это принято у нормальных людей, то надо выполнить соответствующее преобразование типа: Ord(s[0]) = 3 – размер строки.

Почему для типа String[n] существует ограничение 0 0, поэтому то Delphi и не освобождает память, занятую строкой.

Еще, этот счётчик используется и для разрешения проблем, связанных со следующей ситуацией:

procedure ShowInteger;var s1 :AnsiString; s2 : AnsiString; n :Integer;begin n := 123; s1 := ‘abc’+IntToStr(n); s2 := s1; s2[1] := ‘X’;end;

Здесь, как мы уже знаем, после выполнения оператора s2 := s1, обе переменные указывают на один и тот же экземпляр строки ‘abc123’. Однако, что же произойдёт когда выполниться оператор s2[1] := ‘X’? Казалось бы, в единственном имеющимся в нашем распоряжении экземпляре строки первая буква будет заменена на ‘X’. И как следствие, обе строки станут равными ‘Xbc123’. s1 то за что «страдает»? Но, к счастью это не так. Здесь на помощь Delphi вновь приходит счетчик ссылок. Delphi, при выполнении этого оператора понимает, что строка на которую указывает s2 будет изменена, а это может повлиять на других. Поэтому, перед изменением строки, проверяется ее счётчик ссылок. Обнаружив, что на нее ссылается более одной строковой переменной, делается следующее: создается копия этой строки со счётчиком ссылок равным 1, и адрес этой копии, присваивается s2; У исходного экземпляра строки, счетчик ссылок уменьшается на 1 – ведь s2 на неё теперь не ссылается. И лишь после этого, происходит изменение первой буквы, теперь уже собственного экземпляра строки. Т.е., по окончанию выполнения этого оператора, в памяти будут находиться две строки: ‘abc123’ и ‘Xbc123’. Причем, s1 будет ссылаться на первую, а s2 на вторую.

При работе со строками определенными как константы, алгоритм работы несколько отличается.

Пример:

procedure ShowInteger;var s :AnsiString;begin s := ‘Вася’; ShowMessage(s);end;

Казалось бы, при завершении работы процедуры, экземпляр строки ‘Вася’ должен быть уничтожен. Но в данном случае это не так. Ведь, при следующем входе в процедуру, для выполнения присваивания нужно будет вновь где-то взять строку ‘Вася’. Для этого, ещё при компиляции, Delphi размещает экземпляр строки ‘Вася’ в области констант программы, где её даже невозможно изменить, по крайней мере, простыми методами. Но как же при завершении процедуры определить что строка ‘Вася’ – константная строка, и ее нельзя уничтожать? Все очень просто. Для константных строк, счётчик ссылок устанавливается равным -1. Это значение, «выключает» нормальный алгоритм работы со «счётчиком ссылок». Он не увеличивается при присваивании, и не уменьшается при уничтожении переменной. Однако, при попытке изменения переменной (помните s2[1]:=’X’), значение счётчика равное -1 будет всегда считаться признаком того, что на строку ссылается более одной переменной (ведь он не равен 1). Поэтому, в такой ситуации всегда будет создаваться уникальный экземпляр строки, естественно, без декремента счётчика ссылок старой. Это защитит от изменений экземпляр строки-константы.

К сожалению, этот алгоритм срабатывает не всегда.

Где же Delphi хранит «счётчик ссылок»? Причем, для каждой строки свой! Естественно, вместе с самой строкой. Вот что представляет собой эта область памяти, хранящая экземпляр строки ‘abc’:

Байты с 1 по 4 Счётчик ссылок равный -1
Байты с 5 по 8 Длина строки равная 3
Байт 9 Символ ‘a’
Байт 10 Символ ‘b’
Байт 11 Символ ‘c’
Байт 12 Символ с кодом 0 (#0)

Для удобства работы с такой структурой, когда строковой переменной присваивается ссылка на эту строку, в переменную заносится адрес не начала этой структуры, а адрес её девятого байта. Т.е. адрес начала реальной строки (прямо как pChar). Для того, что бы приблизиться к реальной жизни, перепишем приведённую структуру:

Смещение Размер Значение Назначение
-8 -1 Счётчик ссылок
-4 Длина строки
‘a’
‘b’
‘c’
#0

С полем по смещению -8, нам уже должно быть все ясно. Это значение, хранящееся в двойном слове (4 байта), тот самый счетчик, который позволяет оптимизировать хранение одинаковых строк. Значение этого счетчика имеет тип Integer, т.е. может быть отрицательным. На самом деле, используется лишь одно отрицательное значение – «-1», и положительные значения. 0 не используется.

Теперь, обратим внимание на поле, лежащее по смещению -4. Это, четырёхбайтовое значение длинны строки (почти как в ShortString). Думаю, Вы заметили, что размер памяти выделенной под эту строку не имеет избыточности. Т.е. компилятор выделяет под строку минимально необходимое число байт памяти. Это конечно хорошо, но, при попытке «нарастить» строку: s1 := s1 + ‘d’, компилятору, точнее библиотеке времени исполнения (RTL) придется перераспределить память. Ведь теперь строке требуется больше памяти, аж на целый байт. Для перераспределения памяти нужно знать текущий размер строки. Вероятно, именно для того, что бы не приходилось каждый раз сканировать строку, определяя её размер, разработчики Delphi и включили поле длины, строки в эту структуру. Длина строки, хранится как значение Integer, отсюда и ограничение на максимальный размер таких строк – 2 Гбайт. Кстати, именно потому, что память под эти строки выделяется динамически, они и получили ещё одно свое название: динамические строки.

Ещё немного о нескольких особенностях переменных AnsiString. Важнейшей особенностью значений этого типа является возможность приведения их к типу Pointer. Это впрочем, естественно, ведь в «душе» они и есть указатели, как бы они этого не скрывали. Например, если описаны переменные: s :AnsiString и p :Pointer. То выполнение оператора p := Pointer(s) приведет к тому, что переменная p станет указывать на экземпляр строки. Однако, при этом, очень важно знать: счетчик ссылок этой строки не будет увеличен.

Поскольку, переменные этого типа реально являются указателями, то для них и реально такое значение как Nil – указатель в «никуда». Это значение в переменной типа AnsiString по смыслу приравнивается пустой строке. Более того, чтобы не тратить память и время на ведение счётчика ссылок, и поля размера строки всегда равного 0, при присваивании пустой строке переменной этого типа, реально, присваивается значение Nil. Это не очевидно, поскольку обычно не заметно, но как мы увидим позже, очень важная особенность.

Преобразование строк из одного типа в другой

Преобразование между «настоящими» строковыми типами String[n], ShortString, и AnsiString выполняются легко, и прозрачно. Никаких явных действий делать не надо, Delphi все сделает за Вас. Надо лишь понимать, что в маленькое большое не влезает. Например:

var s3 :String[3]; s :AnsiString;. s := ‘abcdef’; s3 := s;

В результате выполнения этого кода, в переменной s3 окажется строка ‘abc’, а не ‘abcdef’. С преобразованием из pChar в String[n], ShortString, и AnsiString, тоже всё очень не плохо. Просто присваивайте, и все будет нормально.

Сложности начинаются тогда, когда мы начинаем преобразовывать «настоящие» строковые типы в pChar. Непосредственное присваивание переменным типа pChar значений строк не допускается компилятором. На оператор p := s где p имеет тип pChar, а s :AnsiString, компилятор выдаст сообщение: «Incompatible types: ‘String’ and ‘PChar'» — несовместимые типы ‘String’ и ‘PChar’. Чтобы избежать такой ошибки, надо применять явное приведение типа: p := pChar(s). Так рекомендуют разработчики Delphi. В общем, они правы. Но, если вспомнить, как хранятся динамические строки — с нулем в конце, как и pChar. А еще и то, что к AnsiString применимо преобразование в тип Pointer. Станет очевидным, что всего, возможно целых три способа преобразования строки в pChar:

var s :AnsiString; p1,p2,p3 :PChar;. p1 := pChar(s); p2 := Pointer(s); p3 := @(s[1]);

Все они, синтаксически правильны. И кажется, что все три указателя (p1, p2 и p3) будут в результате иметь одно и то же значение. Но это не так. Всё зависит от того, что находится в s. Если быть более точным, равно ли значение s пустой строке, или нет:

s <> »p1 = p2 <> p3s = »p1 <> p2 = p3

Чтобы была понятна причина такого явления, опишем, как Delphi выполняет каждое из этих преобразований. Переменные AnsiString представляющие пустые строки, реально имеют значение Nil. Так вот:

Для выполнения преобразования pChar(s), компилятор генерит вызов специальной внутренней функции @LstrToPChar. Эта функция проверяет – если строковая переменная имеет значение Nil, то вместо него, она возвращает указатель на реально размещенную в памяти пустую строку. Т.е. pChar(s) никогда не вернет указатель равный Nil.

Тут все просто, такое преобразование просто возвращает содержимое строковой переменной. Т.е. если она при пустой строке содержит Nil, то и результатом преобразования будет Nil. Если же строка не пуста, то результатом будет адрес экземпляра строки.

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

Теперь, интересно отметить, что если в приведенном примере, преобразование p3 := @(s[1]) выполнить первым, то при не пустой строке в s, все указатели (p1, p2, и p3), будут равны. И содержать они будут адрес «персонального» экземпляра строки.

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

Приведем пример. В нем, преобразование, рекомендуемое разработчиками, приводит к «странному» поведению программы:

procedure X1;var s :AnsiString; p :PChar;begin s := ‘abcd’; p := PChar(s); p^ := ‘X’; // 0 then PChar(S)[0] := ‘q’; //.

вызовет ошибку нарушения доступа при передаче в нее строки сrefCnt = -1.

Чтобы получить уникальную ссылку для строки, состоящей из некоторой последовательности символов, можно воспользоваться функцией UniqueString.Это позволяет ускорить вычисления со строками, так как при этом можно будет сравнивать строки, просто сравнивая указатели на них. У таких строк refCntвсегда равен 1.

Примеры

Пример 1.

В символьной строке подсчитать количество цифр, предшествующих первому символу «!»

+
+
Начало
s
K:=0; I:=1;
I ’!’
S(i)>=’0’ and s(i) ’!’) Do

If (S[I]>=’0’) And (S[i] а, тт -> т ).

1.Имеется строка, содержащая буквы латинского алфавита и цифры. Вывести на экран длину наибольшей последовательности цифр, идущих подряд.

2. Дана строка, содержащая текст, заканчивающийся точкой. Вывести на экран слова, содержащие хотя бы одну букву о.

1. Дан набор слов, разделенных точкой с запятой (;). Набор заканчивается двоеточием (:). Определить, сколько в нем слов, заканчивающихся буквой а.

2. Дана строка, заканчивающаяся точкой. Подсчитать, сколько запятых в строке.

Последнее изменение этой страницы: 2020-07-16; Нарушение авторского права страницы

Илон Маск рекомендует:  Что такое код cpdf_open
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL