Работа с файлами
Доброго времени суток, уважаемые подписчики.Сегодня мы рассмотрим написанную в прошлом уроке программу.
include kernel32.inc
include user32.inc
include def32.inc
.386
.model flat
.data
|
Стандартные вложения/установки
error db 'Ошибка',0
error1 db 'Файл не найден,0
error2 db 'Заданный файл не существует',0
fileName dd 0
programHandle dd 0
fileHandle dd 0
memoryHandle dd 0
memoryOffset dd 0
SizeRW dd 0
|
Переменные:
error - эта надпись выдаётся в синем поле MessageBox
error1,2 - сами ошибки
filename - сюда будет занесён offset на имя файла для открытия
*Handle - handle соответствующего объекта будет храниться в соответствующей переменной
memoryOffset - сюда будет занесён offset отображённой памяти
SizeRW - эта переменная нужна для процедур WriteFile/ReadFile, по идее Microsoft - а её проверкой достигается проверка удачности произведённой процедуры, нам же она нужна только для корректной работы функции.
.code
_start:
xor ebx,ebx
push ebx
call GetModuleHandle
mov programHandle,eax
|
Стандартное начало программы, обнуление ebx и получения Handle процесса.
call GetCommandLine
mov edi,eax
mov al,20h
mov ecx,-1
repne scasb
|
В этой части программы мы получаем offset на командную строку(КС) процедурой GetCommandLine(вызывается без параметров!). После этого еах, указывающий на КС мы помещаем в edi. При этом нам надо учесть, что первым в КС всегда идёт имя запущенной программы, а потом уже возможные параметры и т.д. Поэтому мы ищем первый пробел(код 20h)в этой строке, для этого мы помещаем в al код пробела (20h), а есх максимальное его значение и производим "сравнение символа пока не равно"(repne scasb), то есть полученный после этого в edi offset указывает на символ после первого пробела в строке, то есть на первый параметр в командной строке после имя запущенной программы.
cmp byte ptr [edi],bl
jne File_OK
mov eax,offset error1
jmp program_error
|
Здесь мы проверяем само наличие какого-либо параметра, то есть если следующий байт в командной строке 0, то параметра нет, если он есть (байт не равен нулю) происходит переход на метку File_OK, если же параметр не найден, то в еах задаётся offset на нужную в данном случае ошибку и управление передаётся на обработчик ошибок программы, program_error
File_OK:
repe scasb
dec edi
|
Этими двумя действиями мы находим начало первого параметра, они необходимы, если пользователь нажал лишний пробел или два перед параметром
mov esi,edi
mov fileName,edi
|
Сохраняем offset названия файла в esi для использования и в переменную, специально созданную для этого
push ebx
push FILE_ATTRIBUTE_ARCHIVE
push OPEN_EXISTING
push ebx
push FILE_SHARE_READ
push GENERIC_READ
push esi
call CreateFile
|
Стандартная процедура открытия файла, так как мы встречаемся с ней впервые, давайте рассмотрим её более подробно, она получает следующие 8 параметров:
- Файл с атрибутами для других процессов, в нашем случае программа быстро совершает все нужные её действия с файлом и закрывает его, поэтому нет нужды задавать здесь какие-либо параметры.
- Атрибуты файла, единственный реально НУЖНЫЙ атрибут, это атрибут архивности файла, выставляем только его.
- Атрибуты создания файла, определяет как открывать файл (создать ли его или открыть в любом случае или только существующий файл), нам нужен существующий файл, поэтому выставляем этот флаг.
- Указатель на структуру атрибутов защиты, пока нам это не нужно, поэтому будет 0.
- Разделяемость файла с другими процессами, в принципе не особо нужно, но чтоб не быть жадиной лучше разрешить другим процессам читать этот файл.
- Доступ, нужный нам, сейчас мы собираемся лишь прочитать его содержимое, то есть ставим "просто чтение".
- Указатель на имя файла, думаю, что комментарии не нужны;)
test eax,eax
jnz file_opened_OK
mov eax,offset error2
jmp program_error
|
Проверка на ошибку.
file_opened_OK:
mov fileHandle,eax
push ebx
push eax
call GetFileSize
|
Сохраняем handle открытого файла и получаем его размер(естественно в еах).
mov edi,eax
push eax
push GMEM_MOVEABLE or GMEM_ZEROINIT
call GlobalAlloc
|
В начале сохраняем размер файла в edi, так как нам понадобится использовать его достаточно часто. После этого создаём объект памяти, способный вместить в себя весь файл.
mov memoryHandle,eax
push eax
call GlobalLock
mov memoryOffset,eax
|
Сохраняем его Handle и отображаем в память. После чего сохраняем и offset отображённой памяти.
push ebx
push offset SizeRW
push edi
push eax
push fileHandle
call ReadFile
push fileHandle
call CloseHandle
call CheckSymbol
|
В этом куске программы мы считываем весь файл в созданную память и закрываем файл. После чего передаём управление нашей процедуре, которая заменяет все "s" на "$" в памяти.
push ebx
push FILE_ATTRIBUTE_ARCHIVE
push TRUNCATE_EXISTING
push ebx
push FILE_SHARE_WRITE
push GENERIC_WRITE
push fileName
call CreateFile
|
Пересоздаём файл, заданный в командной строке, мы открываем его и удаляем из него всё (в Паскале это можно записать как rewrite(x)).
push ebx
push offset SizeRW
push edi
push memoryOffset
push FileHandle
call WriteFile
push fileHandle
call CloseHandle
|
Теперь мы записываем прокорректированное содержание в файл и закрываем его.
program_end:
push memoryHandle
call GlobalUnlock
push memoryHandle
call GlobalFree
push ebx
call ExitProcess
|
Это конец программы, здесь мы просто закрываем все используемые нами объекты и завершаем программу.
program_error:
push MB_IconWarning
push offset error
push eax
push ebx
call MessageBox
jmp program_end
|
На этот кусок кода передаётся управление если произошла ошибка, тогда просто выводится сообщение об ошибке и передаётся управление на завершение программы.
CheckSymbol proc
push edi
mov al,'s'
mov ecx,edi
inc ecx
mov edi,memoryOffset
|
В начале процедуры мы подготавливаем регистры, здесь стоит заметить, что перед вызовом процедуры мы сохранили размер файла, что равно размеру сохранённого в памяти, в edi. Мы помещаем это значение в регистр счётчика (есх), в al символ, который мы хотим заменить, а в edi offset на память с содержимым файла.
loop_check:
repne scasb
test ecx,ecx
jz end_check
mov byte ptr [edi-1],'$'
jmp loop_check
end_check:
|
Это главная часть программы, так называемое ядро программы, так как эти 7 строчек выполняю нужную работу, а всё остальное лишь собирает информацию и передаёт её этой процедуре. Работа нашего "ядра" происходит по следующему сценарию, программы проверяет символ за символом на наличие заданного символа до тех пор, пока либо не будет найден символ, либо не кончится источник, если источник кончился управление передаётся на метку end_check, то есть происходит завершение цикла, если же найден символ, то он заменяется на новый и цикл продолжается.
pop edi
ret
CheckSymbol endp
end _start
|
Надеюсь всё понятно...
На сегодня это всё. Если есть какие вопросы, пишите, обязательно отвечу. В следующий раз мы усовершенствуем интерфейс задачи параметров нашему окну. Пишите, Dark_Lord@RusFAQ.ru, Dark_Lord@land.ru.