И, хотя мы и взяли размер буфера с хорошим запасом, нельзя исключать ситуации, когда имя класса окажется длиннее, чем буфер. Ничего страшного при этом не произойдет, т. к. мы передаем в функцию размер буфера специально для того, чтобы она не пыталась что-то записать за пределами буфера. Но в этом случае завершающий строку символ #0
не попадет в буфер, и при попытке дальше работать с этой строкой какая-нибудь другая функция может, не найдя конца строки в пределах буфера, попытаться поискать этот конец за его пределами, что приведет к непредсказуемым результатам. Поэтому на всякий случай записываем #0
в последний символ буфера. Если имя класса оказалось длиннее буфера, это обрежет строку по границе буфера, а если короче, то это ничему не повредит, т. к. признак конца строки будет в буфере где-то раньше, а все символы после него все равно игнорируются. После этого остается только создать новый элемент в дереве, а чтобы заполнить его дочерние элементы — вызвать EnumChildWindows
для получения списка дочерних окон. Так как в EnumChildWindows
передается та же функция обратного вызова, получается рекурсия, которая останавливается тогда, когда функция доходит до окна, не имеющего дочерних окон. Ранее мы говорили, что программа EnumWnd
демонстрирует три метода получения строки через параметр типа LPTSTR
, но пока мы увидели только два (действительно, трудно показать три различных метода на примере получения двух строк). Чтобы показать третий вариант — организацию буфера через строки типа PChar
— перепишем функцию EnumWindowsProc
(листинг 1.23). В исходном коде программы EnumWnd
этот вариант присутствует в виде комментария. Можно убрать этот комментарий, а закомментировать, наоборот, первый вариант, чтобы попробовать, как работает получение строки с помощью PChar
.
EnumWindowsProc
(второй вариант)// Ниже приведен другой вариант функции
// EnumWindowsРrос, который отличается от предыдущего тем,
// что буфер для получения заголовка окна организуется
// вручную с помощью переменной типа PChar, а не string. По
// своим функциональным возможностям оба варианта равноценны.
function EnumWindowsProc(Wnd: HWND; ParentNode: TTreeNode): Bool; stdcall;
const
ClassNameLen = 512;
var
TextLen: Integer;
Text: PChar;
ClassName: array[0..ClassNameLen — 1] of Char;
Node: TTreeNode;
NodeName: string;
begin
Result:= True;
if Assigned(ParentNode) and (GetParent(Wnd) <> HWND(ParentNode.Data)) then Exit;
// Здесь, в отличие от предыдущего варианта к длине,
// получаемой через WM_GETTEXTLENGTH, добавляется
// единица, потому что нужно вручную учесть добавочный
// байт для завершающего нуля.
TextLen:= SendMessage(Wnd, WM_GETTEXTLENGTH, 0, 0) + 1;
// Выделяем требуемое количество памяти. Так как
// компилятор не освободит эту памяти автоматически,
// необходимо использовать блок try/finally, иначе будут
// утечки памяти при исключениях.
Text:= StrAlloc(TextLen);
try
// Так как для буфера даже при пустом заголовке будет
// выделен хотя бы один байт, здесь можно отправлять
// WM_GETTEXT, не проверяя длину строки, как это было
// в предыдущем варианте — буфер всегда будет
// корректным.
SendMessage(Wnd, WM_GETTEXT, TextLen, LParam(Text));
// Обрезаем слишком длинною строку. Модифицировать
// PChar сложнее, чем string. Вставка нуля в середину
// строки приводит к тому, что все API-функции будут
// игнорировать "хвост", но на работу StrDispose это не