На этом шаге мы рассмотрим организацию поиска файлов.
Для поиска файлов в 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.
На следующем шаге мы закончим рассматривать организацию поиска файлов.