Вторник, 22.07.2025, 06:39 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 5. Управление файлами(3)

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

Данная программа осуществляет поиск в указанном или текущем каталоге. Если бы программа была написана на языке высокого уровня, например Си, ее легко можно было бы видоизменить так, чтобы она осуществляла поиск по дереву каталогов. Собственно, небольшая модификация потребовалась бы только для процедуры FIND, которая должна была бы вызываться рекурсивно. Можно видеть, что эта легкость произрастает из наличия в языках высокого уровня такого элемента, как локальная переменная. Попробуем осуществить это, основываясь на материале Главы 1.2. А можно осуществить это без использования локальных переменных?

Программа на Рис. 2.5.2 немного похожа на предыдущую программу. Но поиск она осуществляет по дереву каталогов, начиная с заданного каталога. Эта программа - одна из самых сложных в книге, поэтому советую читателю скрупулезно в ней разобраться. Может быть, Вам удастся ее усовершенствовать. Я могу дать и направление, в котором возможно такое усовершенствование. Дело в том, что вторым параметром командной строки можно указать маску поиска. Если, например, указать маску "*.ЕХЕ", по этой маске будет осуществляться поиск не только файлов, но и каталогов. Этот недостаток и следовало бы устранить в первую очередь.

Поиск по дереву каталогов оптимально производить рекурсивным образом, однако для этого необходимы локальные переменные35. Смысл использования локальной переменной в рекурсивном алгоритме заключается в том, что часть данных должна сохраняться при возврате из процедуры.

В данной программе я, ради простоты, отказался от процедуры LENSTR и использую функцию API lstrlen. Кроме того, я усовершенствовал вывод так, чтобы на экран выводилось полное имя файла.


35 Конечно, можно обойтись и без них, храня данные, например, в глобальном массиве, обращаясь к той или иной области массива в зависимости от уровня рекурсии.


; файл FILES.ASM

.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 lstrcatA@8:NEAR
EXTERN lstrcpyA@8:NEAR
EXTERN lstrlenA@4: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 c:\masm32\lib\user32.lib
includelib c:\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 "*.*"
 DB 50 DUP (0)
 AP DB "\",0
 FIN _FIND <0>
 TEXT DB "Нажмите клавишу ENTER",13, 10, 0
 BUFIN DB 10 DUP (0) ; буфер ввода
 NUM DB 0
 NUMF DWORD 0 ; счетчик файлов
 NUMD DWORD 0 ; счетчик каталогов
 FORM DB "Число найденных файлов: %lu",0
 FORM1 DB "Число найденных каталогов: %lu",0
 DIRN DB " <DIR>",0
 PAR DWORD 0
 PRIZN DB 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
 CMP PAR,3
 JB NO_PAR
; получить параметр - маску поиска
 MOV EDI,3
 LEA EBX, MASKA
 CALL GETPAR
NO_PAR:
;----------------------------------
 PUSH OFFSET BUF
 CALL FIND
; вывести количество файлов
 PUSH NUMF
 PUSH OFFSET FORM
 PUSH OFFSET BUF
 CALL wsprintfA
 LEA EAX, BUF
 MOV EDX,1
 CALL WRITE
;+++++++++++++++++
; вывести количество каталогов
 PUSH NUMD
 PUSH OFFSET FORM1
 PUSH OFFSET BUF
 CALL wsprintfA
 LEA EAX, BUF
 MOV EDX, 1
 CALL WRITE
_END:
 PUSH 0
 CALL ExitProcess@4
; область процедур
;*************************************
; вывести строку (в конце перевод строки)
; EAX - на начало строки
; EDX - с переводом строки или без
WRITE PROC
; получить длину параметра
 PUSH EAX
 PUSH EAX
 CALL lstrlenA@4
 MOV ESI,EAX
 POP EBX
 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 EAX,2
NO_ENT:
; вывод строки
 PUSH 0
 PUSH OFFSET LENS
 PUSH EAX
 PUSH EBX
 PUSH HANDL
 CALL WriteConsoleA@20
 RET
WRITE 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

; получить параметр из командной строки
; EBX - указывает на буфер, куда будет помещен параметр
; в буфер помещается строка с нулем на конце
; 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
;-----------------------------------
; поиск в каталоге файлов и их вывод
; локальные переменные
FINDH EQU [EBP-4] ; дескриптор поиска
DIRS EQU [EBP-304] ; полное имя файла
DIRSS EQU [EBP-604] ; для хранения каталога
DIRV EQU [EBP-904] ; для временного хранения
DIR EQU [EBP+8] ; параметр - имя каталога
FIND PROC
 PUSH EBP
 MOV EBP,ESP
 SUB ESP,904
; инициализация локальных переменных
 MOV ECX,300
 MOV AL,0
 MOV EDI,0
CLR:
 MOV BYTE PTR DIRS+[EDI],AL
 MOV BYTE PTR DIRSS+[EDI],AL
 MOV BYTE PTR DIRV+[EDI],AL
 INC EDI
 LOOP CLR
; определить длину пути
 PUSH DIR
 CALL lstrlenA@4
 MOV EBX,EAX
 MOV EDI, DIR
 CMP BYTE PTR [EDI],0
 JE _OK
;если в конце нет "\" - добавим
 CMP BYTE PTR [EDI+EBX-1],"\"
 JE _OK
 PUSH OFFSET AP
 PUSH DWORD PTR DIR
 CALL lstrcatA@8
_OK:
; запомним каталог
 PUSH DWORD PTR DIR
 LEA EAX,DIRSS
 PUSH EAX
 CALL lstrcpyA@8
; путь с маской
 PUSH OFFSET MASKA
 PUSH DWORD PTR DIR
 CALL lstrcatA@8
; здесь начало поиска
 PUSH OFFSET FIN
 PUSH DWORD PTR DIR
 CALL FindFirstFileA@8
 CMP EAX,-1
 JE _ERR
; сохранить дескриптор поиска
 MOV FINDH,EAX
LF:
; исключить "файлы" "." и ".."
 CMP BYTE PTR FIN.NAM,"."
 JE _FF
;---------------------
 LEA EAX,DIRSS
 PUSH EAX
 LEA EAX,DIRS
 PUSH EAX
 CALL lstrcpyA@8
;---------------------
 PUSH OFFSET FIN.NAM
 LEA EAX, DIRS
 PUSH EAX
 CALL lstrcatA@8
; не каталог ли?
 TEST BYTE PTR FIN.ATR, 10H
 JE NO_DIR
; добавить в строку <DIR>
 PUSH OFFSET DIRN
 LEA EAX, DIRS
 PUSH EAX
 CALL lstrcatA@8
; увеличим счетчики
 INC NUMD
 DEC NUMF
; установим признак каталога
 MOV PRIZN,1
; вывести имя каталога
 LEA EAX, DIRS
 PUSH EAX
 CALL OUTF
 JMP _NO
NO_DIR:
; вывести имя файла
 LEA EAX, DIRS
 PUSH EAX
 CALL OUTF
; признак файла (не каталога)
 MOV PRIZN,0
_NO:
 CMP PRIZN,0
 JZ _F
; каталог, готовимся в рекурсивному вызову
 LEA EAX,DIRSS
 PUSH EAX
 LEA EAX, DIRV
 PUSH EAX
 CALL lstrcpyA@8
 PUSH OFFSET FIN.NAM
 LEA EAX,DIRV
 PUSH EAX
 CALL lstrcatA@8
; осуществляем вызов
 LEA EAX, DIRV
 PUSH EAX
 CALL FIND
; продолжение поиска
_F:
 INC NUMF
_FF:
 PUSH OFFSET FIN
 PUSH DWORD PTR FINDH
 CALL FindNextFileA@8
 CMP EAX,0
 JNE LF
; закрыть дескриптор поиска
 PUSH DWORD PTR FINDH
 CALL FindClose@4
_ERR:
 MOV ESP, EBP
 POP EBP
 RET 4
FIND ENDP
;----------------------------------
; страничный вывод имен найденных файлов
STRN EQU [EBP+8]
OUTF PROC
 PUSH EBP
 MOV EBP,ESP
; преобразовать строку
 PUSH DWORD PTR STRN
 PUSH DWORD PTR STRN
 CALL CharToOemA@8
; здесь вывод результата
 MOV EAX, STRN
 MOV EDX, 1
 CALL WRITE
 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:
 POP EBP
 RET 4
OUTF ENDP
_TEXT ENDS
END START

Рис. 2.5.2. Пример программы, которая осуществляет рекурсивный поиск по дереву каталогов.

Разберем ту роль, которую играют локальные переменные в процедуре FIND. Переменная FINDH - здесь хранится дескриптор поиска в данном каталоге. Рекурсивный вызов процедуры FIND может происходить и тогда, когда поиск в текущем каталоге еще не закончился. Следовательно, после возврата из рекурсии поиск должен быть продолжен. Это можно обеспечить только старым значением дескриптора. Локальная переменная обеспечивает такую возможность, поскольку она разрушается только при переходе на более низкий уровень (к родительскому каталогу).

Аналогичную роль играет переменная DIRSS. В ней хранится текущий каталог. Это важно, т.к. с помощью этой переменной формируется полное имя файла.

Переменные DIRS и DIRV играют вспомогательную роль. В принципе, вместо них можно было бы использовать и глобальные переменные. Хотя, с точки зрения эффективности рекурсивных алгоритмов, чем меньше объем локальных переменных - тем лучше.

Еще один вопрос я хочу здесь обсудить. Для передачи имени каталога при вызове процедуры используется переменная DIRV. Почему же для этой цели нельзя использовать переменную DIRSS? Причина вот в чем. В процедуру передается не само значение, а указатель (адрес). Следовательно, любые изменения с параметром DIR приведет к аналогичным изменениям с переменной DIRSS на нижнем уровне рекурсии, В чем мы, разумеется, не заинтересованы.

Трансляция программы в TASM. Основная проблема при трансляции программ на Рис. 2.5.1 и Рис. 2.5.2 возникнет с локальными метками. Локальная метка - это метка, которая действует в пределах некоторого блока программы. В нашем случае таким блоком программы является процедура. Транслятор MASM автоматически различает метки, находящиеся в пределах процедуры, и считает их локальными. Поэтому не возникает проблемы, когда в разных процедурах встречаются метки с одинаковым именем. В TASM несколько иной подход: по умолчанию метки считаются глобальными. Локальные метки должны иметь перед именем обозначение "@@". Кроме того, в начале программы следует поставить директиву LOCALS. Сделав нужные метки локальными и поставив директиву LOCALS, Вы без труда, уже известными действиями, приведете программу к виду, приемлемому для TASM. Не забудьте о преобразовании wsprintfA -> _wsprintfA.

Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0