...
...

Ассемблер под Windows для чайников. Часть 7

Заждались, наверное, продолжения? Что ж, ожидание только усиливает радость встречи. На сегодня у нас запланирован выход бета-версии 0.2 нашего первого текстового редактора. Он, к сожалению, по возможностям пока что немного уступает даже стандартному "Блокноту", но это ведь только во благо вашему обучению. В дальнейшем у вас будет шанс для усовершенствования программы в меру своих возможностей и, самое главное, знаний. Вы же не ждете, что я всю жизнь буду программировать за вас?

Надеюсь, что нет, потому что основная цель этих занятий — развить интерес к программированию на ассемблере под Windows и изучить его основные моменты. Ну а время покажет, кому что ближе. Кого-то потянет в низкоуровневое программирование, кто-то, привязавшись к макросам, подастся в высокий уровень, забросив ассемблер… Но это все в будущем.

А сейчас мы открываем привычный для нас FASMW.EXE и вводим код, электронную копию которого, как обычно, можно скачать на форуме. Код программы частично соответствует коду, приведенному в 6-й части цикла, но только частично. Так что если решите сэкономить время и скопировать набранные куски из прошлой версии — будьте предельно внимательны и сверяйте каждую строчку.

format PE GUI 4.0
entry start

include 'win32a.inc'
include 'encoding\WIN1251.INC'

MAXSIZE equ 260 ; максимальное имя файла в байтах
MEMSIZE equ 65537 ; размер временного буфера

section '.data' data readable writeable

title db 'Мой Первый Текстовый Редактор',0
class db 'FASMWIN32',0
edit db 'EDIT',0
saveq db 'Сохранить изменения ?',0
filter db 'Text Files',0,'*.txt',0
db 'All formats',0,'*.txt;*.asm;*.inc;*.ini',0
db 'All files',0,'*.*',0,0
fnsaved db 0 ;флаг имени файла 1=сохранено, 0=нет
errtxt db 'Код ошибки: %u',0
errbuf rb $-errtxt+10
fname rb MAXSIZE
hfile dd ?
hheap dd ?
pmem dd ?
sbuf dd ?
hwnd dd ?
hmenu dd ?
hedit dd ?
hacc dd ?
font dd ?

wc WNDCLASS 0,WindowProc,0,0,0,0,0,COLOR_BTNFACE+1,0,class
ofn OPENFILENAME sizeof.OPENFILENAME,0,0,filter,0,0,0,fname,MAXSIZE
msg MSG
client RECT
menuinfo MENUITEMINFO sizeof.MENUITEMINFO,MIIM_STATE

section '.code' code readable executable

start:
invoke GetModuleHandle,0
mov [wc.hInstance],eax
mov [ofn.hInstance],eax
invoke LoadIcon,[wc.hInstance],IDI_MAIN
mov [wc.hIcon],eax
invoke LoadCursor,0,IDC_ARROW
mov [wc.hCursor],eax
invoke RegisterClass,wc
cmp eax,0
je error
invoke LoadAccelerators,[wc.hInstance],IDA_MAIN
mov [hacc],eax
invoke LoadMenu,[wc.hInstance],IDM_MAIN
mov [hmenu],eax
invoke CreateWindowEx,0,class,title,WS_VISIBLE+WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,\
0,eax,[wc.hInstance],0
cmp eax,0
je error
mov [hwnd],eax
msg_loop:
invoke GetMessage,msg,0,0,0
cmp eax,0
je end_loop
invoke TranslateAccelerator,[hwnd],[hacc],msg
cmp eax,0
jne msg_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop

error:
invoke GetLastError
invoke wsprintf,errbuf,errtxt,eax
invoke MessageBox,0,errbuf,0,MB_OK

end_loop:
invoke ExitProcess,[msg.wParam]

proc WindowProc hwnd,wmsg,wparam,lparam
push ebx esi edi
cmp [wmsg],WM_COMMAND
je .wmcommand
cmp [wmsg],WM_CREATE
je .wmcreate
cmp [wmsg],WM_SIZE
je .wmsize
cmp [wmsg],WM_SETFOCUS
je .wmsetfocus
cmp [wmsg],WM_CLOSE
je .EXIT
cmp [wmsg],WM_DESTROY
je .wmdestroy
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
jmp .finish
.wmcommand:
mov eax,[wparam]
cmp ax,IDM_NEW
je .NEW
cmp ax,IDM_OPEN
je .OPEN
cmp ax,IDM_SAVE
je .SAVE
cmp ax,IDM_SAVEAS
je .SAVEAS
cmp ax,IDM_EXIT
je .EXIT
cmp ax,IDM_UNDO
je .UNDO
cmp ax,IDM_CUT
je .CUT
cmp ax,IDM_COPY
je .COPY
cmp ax,IDM_PASTE
je .PASTE
cmp ax,IDM_DELETE
je .DELETE
cmp ax,IDM_SELECTALL
je .SELECTALL
cmp ax,IDM_ABOUT
je .ABOUT
jmp .finish
; обработчики сообщений меню файл:
.NEW:
call get_modified
invoke SendMessage,[hedit],WM_SETTEXT,0,0
mov [fnsaved],0
jmp .finish
.OPEN:
call get_modified
call open_file
jmp .finish
.SAVE:
call save_file
jmp .finish
.SAVEAS:
call get_save
jmp .finish
.EXIT:
call get_modified
invoke DestroyWindow,[hwnd]
jmp .finish
; обработчики сообщений меню правка:
.UNDO:
mov eax,EM_UNDO
jmp .send2editbox
.CUT:
mov eax,WM_CUT
jmp .send2editbox
.COPY:
mov eax,WM_COPY
jmp .send2editbox
.PASTE:
mov eax,WM_PASTE
jmp .send2editbox
.DELETE:
mov eax,WM_CLEAR
.send2editbox:
invoke SendMessage,[hedit],eax,0,0
jmp .finish
.SELECTALL:
invoke SendMessage,[hedit],EM_SETSEL,0,-1
jmp .finish
.ABOUT:
invoke DialogBoxParam,[wc.hInstance],IDD_ABOUT,[hwnd],AboutDialog,0
jmp .finish

.wmcreate:
invoke GetClientRect,[hwnd],client
invoke
CreateWindowEx,WS_EX_CLIENTEDGE,edit,0,WS_VISIBLE+WS_CHILD+WS_HSCROLL+ WS_VSCROLL+ES_AUTOHSCROLL+ES_AUTOVSCROLL+ES_MULTILINE,[client.left], [client.top], [client.right],[client.bottom],[hwnd],0,[wc.hInstance],NULL
cmp eax,0
je .failed
mov [hedit],eax
invoke SendMessage,[hedit],EM_LIMITTEXT,MEMSIZE-1,0
invoke
CreateFont,16,0,0,0,0,FALSE,FALSE,FALSE,RUSSIAN_CHARSET,OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,FIXED_PITCH+FF_DONTCARE, NULL cmp eax,0
je .failed
mov [font],eax
invoke SendMessage,[hedit],WM_SETFONT,eax,FALSE
mov eax,0
jmp .finish
.failed:
mov eax,-1
jmp .finish
.wmsize:
invoke GetClientRect,[hwnd],client
invoke MoveWindow,[hedit],[client.left],[client.top],[client.right],[client.bottom],TRUE
mov eax,0
jmp .finish
.wmsetfocus:
invoke SetFocus,[hedit]
mov eax,0
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
mov eax,0
.finish:
pop edi esi ebx
ret

get_modified:
invoke SendMessage,[hedit],EM_GETMODIFY,0,0
cmp eax,0
je .not_modified
invoke MessageBox,[hwnd],saveq,title,MB_YESNO + MB_ICONWARNING
cmp eax,IDYES
jne .not_modified
call save_file
.not_modified:
retn

save_file:
cmp [fnsaved],1
je create_file
get_save:
mov [ofn.Flags],OFN_EXPLORER + OFN_OVERWRITEPROMPT
invoke GetSaveFileName,ofn
cmp eax,0
je failed
create_file:
invoke CreateFile,fname,GENERIC_READ + GENERIC_WRITE,FILE_SHARE_READ + FILE_SHARE_WRITE,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
mov [hfile],eax
invoke GetProcessHeap
mov [hheap],eax
invoke HeapAlloc,[hheap],HEAP_ZERO_MEMORY,MEMSIZE
mov [pmem],eax
invoke SendMessage,[hedit],WM_GETTEXT,MEMSIZE,[pmem]
invoke lstrlen,[pmem]
invoke WriteFile,[hfile],[pmem],eax,sbuf,0
mov [fnsaved],1
invoke SendMessage,[hedit],EM_SETMODIFY,0,0
invoke HeapFree,[hheap],0,[pmem]
invoke CloseHandle,[hfile]
retn

open_file:
mov [ofn.Flags], OFN_FILEMUSTEXIST + OFN_PATHMUSTEXIST + OFN_EXPLORER
invoke GetOpenFileName,ofn
cmp eax,0
je failed
invoke CreateFile,fname,GENERIC_READ + GENERIC_WRITE,FILE_SHARE_READ + FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
mov [hfile],eax
invoke GetProcessHeap
mov [hheap],eax
invoke HeapAlloc,[hheap],HEAP_ZERO_MEMORY,MEMSIZE
mov [pmem],eax
invoke ReadFile,[hfile],[pmem],MEMSIZE-1,sbuf,0
invoke SendMessage,[hedit],WM_SETTEXT,0,[pmem]
mov [fnsaved],1
invoke HeapFree,[hheap],0,[pmem]
invoke CloseHandle,[hfile]
retn

failed:
retn
endp

proc AboutDialog hwnd,msg,wparam,lparam
push ebx esi edi
cmp [msg],WM_COMMAND
je .close
cmp [msg],WM_CLOSE
je .close
mov eax,0
jmp .finish
.close:
invoke EndDialog,[hwnd],0
.processed:
mov eax,1
.finish:
pop edi esi ebx
ret
endp

section '.idata' import data readable writeable

library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
gdi32,'GDI32.DLL',\
comdlg32,'COMDLG32.DLL'

include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\gdi32.inc'
include 'api\comdlg32.inc'

section '.rsrc' resource data readable

IDM_MAIN = 101
IDA_MAIN = 201
IDD_ABOUT = 301
IDI_MAIN = 401

IDM_NEW = 1101
IDM_OPEN = 1102
IDM_SAVE = 1103
IDM_SAVEAS = 1104
IDM_EXIT = 1109

IDM_UNDO = 1201
IDM_CUT = 1202
IDM_COPY = 1203
IDM_PASTE = 1204
IDM_DELETE = 1205
IDM_SELECTALL = 1206

IDM_ABOUT = 1401

directory RT_MENU,menus,\
RT_ACCELERATOR,accelerators,\
RT_DIALOG,dialogs,\
RT_GROUP_ICON,group_icons,\
RT_ICON,icons,\
RT_VERSION,versions

resource menus,\
IDM_MAIN,LANG_RUSSIAN+SUBLANG_DEFAULT,main_menu

resource accelerators,\
IDA_MAIN,LANG_ENGLISH+SUBLANG_DEFAULT,main_keys

resource dialogs,\
IDD_ABOUT,LANG_RUSSIAN+SUBLANG_DEFAULT,about_dialog

resource group_icons,\
IDI_MAIN,LANG_NEUTRAL,main_icon

resource icons,\
1,LANG_NEUTRAL,main_icon_data

resource versions,\
1,LANG_NEUTRAL,version

menu main_menu
menuitem '&Файл',0,MFR_POPUP
menuitem <'Созд&ать',9,'Ctrl+N'>,IDM_NEW,0
menuitem <'&Открыть…',9,'Ctrl+O'>,IDM_OPEN,0
menuitem <'&Сохранить',9,'Ctrl+S'>,IDM_SAVE,0
menuitem 'Сохранить &как…',IDM_SAVEAS,0
menuseparator
menuitem <'В&ыход',9,'Ctrl+Q'>,IDM_EXIT,MFR_END

menuitem '&Правка',0,MFR_POPUP
menuitem <'&Отменить',9,'Ctrl+Z'>,IDM_UNDO
menuseparator
menuitem <'&Вырезать',9,'Ctrl+X'>,IDM_CUT
menuitem <'&Копировать',9,'Ctrl+C'>,IDM_COPY
menuitem <'Вст&авить',9,'Ctrl+V'>,IDM_PASTE
menuitem <'&Удалить',9,'Del'>,IDM_DELETE
menuseparator
menuitem <'Выделить в&се',9,'Ctrl+A'>,IDM_SELECTALL,MFR_END

menuitem '&Вид',0

menuitem '&Справка',0,MFR_POPUP+MFR_END
menuitem '&О программе',IDM_ABOUT,MFR_END

accelerator main_keys,\
FVIRTKEY+FNOINVERT+FCONTROL,'N',IDM_NEW,\
FVIRTKEY+FNOINVERT+FCONTROL,'O',IDM_OPEN,\
FVIRTKEY+FNOINVERT+FCONTROL,'S',IDM_SAVE,\
FVIRTKEY+FNOINVERT+FCONTROL,'Q',IDM_EXIT,\
FVIRTKEY+FNOINVERT+FCONTROL,'Z',IDM_UNDO,\
FVIRTKEY+FNOINVERT+FCONTROL,'X',IDM_CUT,\
FVIRTKEY+FNOINVERT+FCONTROL,'C',IDM_COPY,\
FVIRTKEY+FNOINVERT+FCONTROL,'V',IDM_PASTE,\
FVIRTKEY+FNOINVERT+FCONTROL,'A',IDM_SELECTALL

dialog about_dialog,'О программе',40,40,172,60,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
dialogitem 'STATIC',<'Мой Первый Текстовый Редактор',0Dh,0Ah,'Copyright ',0A9h,' BarMentaLisk 2008.'>,-
1,27,10,144,40,WS_VISIBLE+SS_CENTER
dialogitem 'STATIC',IDI_MAIN,-1,8,8,32,32,WS_VISIBLE+SS_ICON
dialogitem 'STATIC','',-1,4,34,164,11,WS_VISIBLE+SS_ETCHEDHORZ
dialogitem 'STATIC','Написан при помощи FASM',-1,12,42,100,20,WS_VISIBLE+SS_LEFT
dialogitem 'BUTTON','OK',IDOK,124,40,42,14,WS_VISIBLE+WS_TABSTOP+ BS_DEFPUSHBUTTON
enddialog

icon main_icon,main_icon_data,'1.ico'

versioninfo version,VOS_NT_WINDOWS32,VFT_APP,VFT2_UNKNOWN, LANG_RUSSIAN+SUBLANG_DEFAULT,0,\
'Comments','Написан при помощи FASM',\
'CompanyName','BarMentaLisk',\
'FileDescription','Текстовый редактор',\
'ProductName',<'Мой Первый',0Dh,0Ah,'Текстовый Редактор'>,\
'LegalCopyright',<'Copyright ',0A9h, 'BarMentaLisk 2008'>,\
'FileVersion','0.2.0.0',\
'OriginalFilename','editor1.EXE'

В самом начале объявляются две константы: MAXSIZE и MEMSIZE. Первая — это максимальное количество байт под полный путь и имя файла, вторая — размер памяти, выделяемой под буфер для текста, включая завершающий строку ноль. В данной ситуации максимальное количество символов в тексте у нас не должно превышать 65 536 (64 Кб). Памяти выделяется на один байт больше, но в окне редактирования и в файле на жестком диске завершающий ноль записан не будет. При необходимости вы всегда можете изменить данную константу по своему усмотрению в пределах от 2 до 2 147 483 647 (7FFFFFFFh). Потому что максимальное количество символов, которое может вместить стандартный элемент EDIT = 7FFFFFFEh, а минимальное = 1. В секции данных мало нового. Фильтр допустимых файлов (filter) состоит из одной или нескольких пар строк, завершающихся нулем. Каждая пара — это отображаемое имя фильтра и собственно сам фильтр. Последняя пара должна завершаться двумя нулями. Переменную fnsaved мы будем использовать для того, чтобы определять, сохранено ли текущее имя файла в переменной fname. Еще у нас появилась структура OPENFILENAME. Список ее элементов вы можете увидеть в \INCLUDE\EQUATES\ COMDLG32.INC. Чтобы не париться с нудным описанием каждого элемента, опишу только те, которые могут нам понадобиться в ближайшем будущем, тем более что последние 11 элементов я даже не инициализировал какими-либо значениями (последние элементы структуры обычно автоматически инициализируются нулями, если опущены). Хотя по правилам хорошего тона необходимо было бы дописать после MAXSIZE еще 11 ноликов через запятую, но это уж кому как нравится.

lStructSize — размер структуры в байтах; hwndOwner — дескриптор окна-владельца диалогового окна открытия файла; hInstance — дескриптор исполняемого модуля; lpstrFilter — указатель на фильтр; lpstrCustomFilter — указатель на пользовательский фильтр; nMaxCustFilter — размер пользовательского фильтра; nFilterIndex — номер фильтра, выбираемого по умолчанию; lpstrFile — указатель на полное имя файла; nMaxFile — размер буфера для полного имени файла; lpstrFileTitle — короткое имя файла (только имя, без пути к файлу); nMaxFileTitle — размер буфера для короткого имени; lpstrInitialDir — открываемая по умолчанию директория; lpstrTitle — заголовок диалогового окна открытия/сохранения файла.

Переходим к секции кода. Начало похоже на предыдущую версию, только надо не забыть скопировать дескриптор исполняемого модуля еще и в ofn.hInstance. Затем добавляем обработку сообщения WM_CLOSE, чтобы по команде закрытия можно было проверить, изменилось ли содержимое окна, и предложить сохранение, если изменилось.

Обработчики сообщений от элементов меню "Файл" теперь доступны (параметры MFS_GRAYED сняты с этих элементов в ресурсе menu) и готовы обрабатывать свои сообщения. При выборе пункта "Создать" (IDM_NEW -> .NEW), происходит вызов подпрограммы (метка get_modified). Команда call (от англ. call — вызывать) очень похожа на команду jmp. Только команда call сохраняет при этом в стек адрес возврата — адрес следующей за call команды, чтобы потом команда ret (от англ. return — возвращаться) могла вернуть управление на команду, следующую за call. Пара команд call — ret используется для возможности вызова какой-либо подпрограммы из разных мест программы. Например, макрос invoke, к которому мы уже так привыкли, тоже использует команду call, только предварительно запихивает в стек параметры вызываемой функции. На "том конце провода" функция API выполняет необходимые операции и при помощи команды ret возвращает управление следующей команде нашей программы. Сложно? Ерунда! Это я вам еще только в общих чертах обрисовал картину. Ну да ладно, не стоит на этом загоняться — это уже почти хакерский уровень, а вы пока что как бы "чайники". Запомните пока просто, что call вызывает, а ret возвращает.

Вот и в нашей ситуации выполнение команд временно перескакивает на метку get_modified. Там посылкой сообщения EM_GETMODIFY нашему окну редактирования мы узнаем, был ли изменен текст окна: если изменен, вернется единица, иначе — ноль. Параметры у сообщения отсутствуют, а потому выставляются в ноли. Если изменений нет, то значит и сохранять нечего — возвращаемся. Retn (Return Near) — это тот же ret, только для близких возвратов — мы же находимся внутри процедуры WindowProc. Если же изменения имели место, то переспрашиваем у пользователя, следует ли их сохранять. Если следует (IDYES), то вызываем save_file, нет — возвращаемся. На метке save_file проверяется, сохранен ли путь к нашему файлу (мы сами будем устанавливать переменную fnsaved в единицу, когда путь сохранен или файл открыт, и в ноль при создании нового файла, путь которого еще не определен). Если путь сохранен, сразу переходим к метке create_file, если нет — вызываем диалог сохранения файла функцией
GetSaveFileName. Эта функция имеет лишь один параметр (но зато какой!) — указатель на огромную структуру OPENFILENAME. Предварительно устанавливаем флаги OFN_EXPLORER для открытия диалога в стиле проводника windows и OFN_OVERWRITEPROMPT для подтверждения перезаписи файла, если он существует. GetSaveFileName возвращает ноль в случае, если пользователь отменил сохранение. Иначе в буферы помещается имя файла, а возвращаемое значение отлично от ноля. Таким образом, на метке create_file, в fname содержится полное имя файла.

Функция CreateFile создает или открывает объект ввода/вывода. Параметры:
1. Имя объекта.
2. Права доступа: GENERIC_READ — чтение, GENERIC_WRITE — запись или оба сразу.
3. Права на совместный одновременный доступ к файлу несколькими процессами: 0 — доступ другим процессам запрещен до закрытия файла, FILE_SHARE_READ — разрешено чтение, FILE_SHARE_WRITE — разрешена запись, FILE_SHARE_DELETE — разрешено удаление.
4. Атрибуты безопасности: 0 для значений по умолчанию.
5. Способ открытия файла: CREATE_NEW — создать новый файл, ошибка, если файл существует, CREATE_ALWAYS — создать новый файл, перезаписывает старый, если файл существует, OPEN_EXISTING — открыть файл, ошибка, если файл не существует, OPEN_ALWAYS — открыть файл, если файл не существует, он будет создан, TRUNCATE_EXISTING — открыть файл и очистить его содержимое, ошибка, если файл не существует.
6. Набор атрибутов (скрытый, системный и т.д.).
7. Файл-шаблон атрибутов.

При успешном создании/открытии файла возвращается его дескриптор, если файл отсутствует — возвращается 0, в случае ошибки возвращается -1. Функция GetProcessHeap возвращает дескриптор кучи вызывающего функцию процесса. Куча — это область виртуальной памяти, под которую не выделяется реальная физическая память, но по мере заполнения кучи данными диспетчер, управляющий кучами (heap manager), выделяет под нее физическую память. Функция HeapAlloc выделяет блок памяти в куче и возвращает указатель на выделенный блок. Параметры: дескриптор кучи; флаги способа выделения памяти (HEAP_ZERO_MEMORY — проинициализировать блок нулевыми значениями); размер выделяемого блока в байтах. После выделения блока мы копируем текст, завершающийся нолем, из окна редактирования в этот блок, отсылкой окну редактирования сообщения WM_GETTEXT. Функция lstrlen (параметр — адрес строки) возвращает длину строки в символах без учета завершающего ноля. Функция WriteFile записывает данные в файл. Ее параметры: дескриптор файла; указатель на данные; размер данных в байтах; буфер для ответа, сколько байт удалось записать; указатель на структуру OVERLAPPED для дополнительных сведений. В случае ошибки возвращается ноль. Записываем в fnsaved единицу — файл сохранен и его имя хранится в переменной fname. Отсылаем окну редактирования сообщение EM_SETMODIFY — первый параметр 0 означает, что содержимое окна не изменялось, второй параметр не используется. Функция HeapFree освобождает указанный блок памяти в куче. Параметры: дескриптор кучи; флаги; указатель на блок памяти. Функция CloseHandle закрывает объект (в нашем случае — открытый файл), чей дескриптор указан в единственном параметре. Теперь возвращаемся к следующей после call команде.

При создании файла после вызова подпрограммы get_modified мы очищаем содержимое окна редактирования и устанавливаем fnsaved в ноль, так как имя нового файла пока неизвестно. При открытии файла мы опять же вызываем подпрограмму сохранения старого файла (get_modified), а затем вызываем подпрограмму открытия (open_file). Там все почти аналогично подпрограмме get_modified. Устанавливаем флаги OFN_FILEMUSTEXIST (открываемый файл должен существовать), OFN_PATHMUSTEXIST (открываемый путь к файлу должен существовать) и OFN_EXPLORER. Функция GetOpenFileName вызывает диалог открытия файла и возвращает его имя в буфер fname. Открываем выбранный файл функцией CreateFile. Выделяем память. Функция ReadFile аналогична функции WriteFile, только не пишет данные в файл, а читает их. Размер данных для чтения указываем MEMSIZE-1, потому что последний байт в памяти необходимо оставить нулевым — это завершающий строку ноль. Устанавливаем прочитанные данные текстом окна редактирования (WM_SETTEXT). Задвигаем в fnsaved единицу — файл открыт и его имя нам известно. Освобождаем память. Закрываем файл.

При выборе пункта "Сохранить" подпрограмма сохранения вызывается с метки save_file, дабы избежать вопроса о необходимости сохранения. При выборе пункта "Сохранить как…" вызываем сохранение с метки выбора файла для сохранения.

Выбор пункта "Выход" или закрытие программы приведет к выполнению get_modified и вызову функции DestroyWindow с дескриптором окна в качестве единственного параметра.
На метке failed ничего не происходит — возврат без выполнения каких-либо действий. Здесь вы самостоятельно можете добавить дополнительную обработку ошибок и вывод соответствующего сообщения. Также можете на свое усмотрение внести проверку успешного завершения после каждой важной функции подпрограмм чтения и записи файла.
Согласен, что некоторые моменты разъяснены не так подробно, как хотелось бы, но тут я уповаю на вашу сообразительность. Если что-то непонятно — возможно стоит еще раз прочесть предыдущие уроки. В любом случае не падайте духом и не теряйте уверенности в своих силах и возможностях.
Все приводимые примеры были протестированы на правильность работы под Windows XP и скорее всего будут работать и под другими версиями Windows, однако я не могу дать никаких гарантий их правильной работы на вашем компьютере.

Исходные тексты программ вы можете найти на форуме: сайт

BarMentaLisk, q@sa-sec.org

© Компьютерная газета

полезные ссылки
Корпусные камеры видеонаблюдения
IP камеры видеонаблюдения