Работа функции начинается с проверки того, что родительским окном для данного окна действительно является то окно, чей дескриптор связан с узлом родительского окна. Эта проверка нужна потому, что функция EnumChildWindows перечисляет не только дочерние, но и "внучатые", "правнучатые" и т.д. окна. Нам здесь это не нужно, на каждом шаге нас интересуют только непосредственные "дети" окна, а до "внуков" мы доберемся, когда вызовем EnumChildWindows для дочерних окон, поэтому и отсеиваем лишнее.
Следующий шаг — получение заготовка окна. Для этого мы используем сообщение WM_GETTEXT (разница между этим сообщением и функцией GetWindowText обсуждается в разд. 1.3.1). Буфером является переменная Text типа string. Сначала с помощью сообщения WM_GETTEXTLENGTH мы узнаем длину заголовка окна, а затем выделяем под строку Text требуемое количество памяти с помощью SetLength. После этого можно получить строку с помощью сообщения WM_GETTEXT. Второй параметр этого сообщения — адрес буфера, в который будет помещена строка. Так как переменная типа string и есть указатель на буфер строки (это детально обсуждается в разд. 3.3), достаточно просто привести переменную Text к типу LParam и передать получившееся значение.
ПримечаниеСтрого говоря, у нас здесь нигде нет параметра типа LPTSTR, однако при работе с параметрами этого типа можно действовать точно так же: выделить для строки типа string нужное количество памяти и передать эту переменную, приведенную к типу LPTSTR, в качестве параметра.
Далее получаем название класса окна. Для этого мы используем статический массив ClassName, т.е. размер буфера определяется на этапе компиляции. С одной стороны, это неправильно, потому что ограничений на длину имени класса не существует (по крайней мере, в документации они не упомянуты), а мы уже говорили, что такой метод следует применять только тогда, когда существует известное на этапе компиляции ограничение длины. По с другой стороны, когда речь идет об имени класса, не существует ничего подобного сообщению WM_SETTEXTLENGTH, т.е. API не дает возможности получить длину имени класса, что делает бессмысленными все манипуляции с размером буфера во время работы программы. Поэтому мы определяем размер буфера еще на этапе компиляции, исходя из того, что слишком уж длинные имена классов встречаются редко. При вызове функции с параметром типа LPTSTR можно просто передавать массив без приведения типа, т.к. LPTSTR — это PChar, а массивы символов Char, индексирующиеся с нуля, компилятор полагает совместимыми с этим типом и все необходимые преобразования делает неявно.
И, хотя мы и взяли размер буфера с хорошим запасом, нельзя исключать ситуации, когда имя класса окажется длиннее, чем буфер. Ничего страшного при этом не произойдет, т.к. мы передаем в функцию размер буфера специально для того, чтобы она не пыталась что-то записать за пределами буфера. Но в этом случае завершающий строку символ #0 не попадет в буфер, и при попытке дальше работать с этой строкой какая-нибудь другая функция может, не найдя конца строки в пределах буфера, попытаться поискать этот конец за его пределами, что приведет к непредсказуемым результатам. Поэтому на всякий случай записываем #0 в последний символ буфера. Если имя класса оказалось длиннее буфера, это обрежет строку по границе буфера, а если короче, то это ничему не повредит, т.к. признак конца строки будет в буфере где-то раньше, а все символы после него все равно игнорируются. После этого остается только создать новый элемент в дереве, а чтобы заполнить его дочерние элементы — вызвать EnumChildWindows для получения списка дочерних окон. Так как в EnumChildWindows передается та же функция обратного вызова, получается рекурсия, которая останавливается тогда, когда функция доходит до окна, не имеющего дочерних окон. Ранее мы говорили, что программа EnumWnd демонстрирует три метода получения строки через параметр типа LPTSTR, но пока мы увидели только два (действительно, трудно показать три различных метода на примере получения двух строк). Чтобы показать третий вариант — организацию буфера через строки типа PChar — перепишем функцию EnumWindowsProc (листинг 1.23). В исходном коде программы EnumWnd этот вариант присутствует в виде комментария. Можно убрать этот комментарий, а закомментировать, наоборот, первый вариант, чтобы попробовать, как работает получение строки с помощью PChar.
Листинг 1.23. Функция обратного вызова EnumWindowsProc (второй вариант)