Шаг 45.
Поиск файлов

    На этом шаге мы рассмотрим организацию поиска файлов.

    Для поиска файлов в Windows существуют две функции FindFirstFile и FindNextFile, очень похожие на аналогичные функции MS-DOS и, как и там, работающие в паре. При успешном поиске первая функция возвращает некое число или идентификатор, который затем используется второй функцией для продолжения поиска.

    Первым параметром функции FindFirstFile является указатель на строку для поиска файлов, второй параметр - указатель на структуру, которая получает информацию о найденных файлах. Функция FindNextFile первым своим параметром имеет идентификатор, полученный первой функцией, а вторым параметром - указатель на структуру. Эту структуру можно изучить по приведенной ниже программе.

    Отличие этих функций от соответствующих функций MS-DOS заключается в том, что на входе указывается только маска поиска (*.*, *.ЕХЕ и т. п.). Если файл найден, то по возвращаемой структуре, где содержится вся информация об этом файле, вы уже можете решать, подходит он или нет. В MS-DOS для поиска требовалось еще указать атрибут файла.

    Программа, представленная ниже, осуществляет поиск файлов в указанном каталоге. Программа может иметь один или два параметра или не иметь их вовсе. Если имеются два параметра, то первый параметр трактуется как каталог для поиска, причем программа учитывает, есть ли на конце косая черта или нет (допустимо с:, с:\, c:\windows\, c:\windows\system и т. п.). Второй параметр (в программе он третий, так как первым считается командная строка), если он есть, представляет собой маску поиска. Если его нет, то маска поиска берется в виде *.*. Наконец, если параметров нет вообще, то поиск осуществляется в текущем каталоге по маске *.*. Эту программу легко развить и сделать из нее полезную утилиту. Предоставляем это читателю. Ниже будет дан комментарий к приведенной программе.

.386P
;Плоская модель памяти.
.MODEL FLAT, STDCALL
;Константы.
STD_OUTPUT_HANDLE  equ  -11
STD_INPUT_HANDLE   equ  -10
;Прототипы внешних процедур.
EXTERN  wsprintfA: NEAR
EXTERN  CharToOemA@8:NEAR
EXTERN  GetStdHandle@4:NEAR
EXTERN  WriteConsoleA@20:NEAR
EXTERN  ReadConsoleA@20:NEAR
EXTERN  ExitProcess@4:NEAR
EXTERN  GetCommandLineA@0: NEAR
EXTERN	lstrcat@8:NEAR
EXTERN	FindFirstFileA@8:NEAR
EXTERN	FindNextFileA@8:NEAR
EXTERN	FindClose@4:NEAR
;-----------------------------------------
;Структура, используемая для поиска файла
;при помощи функций FindFirstFile и FindNextFile.
_FIND STRUC
      ATR     DWORD ? ;Атрибут файла.
      CRTIME  DWORD ? ;Время создания файла.
              DWORD ?
      ACTIME  DWORD ? ;Время доступа к файлу.
              DWORD ?
      WRTIME  DWORD ? ;Время модификации файла.
              DWORD ? 
      SIZEH   DWORD ? ;Размер файла.
      SIZEL   DWORD ? 
              DWORD ? ;Резерв.
              DWORD ?
      NAM     DB 260 DUP(0) ;Длинное имя файла. 

      ANAM    DB 14 DUP(0) ;Короткое имя файла.
_FIND ENDS
;-----------------------------------------
;Директивы компоновщику для подключения библиотек 
includelib \masm32\lib\user32.lib 
includelib \masm32\lib\kernel32.lib
;-----------------------------------------
;Сегмент данных.
_DATA SEGMENT DWORD PUBLIC USE32 'DATA' 
      BUF      DB  0
               DB  100 dup(0)
      LENS     DWORD ? ;Количество выведенных символов.
      HANDL    DWORD ?
      HANDL1   DWORD ?
      MASKA    DB   "*.*",0
      AP       DB   "\",0
      FIN      _FIND  <0>
      TEXT     DB  "Для  продолжения нажмите  клавишу ENTER",13,10,0
      BUFIN    DB  10  DUP(0)
      FINDH    DWORD  ?
      NUM      DB  0
      NUMF     DWORD  0   ;Счетчик файлов.
      NUMD     DWORD 0    ;Счетчик каталогов.
      FORM     DB  "Число найденных файлов:   %lu",0
      FORM1    DB  "Число найденных  каталогов:   %lu",0
      BUFER    DB   100   DUP(?)
      DIR      DB   " <DIR>",0
      PAR      DD  0   ;Количество параметров. 
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT  DWORD  PUBLIC USE32   'CODE' 
START: 
;Получить  HANDLE  вывода.
      PUSH    STD_OUTPUT_HANDLE
      CALL    GetStdHandle@4
      MOV     HANDL,EAX 
;Получить  HANDL1  ввода.
      PUSH    STD_INPUT_HANDLE
      CALL    GetStdHandle@4
      MOV     HANDL1,EAX 
;Преобразовать  строки для вывода.
      PUSH    OFFSET TEXT
      PUSH    OFFSET TEXT
      CALL    CharToOemA@8
      PUSH    OFFSET FORM
      PUSH    OFFSET FORM
      CALL    CharToOemA@8
      PUSH    OFFSET FORM1
      PUSH    OFFSET FORM1
      CALL    CharToOemA@8
;Получить количество параметров.
      CALL     NUMPAR
      MOV      PAR,EAX 
;Если параметр один, то искать в текущем каталоге.
      CMP      EAX,1
      JE       NO_PAR
;----------------------------------------
;Получить параметр номером EDI.
      MOV      EDI,2
      LEA      EBX,BUF
      CALL     GETPAR
      PUSH     OFFSET BUF
      CALL     LENSTR 
;Если в конце нет "\" - добавим.
      CMP      BYTE PTR [BUF+EBX-1],"\"
      JE       NO_PAR
      PUSH     OFFSET AP
      PUSH     OFFSET BUF
      CALL     lstrcat@8 
;Нет ли еще параметра, где задана маска поиска.
      CMP      PAR,3
      JB       NO_PAR 
;Получить параметр - маску поиска.
      MOV      EDI,3
      LEA      EBX,MASKA
      CALL     GETPAR 
NO_PAR:
;----------------------------------------
      CALL     FIND 
;Вывести количество файлов.
      PUSH     NUMF
      PUSH     OFFSET FORM
      PUSH     OFFSET BUFER
      CALL     wsprintfA
      LEA      EAX,BUFER
      MOV      EDX,1
      CALL     WRITE 
;Вывести количество каталогов.
      PUSH     NUMD
      PUSH     OFFSET FORM1 
      PUSH     OFFSET BUFER 
      CALL     wsprintfA 
      LEA      EAX, BUFER 
      MOV      EDX,1 
      CALL     WRITE 
_END:
      PUSH     0
      CALL     ExitProcess@4
;****************************
;Область процедур.
;****************************
;Вывести строку (в конце перевод строки).
;EAX - на начало строки.
;EDX - с переводом строки или без.
WRITE PROC
;Получить длину параметра.
      PUSH     EAX
      CALL     LENSTR
      MOV      ESI,EAX
      CMP      EDX,1
      JNE      NO_ENT 
;В конце - перевод строки.
      MOV      BYTE  PTR   [EBX+ESI],13
      MOV      BYTE  PTR   [EBX+ESI+1],10
      MOV      BYTE  PTR   [EBX+ESI+2],0
      ADD      EBX,2 
NO_ENT: 
;Вывод строки.
      PUSH     0
      PUSH     OFFSET LENS
      PUSH     EBX
      PUSH     EAX
      PUSH     HANDL
      CALL     WriteConsoleA@20
      RET
WRITE ENDP
;Процедура определения длины строки 
;Строка  -   [ЕВР+08Н].
;Длина в ЕВХ. 
LENSTR PROC
      PUSH     EBP
      MOV      EBP,ESP
      PUSH     EAX
;---------------------------------------
      CLD
      MOV      EDI,DWORD PTR [EBP+08H]
      MOV      EBX,EDI
      MOV      ECX,100 
;Ограничить длину строки.
      XOR      AL,AL
      REPNE    SCASB ;Найти символ 0.
      SUB      EDI,EBX ;Длина строки, включая 0.
      MOV      EBX,EDI
      DEC      EBX
      POP      EAX
      POP      EBP
      RET      4
LENSTR ENDP
;Процедура определения количества параметров в строке. 
;Определить количество параметров (->EAX) 
NUMPAR PROC
      CALL     GetCommandLineA@0
      MOV      ESI,EAX ;Указатель на строку.
      XOR      ECX,ECX ;Счетчик.
      MOV      EDX,1   ;Признак.
L1:
      CMP      BYTE PTR [ESI],0
      JE       L4
      CMP      BYTE PTR [ESI],32
      JE       L3
      ADD      ECX,EDX ;Номер параметра.
      MOV      EDX,0
      JMP      L2
L3:
      OR       EDX,1 
L2:
      INC      ESI
      JMP      L1 
L4:
      MOV      EAX,ECX
      RET
NUMPAR ENDP
;Получить параметр из командной строки. 
;ЕВХ - указывает на буфер, куда будет помещен параметр 
;В буфер помещается строка с нулем на конце. 
;EDI - номер параметра. 
GETPAR PROC
      CALL     GetCommandLineA@0
      MOV      ESI,EAX  ;Указатель  на строку.
      XOR      ECX,ECX  ;Счетчик.
      MOV      EDX,1    ;Признак. 
L1:
      CMP      BYTE  PTR [ESI], 0
      JE       L4		
      CMP      BYTE  PTR [ESI], 32
      JE       L3		
      ADD      ECX,EDX   ;Номер параметра.
      MOV      EDX,0		
      JMP      L2		
L3:			
      OR       EDX,1		
L2:			
      CMP      ECX,EDI		
      JNE      L5		
      MOV      AL, BYTE PTR [ESI]
      MOV      BYTE PTR [EBX], AL
      INC      EBX		
L5:
      INC      ESI		
      JMP      L1		
L4:
      MOV      BYTE  PTR   [EBX],0
      RET
GETPAR ENDP
;Поиск в каталоге файлов и их вывод.
;имя каталога в BUF.
FIND PROC 
;Путь с маской.
      PUSH     OFFSET MASKA
      PUSH     OFFSET BUF
      CALL     lstrcat@8 
;Здесь начало поиска.
      PUSH     OFFSET FIN
      PUSH     OFFSET BUF
      CALL     FindFirstFileA@8
      CMP      EAX,-1
      JE       _ERR 
;Сохранить дескриптор поиска.
      MOV      FINDH,EAX 
LF: 
;Исключить "файлы" "." и "..".
      CMP      BYTE PTR FIN.NAM,"."
      JE       _NO 
;Не каталог ли?
      TEST     BYTE PTR FIN.ATR,10H
      JE       NO_DIR
      PUSH     OFFSET DIR
      PUSH     OFFSET FIN.NAM
      CALL     lstrcat@8
      INC      NUMD
      DEC      NUMF 
NO_DIR: 
;Преобразовать строку.
      PUSH     OFFSET FIN.NAM
      PUSH     OFFSET FIN.NAM
      CALL     CharToOemA@8 
;Здесь вывод результата.
      LEA      EAX,FIN.NAM
      MOV      EDX,1
      CALL     WRITE 
;Увеличить счетчики.
      INC      NUMF
      INC      NUM 
;Конец страницы?
      CMP      NUM,22
      JNE      _NO
      MOV      NUM,0 
;Ждать ввод строки.
      MOV      EDX,0
      LEA      EAX,TEXT
      CALL     WRITE
      PUSH     0
      PUSH     OFFSET LENS
      PUSH     10
      PUSH     OFFSET BUFIN
      PUSH     HANDL1
      CALL     ReadConsoleA@20 
_NO: 
;Продолжение поиска.
      PUSH     OFFSET  FIN
      PUSH     FINDH
      CALL     FindNextFileA@8
      CMP      EAX,0
      JNE      LF 
;Закрыть поиск.
      PUSH     FINDH
      CALL     FindClose@4 
_ERR:
      RET
FIND ENDP 
_TEXT ENDS 
      END START
Текст этой программы можно взять здесь.

    Программа, представленная на этом шаге, довольно проста. Из нового здесь вы обнаружите лишь то, как обращаться с функциями FindFirstFile и FindNextFile. Процедуры, которые используются для работы с параметрами командной строки, вы уже встречали ранее. Вывод информации осуществляется в текущую консоль, с чем вы тоже знакомы. Для получения дескриптора консоли используется функция GetStdHandle. Процедура WRITE позволила несколько упростить те участки программы, которые отвечают за вывод информации на экран. Ранее мы обещали, что не обойдем вниманием строковые API-функции. В данной программе это обещание выполнено, и наряду со строковыми процедурами "собственного изготовления" используется строковая функция lstrcat, которая осуществляет сложение (конкатенацию) строк. По поводу параметра в командной строке заметим, что при наличии в имени каталога пробела вам придется задавать имя в укороченном виде. Так, например, вместо C:\Program Files придется написать C:\Progra~1. Это должно быть понятно - пробелы отделяют параметры. Чтобы корректно решать проблему, необходимо ввести специальный разделитель для параметров, например - или 0.

    На следующем шаге мы закончим рассматривать организацию поиска файлов.




Предыдущий шаг Содержание Следующий шаг