Функция GetClassInfoпоступает очень просто, но не совсем корректно: один из ее аргументов — указатель на строку с именем класса. Именно его функция и помещает в lpszClassName.
В приведенном примере в качестве аргумента GetClassInfoпередаётся выражение типа string, приведенное к PChar, которое не может быть вычислено на этапе компиляции, поэтому компилятор генерирует код, вычисляющий данное выражение. Этот код размещает вычисленное выражение в динамической памяти, и в GetClassInfoпередаётся указатель на эту строку.
Все строковые выражения, вычисленные подобным образом, должны удаляться из памяти, чтобы не было утечек. Компилятор помещает код, освобождающий эту память, в эпилог той функции, в которой встретилось выражение. В данном случае — в эпилог локальной процедуры DoGetClassInfo.
Освободившуюся память менеджер памяти не сразу возвращает системе придерживает, чтобы иметь возможность быстрее выделить память при следующем запросе. Таким образом, после завершения работы DoGetClassInfoпамять, в которой хранится вычисленное имя оконного класса (и на которую указывает CI.lpszClassName), по-прежнему принадлежит процессу, но менеджер памяти полагает ее свободной и считает себя вправе использовать ее по своему усмотрению.
Когда присваивается значение переменной S, для размещения новой строки менеджер памяти выделяет ту самую область, в которой ранее хранилось имя класса. Так как CI.lpszClassNameпо-прежнему содержит этот адрес, обращение к этому полю возвращает новую строку, которая присвоена переменной S.
Примечание
В Delphi до 7-й версии включительно описанный эффект наблюдается при любой длине строки, присваиваемой переменной S, в более новых версиях Delphi — только в том случае, если длина этой строки находится в пределах от 5 до 11 символов. Это связано с тем, что новый менеджер памяти, появившийся в этих версиях Delphi, с целью уменьшения фрагментации разбивает кучу на несколько областей, в каждой из которых выделяет блоки памяти, укладывающиеся в соответствующий данной области диапазон размеров блоков. Если строка, присваиваемая переменной S, слишком сильно отличается по размеру от 'TForm1'для этой строки выделяется память в другой области, и подмены не происходит.
Если в данном примере не выносить вызов функции GetClassInfoв отдельную процедуру DoGetClassInfo, а вызывать ее напрямую из Button1Click, описанного эффекта не будет, потому что в этом случае освобождение памяти, занятой для вычисленного имени класса, будет производиться в эпилоге Button1Click, и на момент присваивания значения переменной Sэта память будет считаться занятой, поэтому для Sменеджер памяти выделит другую область.
Принципиально и то, что в обоих случаях (в функции GetClassInfoи при присваивании значения переменной S) используются не строковые литералы, а выражения, вычисляемые только на этапе выполнения программы. Строковые литералы размещаются компилятором в сегменте кода, и указатели, переданные в GetClassInfoи присвоенные переменной S, будут указывать не на динамическую память, а на эти литералы, и подмены не произойдет.
Избежать проблемы можно двумя способами. Во-первых, не следует передавать значение поля lpszClassNameза пределы той функции, в которой была вызвана GetClassName. Во-вторых, имя оконного класса должно быть известно программе до вызова GetClassName. Лучше использовать ту строку, в которой хранится это имя, чем поле lpszClassName.
3.4.5. Ошибка EReadError при использовании вещественных свойств
Если в секции publishedкомпонента имеются свойства вещественного типа ( Single, Doubleили Extended), то попытка присвоить в режиме проектирования формы этим свойствам некоторые вполне корректные значения приводит к ошибке EReadErrorпри чтении ресурсов формы (т.е. при создании формы). Для типов Doubleи Extendedошибка возникает, если значение свойства Xлежит в одном из указанных диапазонов:
-1e15 < х <= MinInt - 1
или
MaxInt + 1 <= X < 1e15
Не совсем понятно, при чем здесь значения MaxIntи MinInt, если речь идет о вещественных числах, но проблема существует. Типу Singleне хватает точности, чтобы передавать значения MaxIntи MinIntбез искажений. Тем не менее, с поправкой на уменьшение точности границ диапазонов, эта же ошибка возникает и для свойств типа Single. Ошибка возникает только в случае текстовой формы dfm-файла (все версии Delphi, начиная с пятой, по умолчанию используют эту форму). При бинарной форме dfm-файла ошибки не происходит.
Читать дальше
Конец ознакомительного отрывка
Купить книгу