Рекурсивный обход дерева каталогов

Обход дерева каталогов является одной из классических прикладных задач на применение рекурсии. В 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 из предыдущих публикаций.
Теги:
каталог
Добавлено: 10 Апреля 2018 20:14:03 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...