Обход дерева каталогов является одной из классических прикладных задач на применение
рекурсии. В Windows штатных API-функций для этого нет, поэтому поиск файлов выполняется при помощи пары API-функций
FindFirstFile и
FindNextFile. Совершенно непонятно, почему разработчики Windows не дали возможность точно настраивать критерии поиска, ограничившись только маской имени файла. Даже в MS-DOS для решения аналогичной задачи можно было задать по крайней мере атрибуты файлов, например для поиска только каталогов. Более расширенные возможности для поиска предоставляет API-функция
FindFirstFileEx, но она доступна только в операционной системе Windows 2000 и выше.
Для рекурсивного обхода дерева каталогов я написал следующую функцию. Она сканирует дерево каталогов, начиная с указанного, и передает все найденные имена файлов в заданную функцию-обработчик. Все действия с найденными файлами выполняются уже в ней.
;------------------------------------------------------------------
; Функция рекурсивного обхода дерева каталогов
; (C) ManHunter / PCL
; http://www.manhunter.ru
;
; Параметры вызова:
; lpFStr - указатель на начальный каталог без завершающего слеша.
; он должен быть в буфере размером не менее MAX_PATH байт
; lpProc - указатель на callback-функцию для передачи имен файлов,
; это обязательный параметр
; dFlag - флаг "передавать в callback-функцию имена найденных
; каталогов" (TRUE/FALSE)
;------------------------------------------------------------------
proc FindFileRecursive lpFStr:dword,lpProc:dword,dFlag:dword
local hFind:DWORD ; Локальный хэндл текущего поиска
locals
FndData WIN32_FIND_DATA ; Локальная структура WIN32_FIND_DATA
endl
; Сохранить изменяемые регистры
push ebx ecx edx
; Добавить к пути поиска '\*.*'
invoke lstrcat,[lpFStr],ff_mask
; Найти первый файл
lea eax,[FndData]
push eax
invoke FindFirstFile,[lpFStr]
; В случае ошибки полностью прекратить дальнейшее сканирование
cmp eax,INVALID_HANDLE_VALUE
jne @f
xor eax,eax
jmp ff_exit
@@:
; Сохранить хэндл текущего поиска
mov [hFind],eax
ff_chk_file:
; Проверить имя файла на недопустимое
lea eax,[FndData.cFileName]
push eax
; Имя файла '.'
invoke lstrcmp,ff_skip1
or eax,eax
; Да, пропустить
jz ff_next_file
lea eax,[FndData.cFileName]
push eax
; Имя файла '..'
invoke lstrcmp,ff_skip2
or eax,eax
; Да, пропустить
jz ff_next_file
; Если установлен флаг dFlag=TRUE, то передавать в callback-процедуру
; все найденные результаты, в том числе и каталоги
cmp [dFlag],0
jne @f
; Установлен флаг передавать только файлы. Проверить атрибуты
; найденного файла
mov eax,[FndData.dwFileAttributes]
and eax,FILE_ATTRIBUTE_DIRECTORY
; Это каталог, пропустить
jnz ff_do_not_callback
@@:
; Вычислить длину текущей строки поиска и обрезать '*.*'
invoke lstrlen,[lpFStr]
sub eax,3
add eax,[lpFStr]
mov byte [eax],0
push eax
; Дописать к пути имя найденного файла или каталога
lea eax,[FndData.cFileName]
push eax
invoke lstrcat,[lpFStr]
; Передать имя файла в callback-функцию
stdcall [lpProc],[lpFStr]
; Вернуть маску поиска на место
pop ecx
mov dword [ecx],'*.*'
; Если callback-функция вернула 0, то прекратить сканирование
or eax,eax
jz ff_stop_scan
; Это каталог?
mov eax,[FndData.dwFileAttributes]
and eax,FILE_ATTRIBUTE_DIRECTORY
je ff_next_file
ff_do_not_callback:
; Вычислить длину текущей строки поиска и обрезать '*.*'
invoke lstrlen,[lpFStr]
sub eax,3
add eax,[lpFStr]
mov byte [eax],0
push eax
; Дописать к пути имя найденного каталога
lea eax,[FndData.cFileName]
push eax
invoke lstrcat,[lpFStr]
; Рекурсивный вызов поиска файлов в новом каталоге
stdcall FindFileRecursive,[lpFStr],[lpProc],[dFlag]
; Вернуть маску поиска на место
pop ecx
mov dword [ecx],'*.*'
; Если callback-функция вернула 0, то прекратить сканирование
or eax,eax
jz ff_stop_scan
ff_next_file:
; Найти следующий файл
lea eax,[FndData]
push eax
invoke FindNextFile,[hFind]
or eax,eax
; Файл найден, обработать его
jnz ff_chk_file
; По умолчанию установить флаг "продолжать сканирование"
mov eax,TRUE
ff_stop_scan:
; Закрыть хэндл текущего поиска
push eax
invoke FindClose,[hFind]
pop eax
ff_exit:
; Восстановить измененные регистры
pop edx ecx ebx
; Возврат из процедуры.
; Код возврата EAX=1 - продолжать сканирование, EAX=0 - стоп
ret
ff_mask db '\*.*',0 ; Маска файлов для поиска
ff_skip1 db '.',0 ; Запрещенное имя файла
ff_skip2 db '..',0 ; Запрещенное имя файла
endp
Путь к начальному каталогу должен размещаться в буфере размером не менее определенного в системе MAX_PATH (обычно 256 символов). Обязательным параметром является адрес callback-функции RecursiveFindFileProc, которой поочередно передаются все результаты поиска. Флаг dFlag указывает, будут передаваться в callback-функцию все найденные результаты, включая каталоги, или же будут передаваться только найденные файлы. Код возврата из FindFileRecursive EAX=1 - сканирование было закончено, когда больше ни одного файла не было найдено, EAX=0 - сканирование завершилось аварийно или же было остановлено из callback-функции.
Callback-функция для обработки результатов RecursiveFindFileProc имеет следующую структуру:
;------------------------------------------------------------------
; Callback-функция рекурсивного обхода каталогов
; Параметры:
; lpFile - указатель на полный путь и имя найденного файла
; На выходе:
; EAX=1 - продолжать сканирование
; EAX=0 - остановить сканирование
; ВНИМАНИЕ!!! Ни в коем случае нельзя вносить никакие изменения
; непосредственно в переданную строку имени файла! Если требуется
; ее обработка или модификация, то необходимо сделать копию и все
; действия выполнять уже с копией!
;------------------------------------------------------------------
proc RecursiveFindFileProc lpFile:dword
push ecx edx ebx
...
; Здесь выполняются действия с файлом
...
mov eax,1 ; Продолжать сканирование
pop ebx edx ecx
ret
endp
После выполнения нужных вам действий с найденным файлом, функция должна вернуть EAX=1 если требуется продолжать сканирование, и EAX=0 если дальнейшее сканирование надо остановить. Очень важно, чтобы в переданную строку имени файла не вносились никакие изменения, так как эта строка будет использоваться в дальнейшем поиске. Если требуется выполнить какие-либо действия с этой строкой, то создавайте ее копию в другом участке памяти и все действия выполняйте уже с копией.
В приложении пример программы, ищущей все ini-файлы в папке с установленной Windows. Для создания списка используется функция
AddLog из предыдущих публикаций.