А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
Программа EnumWnd является также примером того, как можно работать с параметрами типа LPTSTR, через которые функции Windows API возвращают программе строковые значения. В разд. 1.1.13 были перечислены три способа создания буфера для работы с такими параметрами: выделение памяти в стеке в виде массива элементов типа Char
, использование строк типа string
и строк типа PChar
. Все три способа реализованы в примере EnumWnd
. На главной и единственной форме программы EnumWnd
размещены два компонента: TreeWindow
типа TTreeView
и кнопка BtnBuild
. Обработчик нажатия кнопки выглядит очень лаконично (листинг 1.21).
BtnBuild
procedure TFomWindows.BtnBuildClick(Sender: TObject);
begin
Screen.Cursor := crHourGlass;
try
TreeWindows.Items.Clear;
EnumWindows(@EnumWindowsProc, 0);
finally
Screen.Cursor := crDefault;
end;
end;

Рис. 1.8.Окно программы EnumWnd
Все, что делает этот обработчик, — это очищает компонент TreeWindows
и вызывает EnumWindows
, передавая ей функцию обратного вызова EnumWindowsProc
, в которой и выполняется основная работа. Сразу отметим, что в этом примере мы будем использовать одну и ту же функцию обратного вызова как для EnumWindows
, так и для EnumWindowsProc
. Сама функция обратного вызова выглядит следующим образом (листинг 1.22).
EnumWindowsProc
(первый вариант)// Это функция обратного вызова, которая будет
// использоваться при вызове EnumWindows и EnumChildWindows.
// Тип второго параметра не совпадает с типом, который
// указан MSDN. Однако TTreeNode, как и любой класс,
// является указателем, поэтому может использоваться везде,
// где требуется нетипизированный указатель - на двоичном
// уровне между ними нет разницы. Указатель на функцию
// обратного вызова в EnumWindows и EnumChildWindows в
// модуле Windows.dcu объявлен как нетипизированный
// указатель, поэтому компилятор не контролирует
// соответствие реального прототипа заявленному.
function EnumWindowsProc(Wnd: HWND; ParentNode: TTreeNode): Bool; stdcall;
// Система не предусматривает возможности узнать, какова
// длина имени класса, поэтому при получении этого имени
// приходится выделять буфер большой длины в надежде, что
// имя класса не окажется еще длиннее. В данном примере
// размер этого буфера определяется константой ClassNameLen.
// Крайне маловероятно, что имя класса скажется длиннее,
// чем 511 символов (512-й зарезервирован для завершающего
// нулевого символа).
const
ClassNameLen = 512;
var
// Здесь будет храниться заголовок окна
Text: string;
TextLen: Integer;
// Это - буфер для имени класса
ClassName: array[0..ClassNameLen - 1] of Char;
Node: TTreeNode;
NodeName: string;
begin
Result := True;
// Функция EnumChildWindows перечисляет не только
// непосредственно дочерние окна данного окна, но и
// дочерние окна его дочерних окон и т.п. Но при
// построении дерева на каждом шаге нам нужны только
// прямые потомки, поэтому все окна, не являющиеся прямыми
// потомками, мы здесь игнорируем.
if Assigned(ParentNode) and (GetParent(Wnd) <> HWND(ParentNode.Data)) then Exit;
// Получаем длину заголовка окна. Вместо функций
// GetWindowText и GetWindowTextLength мы здесь
// используем сообщения WM_GETTEXT и WM_GETTEXTLENGTH,
// потому что функции, в отличие от сообщений, не
// умеют работать с элементами управления,
// принадлежащими окнам чужих процессов.
TextLen := SendMessage(Wnd, WM_GETTEXTLENGTH, 0, 0);
// Устанавливаем длину строковой переменной, которая
// будет служить буфером для заголовка окна.
// Использование SetLength гарантирует, что будет
// выделена специальная область памяти, на которую не
// будет других ссылок.
SetLength(Text, TextLen);
// Если заголовок окна - пустая строка, TextLen будет
// иметь значение 0, и указатель Text при выполнении
// Set Length получит значение nil. Но при обработке
// сообщения WM_GETTEXT оконная процедура в любом случае
// попытается записать строку по переданному адресу,
// даже если заголовок окна пустой - в этом случае в
// переданный буфер будет записан один символ -
// завершающий ноль. Но если будет передан nil, то
// попытка записать что-то в такой буфер приведет к
// Access violation, поэтому отправлять окну WM_GETTEXT
// можно только в том случае, если TextLen > 0.
if TextLen > 0 then
SendMessage(Wnd, WM_GETTEXT, TextLen + 1, LParam (Text));
// Заголовок окна может быть очень длинным - например, в
// Memo заголовком считается весь текст, который там
// есть. Практика показывает, что существуют проблемы
// при добавлении в TTreeView узлов с очень длинным
// названиями: при попытке открыть такой узел программа,
// запущенная из Delphi, вылетает в отладчик (при
// запуске вне среды Delphi проблем не замечено). Чтобы
// этого не происходило, слишком длинные строки
// обрезаются.
if TextLen > 100 then
Text := Copy(Text, 1, 100) + '...';
GetClassName(Wnd, ClassName, ClassNameLen);
ClassName[ClassNameLen - 1] := #0;
if Text = '' then NodeName := 'Без названия (' + ClassName + ') '
else NodeName := Text + ' (' + ClassName + ')';
Node := FormWindows.TreeWindows.Items.AddChild(ParentNode, NodeName);
// Записываем в данные узла дескриптор соответствующего
// ему окна, чтобы иметь возможность отбросить непрямые
// потомки.
Node.Data := Pointer(Wnd);
// Вызываем EnumChildWindows, передавая функцию
// EnumWindowsProc в качестве параметра, а указатель на
// созданный узел - в качестве параметра этой функции.
// При этом EnumWindowsProc будет вызываться из
// EnumChildWindows, т.е. получается рекурсия.
EnumChildWindows(Wnd, @EnumWindowsProc, LParam(Mode));
end;
Как мы помним, первый параметр функции обратного вызова для EnumWindows
содержит дескриптор найденного окна, а второй параметр может быть произвольным 4-байтным значением, которое система игнорирует, просто копируя сюда то значение, которое было передано при вызове EnumWindows
или EnumChildWindows
. Мы задействуем этот параметр для передачи ссылки на узел дерева, соответствующий родительскому окну. Также договоримся, что в свойство Data каждого узла будем записывать дескриптор связанного с ним окна. Для окон верхнего уровня ссылка будет иметь значение nil
— это обеспечивается тем, что при вызове EnumWindows второй параметр равен нулю (см. листинг 1.21).
Интервал:
Закладка: