Assembler - язык неограниченных возможностей

         

Полноценное приложение


Теперь, когда мы знаем, как строятся программы с меню и диалогами, напишем одно настоящее полноценное приложение, включающее в себя все то, что требуется от программы, — меню, диалоги, комбинации клавиш для быстрого доступа к элементам меню и т.д. В качестве примера создадим простой текстовый редактор, аналогичный Notepad. В этом примере мы увидим, как получить параметры из командной строки, прочитать и записать файл, выделить и освободить память.

// winpad95.rc // Файл ресурсов для программы winpad95.asm // идентификаторы сообщений от пунктов меню #define IDM_NEW 0x1001 #define IDM_OPEN 0x101L #define IDM_SAVE 0x102L #define IDM_SAVEAS 0x103L #define IDM_EXIT 0x104L #define IDM_ABOUT 0x105L #define IDM_UNDO 0x106L #define IDM_CUT 0x107L #define IDM_COPY 0x108L #define IDM_PASTE 0x109L #define IDM_CLEAR 0x10AL #define IDM_SETSEL 0x10BL

// идентификаторы основных ресурсов #define IDJ1ENU 0x700L #define ID_ACCEL 0x701L #define ID_ABOUT 0x702L

// если есть иконка - можно раскомментировать эти две строки // #define ID_ICON 0x703L // ID_ICON ICON "winpad95.ico"

// основное меню ID_MENU MENU DISCARDABLE { POPUP "&File" { MENUITEM "&New\tCtrl+N", IDM_NEW MENUITEM "&Open...\tCtrl+O", IDM_OPEN MENUITEM "&Save\tCtrl+S", IDM_SAVE MENUITEM "Save &As...\tCtrl+Shift+S", IDM_SAVEAS MENUITEM SEPARATOR MENUITEM "E&xit\tCtrl+Q", IDM_EXIT } POPUP "&Edit" { MENUITEM "&Undo\tCtrl-Z", IDM_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t\tCtrl-X", IDM_CUT MENUITEM "&Copy\tCtrl-C", IDM_COPY MENUITEM "&Paste\tCtrl-V", IDM_PASTE MENUITEM "&Delete\tDel", IDM_CLEAR MENUITEM SEPARATOR MENUITEM "Select &All\tCtrl-A", IDM_SETSEL } POPUP "&Help" { MENUITEM "About", IDM_ABOUT } }

// комбинации клавиш ID_ACCEL ACCELERATORS DISCARDABLE { "N", IDM_NEW, CONTROL, VIRTKEY "O", IDM_OPEN, CONTROL, VIRTKEY "S", IDM_SAVE, CONTROL, VIRTKEY "S", IDM_SAVEAS, CONTROL, SHIFT, VIRTKEY "Q", IDM_EXIT, CONTROL, VIRTKEY "Z", IDM_UNDO, CONTROL, VIRTKEY "A", IDM_SETSEL, CONTROL, VIRTKEY }


// все эти определения можно заменить на #include <winuser.h> #define DS_MODALFRAME 0x80L #define DS_3DLOOK 4 #define WS_POPUP 0x80000000L #define WS_CAPTION 0xC00000L #define WS_SYSMENU 0x80000L #define IDOK 1 #define IDC_STATIC -1 #define IDI_APPLICATION 32512 #define WS_BORDER 0x800000L

// стандартный диалог "About" ID_ABOUT DIALOG DISCARDABLE 0, 0, 125, 75 STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About Asmpad95" { ICON IDI_APPLICATION,IDC_STATIC,12,15,21,20 CTEXT "Asmpad95",IDC_STATIC,0,30,40,8 CTEXT "Prototype notepad-style editor for Windows 95 written entirely in assembly language",IDC_STATIC,45,10,70,45,WS_BORDER DEFPUSHBUTTON "OK",IDOK,35,60,40,10 }

Теперь рассмотрим текст программы:

; winpad95.asm ; графическое win32-приложение - текстовый редактор ; include def32.inc include user32.inc include kernel32.inc include comdlg32.inc

ID_MENU equ 700h ID_ACCEL equ 701h ID_ABOUT equ 702h

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

EditID equ 1

.386 .model flat .const c_w_name db "Asmpad95",0 ; это и имя класса, и имя основного окна edit_class db "edit",0 ; предопределенное имя класса для редактора changes_msg db "Save changes?",0 filter_string db "All Files",0,'*.*',0 ; маски для Get * FileName db "Text Files",0, '*.txt',0,0

.data ; структура, использующаяся Get * FileName ofn OPENFILENAME <SIZE ofn,?,?,offset filter_string,?,?,?,offset buffer,\ MAXSIZE,0,?,?,?,?,?,?,0,?,?,?> ; структура, описывающая наш основной класс wc WNDCLASSEX <SIZE WNDCLASSEX,CS_HREDRAW or CS_VREDRAW,offset win_proc,0,\ 0,?,?,?,COLOR_WINDOW+1,ID_MENU,offset c_w_name,0> flag_untitled db 1 ; = 1, если имя файла не определено (новый файл)

.data? h_editwindow dd ? ; идентификатор окна редактора h_accel dd ? ; идентификатор массива акселераторов p_memory dd ? ; адрес буфера в памяти SizeReadWrite dd ? msg_ MSG <> rec RECT <> buffer db MAXSIZE dup(?) ; имя файла window_title db MAXSIZE dup(?), 12 dup(?)



.code _start: call GetCommandLine ; получить нашу командную строку mov edi,eax mov al,' ' mov ecx,MAXSIZE repne scasb ; найти конец имени нашей программы cmp byte ptr [edi],0 je cmdline_empty mov esi,edi mov edi,offset buffer rep movsb mov flag_untitled,0 cmdline_empty: ; подготовить и зарегистрировать класс xor ebx,ebx call GetModuleHandle ; определить наш идентификатор mov esi,eax mov wc.hInstance,eax ; и сохранить его в wc.hInstance mov ofn._hInstance,eax push IDI_APPLICATION ; или IDI_ICON, если иконка есть в ресурсах, push ebx ; или esi, если иконка есть в ресурсах call LoadIcon mov wc.hIcon,eax push IDC_ARROW ; предопределенный курсор (стрелка) push ebx call LoadCursor mov wc.hCursor,eax push offset wc call RegisterClassEx ; создать основное окно push ebx push esi push ebx push ebx push 200 push 300 push CW_USEDEFAULT push CW_USEDEFAULT push WS_OVERLAPPEDWINDOW push offset c_w_name push offset c_w_name push WS_EX_CLIENTEDGE call СreateWindowEx push eax ; для pop esi перед message_loop push eax push SW_SHOWNORMAL push eax call ShowWindow call UpdateWindow ; инициализировать акселераторы push ID_ACCEL push esi call LoadAcceleratоrs mov h_accel,eax ; цикл ожидания сообщения pop esi ; ESI - идентификатор основного окна mov edi,offset msg_ ; EDI - структура с сообщением от него message_loop: push ebx push ebx push ebx push edi call GetMessage ; получить сообщение, test eax,eax ; если это WM_OUIT, - jz exit_msg_loop ; выйти из цикла push edi push h_accel push esi ; hWnd call TranslateAccelerator ; преобразовать акселераторы в IDM* test eax,eax jnz message_loop push edi call TranslateMessage ; преобразовать сообщения от клавиш push edi call DispatchMessage ; и отослать обратно jmp short message_loop exit_msg_loop: push msg_.wParam call ExitProcess ; конец программы

; процедура win_proc ; ; процедура не должна изменять регистры EBP,EDI,ESI и ЕВХ! win_proc proc near ; параметры (с учетом push ebp) wp_hWnd equ dword ptr [ebp+08h] wp_uMsg equ dword ptr [ebp+0Ch] wp_wParam equ dword ptr [ebp+10h] wp_lParam equ dword ptr [ebp+14h] ; инициализируем стековый кадр push ebp mov ebp,esp ; создать стековый кадр pusha ; сохранить все регистры xor ebx,ebx ; 0 для команд push 0 mov esi,wp_hWnd ; для команд push hWnd mov eax,wp_uMsg ; обработать пришедшее сообщение cmp eax,WM_CREATE je h_wm_create cmp eax,WM_SIZE je h_wm_size cmp eax,WM_DESTROY je h_wm_destroy cmp eax,WM_COMMAND je h_wm_command cmp eax,WM_ACTIVATE je h_wm_activate cmp eax,WM_CLOSE je h_wm_close def_proc: popa leave ; если это ненужное сообщение, jmp DefWindowProc ; оставить его обработчику по умолчанию



; обработчик WM_CLOSE, ; если нужно, спрашивает, сохранить ли файл h_wm_close: call save_contents jmp short def_proc

; обработчик WM_CREATE ; h_wm_create: ; здесь также можно создать toolbar и statusbar ; создать окно редактора push ebx push wc.hInstance ; идентификатор основной программы push EditID push esi ; hWnd push ebx ; 0 push ebx ; 0 push ebx ; 0 push ebx ; 0 push WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or \ ES_AUTOHSCROLL or ES_AUTOVSCROLL push ebx ; 0 push offset edit_class push ebx ; 0 call CreateWindowEx mov h_editwindow,eax ; передать ему фокус push eax call SetFocus cmp flag_untitled,1 je continue_create call skip_getopen ; открыть файл, указанный в командной строке continue_create: call set_title jmp end_wm_check

; обработчик WM_COMMAND ; h_wm_command: mov eax,wp_wParam cwde ; младшее слово содержит IDM_* sub eax,100h jb def_proc ; обработать сообщения от пунктов меню call dword ptr menu_handlers[eax*4] jmp end_wm_check

menu_handlers dd offset h_idm_new,offset h_idm_open,offset h_idm_save dd offset h_idm_saveas,offset h_idm_exit,offset h_idm_about dd offset h_idm_undo, offset h_idm_cut, offset h_idm_copy dd offset h_idm_paste, offset h_idm_clear, offset h_idm_setsel ; сообщения от пунктов меню должны быть описаны в win95pad.rc именно в таком ; порядке - от IDM_NEW 100h до IDM_CLEAR 10Ah

h_idm_setsel: push -1 ; -1 push ebx ; 0 push EM_SETSEL ; выделить весь текст push h_editwindow call SendMessage ret

; обработчики сообщений из меню EDIT: h_idm_clear: mov eax,WM_CLEAR jmp short send_to_editor h_idm_paste: mov eax,WM_PASTE jmp short send_to_editor h_idm_copy: mov eax,WM_COPY jmp short send_to_editor h_idm_cut: mov eax,WM_CUT jmp short send_to_editor h_idm_undo: mov eax,EM_UNDO send_to_editor: push ebx ; 0 push ebx ; 0 push eax push h_editwindow call SendMessage ret

; обработчик IDM_NEW h_idm_new: call save_contents ; записать файл, если нужно mov byte ptr flag_untitled,1 call set_title ; отметить, что файл не назван push ebx push ebx push WM_SETTEXT push h_editwindow call SendMessage ; послать пустой WM_SETTEXT редактору ret



; обработчик IDM_ABOUT h_idm_about: push ebx ; 0 push offset about_proc push esi ; hWnd push ID_ABOUT push wc.hInstance call DialogBoxParam ret

; обработчик IDM_SAVEAS и IDM_SAVE h_idm_save: cmp flag_untitled, 1 ; если файл назван, jne skip_getsave ; пропустить вызов GetSaveFileName h_idm_saveas: ; спросить имя файла mov ofn.Flags,OFN_EXPLORER or OFN_OVERWRITEPROMPT push offset ofn call GetSaveFileName test eax,eax jz file_save_failed skip_getsave: ; создать его push ebx push FILE_ATTRIBUTE_ARCHIVE push CREATE_ALWAYS push ebx push FILE_SHARE_READ or FILE_SHARE_WRITE push GENERIC_READ or GENERIC_WRITE push offset buffer call CreateFile mov edi,eax ; выделить память push MEMSIZE push GMEM_MOVEABLE or GMEM_ZEROINIT call GlobalAlloc push eax ; hMemory для GlobalFree push eax ; hMemory для GlobalLock call GlobalLock mov esi,eax ; адрес буфера в ESI ; забрать текст из редактора push esi push MEMSIZE-1 push WM_GETTEXT push h_editwindow call SendMessage ; записать в файл push esi ; pMemory call lstrlen push ebx push offset SizeReadWrite push eax ; размер буфера push esi ; адрес буфера push edi ; идентификатор файла call WriteFile push esi ; pMemory call GlobalUnlock call GlobalFree ; hMemory уже в стеке push edi ; идентификатор файла call CloseHandle ; сбросить флаг модификации в редакторе push ebx push ebx push EM_SETMODIFY push h_editwindow call SendMessage mov byte ptr flag_untitled,0 call set_title file_save_failed: push h_editwindow call SetFocus ret

; обработчик IDM_OPEN h_idm_open: call save_contents ; вызвать стандартный диалог выбора имени файла mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or \ OFN_EXPLORER push offset ofn call GetOpenFileName test eax,eax jz file_open_failed skip_getopen: ; открыть выбранный файл push ebx push FILE_ATTRIBUTE_ARCHIVE push OPEN_EXISTING push ebx push FILE_SHARE_READ or FILE_SHARE_WRITE push GENERIC_READ or GENERIC_WRITE push offset buffer call CreateFile mov edi,eax ; идентификатор для ReadFile ; выделить память push MEMSIZE push GMEM_MOVEABLE or GMEM_ZEROINIT call GlobalAlloc push eax ; hMemory для GlobalFree push eax ; hMemory для GlobalLock call GlobalLock ; получить адрес выделенной памяти push eax ; pMemory для GlobalUnlock push eax ; pMemory для SendMessage ; прочитать файл push ebx push offset SizeReadWrite push MEMSIZE-1 push eax ; pMemory для ReadFile push edi call ReadFile ; послать окну редактора сообщение wm_settext, чтобы он забрал текст из буфера push ebx ; pMemory уже в стеке push WM_SETTEXT push h_editwindow call SendMessage ; а теперь можно закрыть файл и освободить память call GlobalUnlock ; pMemory уже в стеке call GlobalFree ; hMemory уже в стеке push edi ; hFile call CloseHandle mov byte ptr flag_untitled,0 call set_title file_open_failed: push h_editwindow call SetFocus ret



; обработчик IDM_EXIT h_idm_exit: call save_contents push esi ; hWnd call DestroyWindow ; уничтожить наше окно ret

; обработчик WM_SIZE h_wm_size: ; здесь также надо послать WM_SIZE окнам toolbar и statusbar, ; изменить размер окна редактора так, чтобы оно по-прежнему было на все окно push offset rec push esi ; hWnd call GetClientRect push 1 ; true push rec. bottom ; height push rec. right ; width push ebx ; у push ebx ; x push h_editwindow call MoveWindow jmp short end_wm_check

; обработчик WM_ACTIVATE h_wm_activate: push h_editwindow call SetFocus jmp short end_wm_check

; обработчик WM_DESTROY h_wm_destroy: push ebx call PostQuitMessage ; послать WM_QUIT основной программе

; конец процедуры window_proc end_wm_check: рора xor еах,еах ; вернуть 0 leave ret 16

; процедура set_title ; устанавливает новый заголовок для основного окна set_title: push esi push edi mov edi, offset window_title cmp byte ptr flag_untitled,1 ; если у файла нет имени, je untitled ; использовать Untitled mov esi,ofn.lpstrFile ; [ESI] - имя файла с путем movzx eax,ofn.nFileOffset ; eax - начало имени файла add esi,eax copy_filename: lodsb ; скопировать файл побайтно в название окна, test al,al jz add_progname ; пока не встретится ноль stosb jmp short copy_filename add_progname: mov dword ptr [edi],'-' ; приписать минус add edi,3 mov esi,offset c_w_name mov ecx,9 ; и название программы rep movsb pop edi pop esi push offset window_title push esi ; идентификатор окна call SetWindowText ret untitled: mov dword ptr [edil'itnU' ; дописать "Unti" mov dword ptr [edi+4],'delt' ; дописать "tled" add edi,8 jmp short add_progname

; процедура save_contents ; EBX = 0, ESI = hWnd save_contents: ; спросить редактор, изменялся ли текст push ebx push ebx push EM_GETMODIFY push h_editwindow call SendMessage test eax,eax jz not_modified ; спросить пользователя, сохранять ли его push MB_YESNO + MB_ICONWARNING push offset c_w_name push offset changes_msg push esi call MessageBox cmp eax,IDYES jne not_modified ; сохранить его call h_idm_save not_modified: ret



win_proc endp

about_proc proc near ; параметры ( с учетом push ebp) ap_hDlg equ dword ptr [ebp+08h] ap_uMsg equ dword ptr [ebp+0Ch] ap_wParam equ dword ptr [ebp+10h] ap_lParam equ dword ptr [ebp+14h] push ebp mov ebp,esp ; создать стековый кадр cmp ap_uMsg,WM_COMMAND jne dont_proceed cmp ap_wParam,IDOK jne dont_proceed push 1 push ap_hDlg call EndDialog dont_proceed: xor eax,eax ; не обрабатывается leave ret 16 about_proc endp

end _start

Размер этой программы — 6,5 Кб (скомпилированной ml/link), и даже версия, в которую добавлено все, что есть в Notepad (вывод файла на печать и поиск по тексту), все равно более чем в три раза меньше notepad.exe. Чем большее Windows-приложение создается, тем сильнее сказывается выигрыш в размерах при использовании ассемблера, даже несмотря на то, что мы только вызываем системные функции, практически не занимаясь программированием.

Прежде чем можно будет скомпилировать winpad95.asm, конечно, надо внести необходимые дополнения в наши включаемые файлы.

Добавления в файл def32.inc:

; из winuser.h WM_CREATE equ 1 WM_ACTIVATE equ 6 WM_SETTEXT equ 0Ch WM_GETTEXT equ 0Dh WM_CUT equ 300h WM_COPY equ 301h WM_PASTE equ 302h WM_CLEAR equ 303h WM_UNDO equ 304h WM_SIZE equ 5 WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h WS_EX_CLIENTEDGE equ 200h ES_LEFT equ 0 ES_MUITILINE equ 4 ES_AUTOHSCROLL equ 80h ES_AUTOVSCROLL equ 40h EM_GETHANDLE equ 0BDh EM_GETMODIFY equ 0B8h EM_SETMODIFY equ 0B9h EM_UNDO equ 0C7h EM_SETSEL equ 0B1h MB_YESNO equ 4 MB_ICONWARNING equ 30h IDOK equ 1 IDYES equ 6

; из winnt.h GENERIC_READ equ 80000000h GENERIC_WRITE equ 40000000h FILE_SHARE_READ equ 1 FILE_SHARE_WRITE equ 2 FILE_ATTRIBUTE_ARCHIVE equ 20h

; из commdlg.h OFN_PATHMUSTEXIST equ 800h OFN_FILEMUSTEXIST equ 1000h OFN_EXPLORER equ 80000h OFN_OVERWRITEPROMPT equ 2 OPENFILENAME struc IStructSize dd ? hwndOwner dd ? _hInstance dd ? lpstrFilter dd ? lpstrCustomFilter dd ? nMaxCustFilter dd ? nFilterlndex dd ? lpstrFile dd ? nMaxFile dd ? lpstrFileTitle dd ? nMaxFileTitle dd ? lpstrInitialDir dd ? lpstrTitle dd ? Flags dd ? nFileOffset dw ? nFileExtension dw ? lpstrDefExt dd ? lCustData dd ? lpfnHook dd ? lpTemplateName dd ? OPENFILENAME ends



; из windef. h RECT struc left dd ? top dd ? right dd ? bottom dd ? RECT ends

; из winbase.h GMEM_MOVEABLE equ 2 GMEM_ZEROINIT equ 40h OPEN_EXISTING equ 3 CREATE_ALWAYS equ 2

Добавления в файл kernel32.inc между ifdef _TASM_ и else:

extrn lstrlen:near extrn GetCommandLineA:near extrn CloseHandle:near extrn GlobalAlloc:near extrn GlobalLock:near extrn GlobalFree:near extrn CreateFileA:near extrn ReadFile:near extrn WriteFile:near GetCommandLine equ GetCommandLineA CreateFile equ CreateFileA

и между else и endif:

extrn __imp__lstrlen@4:dword extrn __imp__GetCommandLineA@0:dword extrn __imp__CloseHandle@4:dword extrn __imp__GlobalAlloc@8:dword extrn __imp__GlobalLock@4:dword extrn __imp__GlobalFree@4:dword extrn __imp__CreateFileA@28:dword extrn __imp__ReadFile@20:dword extrn __imp__WriteFile@20:dword lstrlen equ __imp__lstrlen@4 GetCommandLine equ __imp__GetCommandLineA@0 CloseHandle equ __imp__CloseHandle@4 GlobalAlloc equ __imp__GlobalAlloc@8 GlobalLock equ __imp__GlobalLock@4 GlobalFree equ __imp__GlobalFree@4 CreateFile equ __imp__CreateFileA@28 ReadFile equ __imp__ReadFile@20 WriteFile equ __imp__WriteFile@20

Добавления в файл user32.inc:

extrn LoadAcceleratorsA:near extrn TranslateAccelerator:near extrn SendMessageA:near extrn SetWindowTextA:near extrn MoveWindow:near extrn GetClientRect:near extrn GlobalUnlock:near LoadAccelerators equ LoadAcceleratorsA SendMessage equ SendMessageA SetWindowText equ SetWindowTextA

и между else и endif:

extrn __imp__LoadAcceleratorsA@8:dword extrn __imp__ТranslateAccelerator@12:dword extrn __imp__SendMessageA@16:dword extrn __imp__SetWindowTextA@8:dword extrn __imp__MoveWindow@24:dword extrn __imp__GetClientRect@8:dword extrn __imp__GlobalUnlock@4:dword LoadAccelerators equ __imp__LoadAcceleratorsA@8 TranslateAccelerator equ __imp__TranslateAccelerator@12 SendMessage equ __imp__SendMessageA@16 SetWindowText equ __imp__SetWindowTextA@8 MoveWindow equ __imp__MoveWindow@24 GetClientRect equ __imp__GetClientRect@8 GlobalUnlock equ __imp__GlobalUnlock@4



Кроме того, нам потребуется новый включаемый файл, comdlg32.inc, описывающий функции, связанные с вызовами стандартных диалогов (выбор имени файла, печать документа, выбор шрифта и т.д.):

; comdlg32.inc ; включаемый файл с функциями из comdlg32.dll ifdef _TASM_ includelib import32.lib extrn GetOpenFileNameA:near extrn GetSaveFileNameA:near GetOpenFileName equ GetOpenFileNameA GetSaveFileName equ GetSaveFileNameA else includelib comdlg32.lib ; истинные имена используемых функций extrn __imp__GetOpenFileNameA@4:dword extrn __imp__GetSaveFileNameA@4:dword ; присваивания для удобства использования GetOpenFileName equ __imp__GetOpenFileNameA@4 GetSaveFileName equ __imp__GetSaveFileNameA@4 endif

Конечно, эту программу можно еще очень долго развивать — добавить toolbar и statusbar, написать документацию, можно сделать так, чтобы выделялось не фиксированное небольшое количество памяти для переноса файла в редактор, а равное его длине. Можно также воспользоваться функциями отображения части файла в память (CreateFileMapping, OpenFileMapping, MapViewOfFile, UnmapViewOfFile), позволив работать с неограниченно большими файлами. Win32 API настолько богат функциями, что можно довольно долго заниматься только их изучением, а это относится к теме программирования на ассемблере ровно настолько, насколько относится к программированию на любом другом языке.


Содержание раздела