Ассемблер под Windows №11

Работа с файлами


Доброго времени суток, уважаемые подписчики.Сегодня мы рассмотрим написанную в прошлом уроке программу.
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.

Сайт управляется системой uCoz