Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подводные камни
  
О разделе

Список по категориям
Общий список

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  18:34[Войти] | [Зарегистрироваться]

WIN32.Перетирание имени оконного класса, возвращаемого GetClassInfo

Антон Григорьев
дата публикации 19-09-2005 08:29

КАТЕГОРИЯСИСТЕМА .WIN32.Перетирание имени оконного класса, возвращаемого GetClassInfo
ПРОДУКТDelphi
ПЛАТФОРМАWindows


Создадим новый проект в Delphi, поместим на форму кнопку Button1 и создадим ей следующий обработчик OnClick:
procedure TForm1.Button1Click(Sender: TObject);
 var CI:TWndClass;
     S:string;

  procedure DoGetClassInfo;
   begin
    GetClassInfo(hInstance,PChar('TForm'+IntToStr(1)),CI)
   end;

  begin
   DoGetClassInfo;
   S:='X'+IntToStr(2);
   Button1.Caption:=CI.lpszClassName
  end;
Какой заголовок будет у кнопки после выполнения этого кода? Так как класс называется 'TForm1', логично предположить, что именно такой заголовок и получит кнопка. На самом деле заголовок будет 'X2' – а это та строка, которая присвоена переменной S.

Разберёмся, как значение переменной S оказывается в поле CI.lpszClassName.

Согласно MSDN поле lpszClassName имеет тип LPCTSTR (PChar), и в него функция GetClassInfo заносит указатель на строку, содержащую имя оконного класса. Но нигде не сказано, в какой области памяти должна располагаться эта строка.

Функция GetClassInfo поступает очень просто, но не совсем корректно: один из её аргументов – указатель на строку с именем класса. Именно этот указатель функция и помещает в lpszClassName.

В приведённом примере в качестве аргумента GetClassInfo передаётся выражение типа string, приведённое к PChar. Это выражение не может быть вычислено на этапе компиляции, поэтому компилятор генерирует код, который его вычисляет. Этот код размещает вычисленное выражение в динамической памяти, и в GetClassInfo передаётся указатель на эту строку.

Все строковые выражения, вычисленные подобным образом, должны удаляться из памяти, чтобы не было утечек. Компилятор помещает код, освобождающий эту память, в эпилог той функции, в которой встретилось выражение. В данном случае – в эпилог процедуры DoGetClassInfo.

Освободившуюся память менеджер памяти не сразу возвращает системе, а придерживает, чтобы иметь возможность быстрее выделить память при следующем запросе. Таким образом, после завершения работы DoGetClassInfo память, в которой хранится вычисленное имя оконного класса (и на которую указывает CI.lpszClassName), по-прежнему принадлежит процессу, но менеджер памяти полагает её свободной и считает себя вправе использовать её по своему усмотрению.

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

Если в данном примере не выносить вызов функции GetClassInfo в отдельную процедуру DoGetClassInfo, а вызывать её напрямую из Button1Click, описанного эффекта не будет, потому что в этом случае освобождение памяти, занятой для вычисленного имени класса, будет производиться в эпилоге Button1Click, и на момент присваивания значения переменной S эта память будет считаться занятой, поэтому для S менеджер памяти выделит другую область.

Принципиальным является и то, что в обоих случаях (в функции GetClassInfo и при присваивании значения переменной S) используются не строковые константы, а выражения, вычисляемые только на этапе выполнения программы. Строковые константы размещаются компилятором в сегменте кода, и указатели, переданные в GetClassInfo и присвоенные переменной S, будут указывать не на динамическую память, а на эти константы, и перетирания не произойдёт.

Программа протестирована в Delphi 3, 4, 5, 6 и 7 в Windows 95 OSR2, Windows 2000 и Windows XP – везде наблюдался описанный эффект.


Типовые решения


Избежать проблемы можно двумя способами. Во-первых, не следует передавать значение поля lpszClassName за пределы той функции, в которой была вызвана функция GetClassInfo. Во-вторых, имя оконного класса должно быть известно программе до вызова GetClassInfo. Лучше использовать ту строку, в которой хранится это имя, чем поле lpszClassName.


Комментарий
В процедуре DoGetClassInfo мы передаем в GetClassInfo указатель на строку как временный, а назад получаем его как постоянный — на время жизни структуры TWndClass. Этот тонкий момент и есть вся соль данного камушка.

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


Смотрите также материалы по темам:


[TForm] [Окна, оконные сообщения]

 Обсуждение материала [ 02-03-2008 10:02 ] 4 сообщения
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования