Assembler для Windows

              

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

Файл - важнейшая образующая практически любой программы. С появлением скоростных дисков больших объемов значение файлов сильно возросло. Использование API-функций управления файлами может сделать вашу программу более эффективной и производительной. Большинство программ данной главы являются консольными, потому что консольная программа как никакая другая подходит для демонстрации файловой обработки.

I

32-битная FAT. Характеристики файлов

Давая описание характеристикам файлов, я буду основываться на характеристиках, которыми манипулируют функции API. О типах и структуре файловых систем речь пойдет далее.

Атрибут файла. Размер - DWORD.
FILE_ATTRIBUTE_READONLY equ 1h
Атрибут - "только чтение". Приложения могут лишь читать данный файл. Соответственно, попытка открыть файл для записи вызовет ошибку.
FILE_ATTRIBUTE_HIDDEN equ 2h
Атрибут - "скрытый файл". "Невиден" при обычном просмотре каталога (см. ниже, поиск файлов).
FILE_ATTRIBUTE_SYSTEM equ 4h
Атрибут - "системный файл". Говорит о том, что данный файл принадлежит операционной системе.
FILE_ATTRIBUTE_DIRECTORY equ 10h
Атрибут - "директорий", С файлами с таким атрибутом операционная система обращается особым образом, считая его каталогом, т.е. считая его списком файлов, состоящим из записей по 32 байта.
FILE_ATTRIBUTE_ARCHIVE equ 20h
Со времен MS DOS таким атрибутом отмечались файлы, над которыми не произведена операция BACKUP или XCOPY. Для целей программирования данный атрибут эквивалентен нулевому значению атрибута.
FILE_ATTRIBUTE_NORMAL equ 80h
Данный атрибут означает, что у файла не установлены другие атрибуты.
FILE_ATTRIBUTE_TEMPORARY equ 100h
Атрибут означает, что данный файл предназначен для временного хранения. После закрытия файла система должна его удалить.
FILE_ATTRIBUTE_COMPRESSED equ 800h
Для файла это означает, что он сжат системой; для директория - что вновь создаваемый файл по умолчанию должен быть сжат.
FILE_ATTRIBUTE_OFFLINE equ 1000h
Данный атрибут означает, что данные файла не доступны в данный момент.

Смену атрибута можно осуществить функцией SetFileAttributes, получить значение атрибута функцией GetFileAttributes. Следует заметить, что если операционная система не накладывает никаких ограничений на возможности изменения атрибутов файлов, то фактически обесценивается смысл самих атрибутов - всегда можно снять атрибут "только чтение" и делать с файлом, что заблагорассудится.

Файл имеет три временные характеристики: время создания, время последней модификации, время последнего доступа. Время отсчитывается в наносекундных интервалах начиная с 12.00 по полудни 1 января 1600 года и хранится в двух 32-битных величинах. Надо сказать, что время хранится в так называемых универсальных координатах и должно еще быть преобразовано в локальное время (функция FileTimeToLocalFileTime). Получить значение всех трех времен можно функцией GetFileTime.

Длина файла в байтах хранится обычно в двух 32-битных величинах либо в одной 64-битной величине. Если 32-битные величины обозначить как l1 (младшая часть) и l2 (старшая часть), то 64-битная величина выразится формулой l2*0FFFFH+l1. Paзмер файла можно получить функцией GetFileSize.

Кроме указанных характеристик, файл, разумеется, имеет имя. При этом мы будем различать длинное и короткое имя. Точно также будем различать полный путь (со всеми длинными именами) и укороченный путь (все длинные имена заменены укороченными). Необходимость использования укороченного имени и пути диктуется, прежде всего, тем, что некоторые программы получают путь или имя на стандартный вход и трактуют пробелы как разделители для параметров. Преобразование длинного имени в короткое можно осуществить функцией GetShortPathName, которая работает и для имени, и для пути. Обратное преобразование можно осуществить функиией GetFullPathName.

В данной книге мы не рассматриваем вопроса о прямом доступе к диску. Но вопрос о структуре записей каталога может у читателя все же возникнуть. Это и понятно, ведь с переходом к FAT3233, во-первых, появилась возможность хранения файлов с длинным именем, во-вторых, у файла, кроме времени и даты модификации, появились еще время и дата создания и доступа. Где же все это хранится?

Для того чтобы ответить на поставленный вопрос, вспомним, что каталог в файловых системах FAT34 делится на записи длиной 32 байта. Ниже приводится структура записи для FAT32. Пустыми записями считаются записи, содержащие нулевые байты, либо начинающиеся с кода E5H (для удаленных записей). На файл с обычным именем (восемь байт на имя и 3-на расширение) отводится 32 байта. В байте со смещением +11 содержится атрибут файла. Если атрибут файла равен 0FH, то система считает, что здесь содержится длинное имя. Длинное имя кодируется в UNICODE и записывается в обратном порядке. За одной или несколькими записями с длинным именем должна следовать запись с обычным именем, содержащим знак "~" - тильда. Здесь содержится также остальная информация о файле. Как видите, алгоритм просмотра каталога с выявлением информации о файле весьма прост. Обратимся теперь к структуре записи каталога. В старой операционной системе MS DOS байты с 12 по 21 никак не использовались системой (см. [1]). Новой системе они пригодились. Ниже в таблице дана новая структура записи каталога.

СмещениеРазмерСодержимое
(+0) 8Имя файла или каталога, выровненное на левую границу и дополненное пробелами.
(+8) 3Расширение имени файла, выровненное на левую границу и дополненное пробелами.
(+11)1Атрибут файла.
(+12)2Время доступа.
(+14)2Время создания.
(+16)2Дата создания.
(+18)2Дата доступа.
(+20)2Два старших байта номера первого кластера файла.
(+22)2Время модификации файла.
(+24)2Дата модификации файла.
(+26)2Два младших байта номера первого кластера файла.
(+28)4Размер файла в байтах.

Как видите, все байты 32-байтной записи каталога теперь заняты. Лишний раз убеждаешься в первоначальной непродуманности файловой системы MS DOS, Это касается, в частности, длины файла. Как можно заметить, на длину файла отводится всего 4 байта. А как найти длину файла, если на нее требуется более 4 байт? Разумеется, в этом случае следует считать, что в каталоге хранятся младшие байты длины, а полную длину легко определить, обратившись к таблице размещения файлов. Но, согласитесь, что это уже явная недоработка. Странно также выглядит функция GetFileSize, которая возвращает четыре младших байта длины файла, старшие же байты возвращаются во втором параметре функции.

Иное дело в файловой системе NTFS, поддерживаемой Windows NT, изначально планируемой для работы с файлами больших размеров. Здесь для индексации кластеров используются 64-битные поля.


33 В начале Windows 95 работала с 16-битной FAT, но длинные имена уже поддерживала.

34 FAT (File Allocation Table) - один из элементов, на котором базируются файловые системы MS DOS и Windows 9х. По этой причине часто такие файловые системы называют FAT системами.


II

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

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

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

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

; файл 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 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 "*.*",0
АР 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 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
PUSH OFFSET BUF
CALL LENSTR
; если в конце нет "\" - добавим
CMP BYTE PTR [BUF+EBX-1],"\"
JE NO_PAR
PUSH OFFSET AP
PUSH OFFSET BUF
CALL lstrcatA@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
; процедура определения длины строки
; строка - [EBP+08Н]
; длина в EBX
LENSTR PROC
PUSH EBP
MOV EBP,ESP
PUSH EAX
;----------------------
CLD
MOV EDI, DWORD PTR [EBP+08Н]
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
; получить параметр из командной строки
; 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
; поиск в каталоге файлов и их вывод
; имя каталога в BUF
FIND PROC
; путь с маской
PUSH OFFSET MASKA
PUSH OFFSET BUF
CALL lstrcatA@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 lstrcatA@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

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

Программа на Рис. 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.

III

Приемы работы с двоичными файлами*. Манипуляция внешними файлами36 основывается на нескольких функциях API, главной и наиболее сложной из которых является функция CreateFile.

В связи с ограниченностью объема книги, мы не можем подробно остановиться на свойствах функции CreateFile. Однако замечу, что с помощью этой функции можно не только создавать или открывать файл, но и такие объекты как каналы (PIPE), консоли, устройства жесткого диска (disk device), коммуникационный ресурс и др. Функция различает устройство по структуре имени. К примеру, "C:\config.sys" определяет файл, a "CONOUT$" - буфер вывода текущей консоли.

Сейчас я представлю две простые, но весьма важные программы (Рис. 2.5.3(1) и Рис. 2.5.3(2)). Обе программы выводят содержимое текстового файла37, имя которого указано в командной строке, в текущую консоль. В первом случае мы получаем дескриптор текущей консоли стандартным способом. Во втором случае - открываем консоль как файл и, соответственно, выводим туда информацию, как в файл. Хочу обратить Ваше внимание на роль буфера, в который читается содержимое файла. Поэкспериментируйте с размером буфера, взяв для пробы большой текстовый файл. Интересно, что в указанных программах никак не учитывается структура текстового файла. Для такого ввода-вывода это ни к чему. Ниже мы поговорим и о структуре текстового файла.


36 Имеется в виду файлами, расположенными на внешнем устройстве.

37 Точнее любого файла, но смысл выводить файл на консоль именно таким образом имеется только для текстового файла.

* Кажется, автор имеет ввиду текстовые файлы, а не бинарные, как это следует из дальнейшего повествования :)


;файл FILES1.ASM
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
GEN = GENERIC_READ or GENERIC_WRITE
SHARE = 0
OPEN_EXISTING equ 3
; прототипы внешних процедур
EXTERN GetStdHandle@4:NEAR
EXTERN WriteConsoleA@20:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN ReadFile@20:NEAR
;-------------------------------------------------
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;-------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
HANDL DWORD ?
HFILE DWORD ?
BUF DB 100 DUP (0)
BUFER DB 300 DUP (0)
NUMB DWORD ?
NUMW DWORD ?
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить HANDLE вывода
PUSH STD_OUTPUT_HANDLE
CALL GetStdHandle@4
MOV HANDL,EAX
; получить количество параметров
CALL NUMPAR
CMP EAX,1
JE NO_PAR
;------------------------------
; получить параметр номером EDI
MOV EDI,2
LEA EBX, BUF
CALL GETPAR
; открыть файл
PUSH 0 ; должен быть равен 0
PUSH 0 ; атрибут файла (если создаем)
PUSH OPEN_EXISTING ; как открывать
PUSH 0 ; указатель на security attr
PUSH 0 ; режим общего доступа
PUSH GEN ; режим доступа
PUSH OFFSET BUF ; имя файла
CALL CreateFileA@28
CMP EAX,-1
JE NO_PAR
MOV HFILE, EAX
L00:
; прочесть в буфер
PUSH 0
PUSH OFFSET NUMB
PUSH 300
PUSH OFFSET BUFER
PUSH HFILE
CALL ReadFile@20
; вывести содержимое буфера на консоль
PUSH 0
PUSH OFFSET NUMW
PUSH NUMB
PUSH OFFSET BUFER
PUSH HANDL
CALL WriteConsoleA@20
; проверить, не последние ли байты прочитаны
CMP NUMB, 300
JE L00
; закрыть файл
PUSH HFILE
CALL CloseHandle@4
; конец работы программы
NO_PAR:
PUSH 0
CALL ExitProcess@4
; область процедур
; процедура определения количества параметров в строке
; определить количество параметров (->ЕАX)
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
_TEXT ENDS
END START

Рис. 2.5.3(1). Вывод на консоль содержимого текстового файла. Первый способ.

; файл FILES2.ASM
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
GEN = GENERIC_READ or GENERIC_WRITE
SHARE = 0
OPEN_EXISTING equ 3
; прототипы внешних процедур
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN ReadFile@20:NEAR
EXTERN WriteFile@20:NEAR
;-------------------------------------------------
; директивы компоновщику для подключения библиотек
; includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;-------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
HANDL DWORD ?
HFILE DWORD ?
BUF DB 100 DUP (0)
BUFER DB 300 DUP (0)
NUMB DWORD ?
NUMW DWORD ?
NAMEOUT DB "CONOUT$"
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить HANDLE вывода (консоли) как файла
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GEN
PUSH OFFSET NAMEOUT
CALL CreateFileA@28
MOV HANDL,EAX
; получить количество параметров
CALL NUMPAR
CMP EAX, 1
JE NO_PAR
;---------------------------------------------
; получить параметр номером EDI
MOV EDI,2
LEA EBX,BUF
CALL GETPAR
; открыть файл
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GEN
PUSH OFFSET BUF
CALL CreateFileA@28
CMP EAX,-1
JE NO_PAR
MOV HFILE,EAX
L00:
; прочесть в буфер
PUSH 0
PUSH OFFSET NUMB
PUSH 300
PUSH OFFSET BUFER
PUSH HFILE
CALL ReadFile@20
; вывести на консоль как в файл
PUSH 0
PUSH OFFSET NUMW
PUSH NUMB
PUSH OFFSET BUFER
PUSH HANDL
CALL WriteFile@20
CMP NUMB, 300
JE L00
; закрыть файл
PUSH HFILE
CALL CloseHandle@4
; конец работы программы
NO_PAR:
PUSH 0
CALL ExitProcess@4
; область процедур
; процедура определения количества параметров в строке
; определить количество параметров (->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
_TEXT ENDS
END START

Рис. 2.5.3(2). Вывод на консоль содержимого текстового фаша. Второй способ.

Сейчас мы поговорим более подробно о структуре текстового файла. При работе с языками высокого уровня теряются определенные алгоритмические навыки. Это касается, в частности, и работы с текстовыми файлами. Ассемблер не дает расслабиться. Рассмотрим возможные варианты работы с текстовыми файлами.

Основным признаком текстового файла является то, что он состоит из строк разной длины. Строки отделены друг от друга разделителями. Чаще всего это последовательность двух кодов - 13 и 10. Возможны и другие варианты, например, некоторые DOS-редакторы отделяли строки только одним кодом 13.

Построчное чтение текстового файла можно осуществить четырьмя наиболее очевидными способами.

  1. Побайтное чтение из файла. Как только достигаем символа-разделителя, производим действие над считанной строкой и переходим к чтению следующей строки. При этом, разумеется, следует учесть, что на конце файла может не быть символа-разделителя. Если кто-то решит, что это слишком медленный способ, то замечу, что Windows неплохо кэширует диск, так что все выглядит не так уж плохо.
  2. Чтение в небольшой буфер, но так чтобы туда входила, по крайней мере, одна строка. Прочитав, находим в буфере конец строки и производим над ней какое-либо действие. Далее следует обратиться к файлу и передвинуть указатель так, чтобы он был в файле на начале следующей строки и, разумеется, повторить действие.
  3. Чтение в произвольный буфер. После чтения производится поиск всех строк, попавших в буфер, и совершение над ними действий. При этом с большой вероятностью должна возникнуть ситуация, когда одна строка неполностью умещается в буфере. Мы обязаны учесть такую возможность.
  4. Чтение в буфер, в который помещается весь файл. Это частный случай третьего подхода, и наиболее простой с точки зрения программирования.

В программе на Рис. 2.5.4 реализуется третий подход.

; файл FILES2.ASM
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_OUTPUT_HANDLE equ -11
GENERIC_READ equ 80000000h
GENERIC_WRITE equ 40000000h
GEN = GENERIC_READ or GENERIC_WRITE
SHARE = 0
OPEN_EXISTING equ 3
; прототипы внешних процедур
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN ReadFile@20:NEAR
EXTERN WriteFile@20:NEAR
EXTERN CharToOemA@8:NEAR
;-------------------------------------------------
; директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;-------------------------------------------------
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
HANDL DWORD ? ; дескриптор консоли
HFILE DWORD ? ; дескриптор файла
BUF DB 100 DUP (0) ; буфер для параметров
BUFER DB 1000 DUP (0) ; буфер для файла
NAMEOUT DB "CONOUT$"
INDS DD 0 ; номер символа в строке
INDB DD 0 ; номер символа в буфере
NUMB DD ?
NUMC DD ?
PRIZN DD 0
STROKA DB 300 DUP (0)
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить HANDLE вывода (консоли) как файла
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GEN
PUSH OFFSET NAMEOUT
CALL CreateFileA@28
MOV HANDL,EAX
; получить количество параметров
CALL NUMPAR
CMP EAX, 1
JE NO_PAR
;---------------------------------------------------
; получить параметр номером EDI
MOV EDI,2
LEA EBX,BUF
CALL GET PAR
; открыть файл
PUSH 0
PUSH 0
PUSH OPEN_EXISTING
PUSH 0
PUSH 0
PUSH GEN
PUSH OFFSET BUF
CALL CreateFileA@28
CMP EAX,-1
JE NO_PAR
MOV HFILE, EAX
;++++++++++++++++++++++++++++
L00:
; читать 1000 байт
PUSH 0
PUSH OFFSET NUMB
PUSH 1000
PUSH OFFSET BUFER
PUSH HFILE
CALL ReadFile@20
MOV INDB, 0
; проверим, есть ли в буфере байты
CMP NUMB, 0
JZ _CLOSE
; заполняем строку
L001:
MOV EDI,INDS
MOV ESI,INDB
MOV AL,BYTE PTR BUFER[ESI]
CMP AL,13 ; проверка на конец строки
JE _ENDSTR
MOV BYTE PTR STROKA[EDI],AL
INC ESI
INC EDI
MOV INDS,EDI
MOV INDB,ESI
CMP NUMB, ESI ; проверка на конец буфера
JNBE L001
; закончился буфер
MOV INDS,EDI
MOV INDB,ESI
JMP L00
_ENDSTR:
; делаем что-то со строкой
CALL OUTST
; обнулить строку
MOV INDS,0
; перейти к следующей строке в буфере
ADD INDB,2
; не закончился ли буфер?
MOV ESI,INDB
CMP NUMB, ESI
JAE L001
JMP L00
;++++++++++++++++++++++++++++++
_CLOSE:
; проверим, не пустая ли строка
CMP INDS,0
JZ CONT
; делаем что-то со строкой
CALL OUTST
CONT:
; закрыть файлы
PUSH HFILE
CALL CloseHandle@4
; конец работы программы
NO_PAR:
PUSH 0
CALL ExitProcess@4
; область процедур
; процедура определения количества параметров в строке
; определить количество параметров (->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
; вывести строку в консоль с разделителем
OUTST PROC
MOV EBX,INDS
MOV BYTE PTR STROKA[EBX],0
PUSH OFFSET STROKA
PUSH OFFSET STROKA
CALL CharToOemA@8
; в конце строки - разделитель
MOV BYTE PTR STROKA[EBX],6
INC INDS
; вывести строку
PUSH 0
PUSH OFFSET NUMC
PUSH INDS
PUSH OFFSET STROKA
PUSH HANDL
CALL WriteFile@20
RET
OUTST ENDP
_TEXT ENDS
END START

Рис. 2.5.4. Пример обработки текстового файла.

Программа на Рис. 2.5.4 демонстрирует один из возможных алгоритмов обработки текстового файла - построчное чтение текстового файла. Часть программы, занимающаяся чтением и анализом текстового файла, сосредоточена между метками L00 и CONT. Детально разберитесь в алгоритме и проникнитесь тем, что язык высокого уровня никогда не будет стимулировать написание таких алгоритмов, а значит, язык ассемблера делает нас интеллектуально богаче.