ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 18.01.2025
Просмотров: 760
Скачиваний: 2
СОДЕРЖАНИЕ
Строковые типы в Delphi. Особенности реализации и использования
Какие строковые типы существуют в Delphi, и чем они отличаются друг от друга?
Преобразование строк из одного типа в другой
Функции для работы со строками, о которых многие часто забывают, или вовсе не знают
If AnsiMatchText(YouName,['Вася','Петя','Гриша']) then
Запись строк в файл и чтение из файла
Использование строк в качестве параметров и результатов функций размещенных в dll.
Запись строк в файл и чтение из файла
Очень часто возникают вопросы вроде такого: Пишу строку в файл, затем читаю её от-туда, в результате получаю белиберду. В качестве кода приводят что-то вроде такого:
var
Stream :tStream;
Str :AnsiString;
...
Stream.WriteBuffer(Str,SizeOf(Str)); // так пишу
...
Stream.ReadBuffer(Str,SizeOf(Str)); // так читаю
или
var
f :file;
Str :AnsiString;
...
BlockWrite(f,Str,SizeOf(Str)); // так пишу
...
BlockRead(f,Str,SizeOf(Str)); // так читаю
Естественно, поскольку переменная динамической строки - на самом деле указатель, то таким образом, записывается лишь четыре байта адреса текущего положения строки в памяти. Естественно, что при чтении этот адрес не имеет никакого смысла.
Такой способ записи/чтения приемлем только для строковых переменных типов ShortString и String[n], поскольку переменные этих типов хранят саму строку, а не указатель на неё. Более того, есть даже одно неявное удобство. Поскольку в начале таких переменных хранится и байт текущей длины строки, то он тоже записывается в файл. Поэтому, при чтении записанной таким образом строки не встает проблемы с определением её действительного размера. Однако надо понимать что таким образом Вы получаете не текстовый файл. Во первых, байт длины может принимать любые значения от 0 до 255, и не всегда будет представлять собой код печатного символа. Во вторых, если, например, строка объявлена как ShortString. То даже если она в данный момент содержит в себе строку 'abcd', в файл будет записано 256 байт. 1 байт длины, 4 байта реальной строки и 251 байт "мусора", не обязательно печатного :).
Но, вернемся к записи динамических строк. Когда ошибка становится понятной, обычно следующим шагом переделывают алгоритм записи строки, например, так:
Stream.WriteBuffer(pChar(Str)^,Length(Str)); // так пишу
или так:
BlockWrite(f, pChar(Str)^,Length(Str)); // так пишу
Ну а потом, встает естественный вопрос – а как же это прочитать? Ведь неизвестно сколько байт занимает строка, а, следовательно, и неизвестно сколько байт из файла читать. Да и размер буфера неясен. Так вот. Об этом надо позаботиться заранее, ещё когда записываешь строку. Например, записать можно так:
var
Stream :tStream;
Str :AnsiString;
Len :Longint;
...
Len := Length(Str);
Stream.WriteBuffer(Len,SizeOf(Len)); // длинна строки
Stream.WriteBuffer(pChar(Str)^,Length(Str)); // сама строка
или так:
var
f :file;
Str :AnsiString;
Len :Longint;
...
Len := Length(Str);
BlockWrite(f,Len,SizeOf(Len)); // длинна строки
BlockWrite(f,pChar(Str)^,Length(Str)); // сама строка
Тогда, можно будет прочитать вот так:
Stream.ReadBuffer(Len,SizeOf(Len)); // длинна строки
SetLength(Str,Len); // выделение памяти под строку
Stream.ReadBuffer(pChar(Str)^,Len); // чтение строки
или так:
BlockRead(f,Len,SizeOf(Len)); // длинна строки
SetLength(Str,Len); // выделение памяти под строку
BlockRead(f,pChar(Str)^,Len); // чтение строки
Что, сложно? Ну, за возможность хранить строки длинной >255 символов приходится платить.
Если же тебе достаточно и 255 символов, то используй ShortString, или String[n].
Есть еще одно, на мой взгляд, замечание. Если Вы обратили внимание на тип переменной Len в моем примере, то возможно у Вас возник вопрос: А почему LongInt, а не Integer? Жаль если у Вас вопрос не возник – либо вы все знаете, либо ничего :). Для остальных поясню: дело в том, что тип LongInt фундаментальный тип, размер которого (4 байта) не будет меняться в последующих версиях Delphi. А тип Integer, это универсальный тип, размерность которого может меняться от версии к версии. Например, для 64-разрядных компьютеров он наверняка "вырастет" до 8-ми байт (64 бита). Лично мне, хочется, что бы файлы данных записанные моей старой версией программы могли быть нормально прочитаны более поздними версиями, возможно скомпилированными уже под 64-разрядной OS.
Использование строк в записях
Проблема возникает тогда, когда одно поле (или несколько полей) имеют тип динамической строки. В этом случае, часто возникает проблема схожая с проблемой записи динамической строки в файл – не все данные лежат в записи, динамические строки представлены в ней лишь указателями. Решается проблема также как и с записью строк в файл. Жаль только что тогда нельзя будет оперировать (записать/прочитать) целиком всей записью. Строки придется обрабатывать особо. Можно сделать, например, так:
var
r :packed record
f1 :Integer;
f2 :array [1..30] of Double;
f3 :AnsiString;
f4 :Boolean;
...
end
...
var
Stream :tStream;
s :String;
Len :Longint;
...
// запись записи :) в поток
s := r.f3; r.f3 := ''; // дабы не писать неправильный адрес
Stream.WriteBuffer(r,SizeOf(r)); // record с f3 равным Nil
r.f3 := s; // восстановление значения f3
Len := Length(s);
Stream.WriteBuffer(Len,SizeOf(Len)); // длинна строки
Stream.WriteBuffer(pChar(s)^,Length(s)); // сама строка
...
// чтение записи из потока
Stream.ReadBuffer(r,SizeOf(r)); // record (вместо строки лабуда)
Stream.ReadBuffer(Len,SizeOf(Len)); // длинна строки
SetLength(r.f3,Len); // выделение памяти под строку
Stream.ReadBuffer(pChar(r.f3)^,Len); // чтение строки
Обратите внимание на то, что перед записью в поток я делаю так, что бы в поле f3 попал указатель Nil. Если этого не сделать, то в поток попадет адрес текущего экземпляра динамической строки. При чтении, он будет прочитан в поле f3. Т.е. поле f3 станет указывать на какое-то место в памяти. При выполнении SetLength, поскольку Delphi сочтет что текущее значение f3 лежит по указанному адресу, будет попытка интерпретировать лежащую там информацию как динамическую строку. Если же в поток записать Nil, то SetLength, никуда лезть не будет – экземпляра-то нет.
Использование строк в качестве параметров и результатов функций размещенных в dll.
Многие, наверное, пытались хоть раз написать собственную Dll. Если при этом Вы использовали соответствующего мастера Delphi, то наверняка видели комментарий который он вставляет в начало файла библиотеки:
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
Общий смысл этого эпоса в том, что если Ваша Dll экспортирует хотя бы одну процедуру или функцию с типом параметра соответствующим любой динамической строке (AnsiString например), или функцию, возвращающую результат такого типа. Вы должны обязательно и в Dll, и в использующей ее программе, первым модулем в списке импорта (uses) указать модуль ShareMem. И как следствие, поставлять со своей программой и Dll еще одну стандартную библиотеку BORLNDMM.DLL.
Вы не задумывались над вопросами: "Зачем все эти сложности?"; "Что будет если этого не сделать?" и "Можно ли этого избежать?"; "Если да, то как?" Если не задумывались, то самое время сделать это.
Попробуем разобраться что будет происходить с экземплярами динамических строк в следующем примере:
procedure Y (var s :AnsiString);
// Добавляет в конец строки символ '$', если его там еще нет
begin
if (Length(s)>0) and (s[Length(s)]<>'$') then
s := s+'$';
end;
...
procedure X;
var
Str :AnsiString;
begin
...
Str := IntToStr(100);
X(Str);
...
end;
Сначала, при выполнении процедуры 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) используемых для мультиязыковых приложений. Может и еще что-то забыл рассказать. Но, не расстраивайтесь. Я свои знания получал, изучая книги, тексты своих и чужих программ, код сгенерированный компилятором, и т.п. Т.е., все из открытых источников. Значит это все доступно и Вам. Главное, чтобы Вы были любознательными, и почаще задавали себе вопросы "Как?", "Почему?", "Зачем?". Тогда во всем сможете разобраться и сами.