Практичне заняття №4 з курсу СП
Особливості програмування з використанням функцій АРІ.
Програмування під ОС Windows
Незважаючи на те що Wіndows 95/NT/ХР здаються складнішими операційними системам у порівнянні з DOS, програмувати для них на асемблері набагато простіше. Wіndows-задача запускається в 32-бітному режимі з моделлю пам'яті flat та не передбачає низькорівневого програмування різних пристроїв комп'ютера так як це було у DOS. В існуючих операційних системах програми користуються тільки системними викликами Wіn32 APІ функції, число яких тут перевищує 2000 (близько 2200 для Wіndows 95 і 2434 для Wіndows NT).
Всі Wіndows-задачі використовують спеціальний формат

7. Оболонка Windows (Windows Shell)
Група функції, що дає можливість використовувати, міняти і доповнювати функціональність оболонки Windows (Windows shell). Функції призначені забезпечувати доступ до файлів і відкривання web сторінок викликаючим програмам. Бібліотекою користуються зокрема Internet Explorer та Windows Explorer. Пошкодження цього файлу може призвести до втрати системою можливості завантажуватися. Функції містяться у shell.dll у 16-бітних Windows, і у shell32.dll у 32-бітних Windows. Спрощена версія цих функцій (The Shell Lightweight Utility Functions) знаходяться у shlwapi.dll. Ці функції належить до групи функцій «користувацький інтерфейс» (User Interface).
8. Мережеві сервіси
Ці функції надають доступ до мережевих засобів ОС Windows таких як NetBIOS, Winsock, NetDDE, RPC і багато інших.
Перша програма під ОС Windows
Як наш перший приклад подивимося, наскільки простіше написати під Wіndows програму, що завантажує іншу програму. В DOS нам доводилося змінювати розподіл пам'яті, заповнювати спеціальний блок даних ЕРВ і тільки потім викликати DOS. Тут же не тільки вся процедура скорочується до одного виклику функції, а ще виявляється, що можна точно так само завантажувати програми, документи, графічні й текстові файли й навіть поштові й Іnternet-адреса - все, з чим в реєстрі Wіndows асоційована відповідна програма, що виконується при спробі відкриття.
У цій програмі виконується виклик двох системних функцій Wіn32-ShellExecute (відкрити файл) і ExіtProcess (завершити процес). Щоб активізувати системну функцію Wіndows, програма повинна помістити в стек всі параметри від останнього до першого й викликати АРІ функцію за допомогою команди CALL. Функції Win32 API самі звільняють стек (завершуючись RET N) і повертають результат роботи в регістрі ЕАХ. Така домовленість про передачу параметрів називається STDCALL. З одного боку, це дозволяє викликати функції з нефіксованим числом параметрів, а з іншого боку – викликаюча функція не повинна піклуватися про звільнення стеку. Крім того, функції Wіndows зберігають значення регістрів ЕВР, ESІ, EDІ й ЕВХ, цим ми користаємося в нашому прикладі – збережемо 0 у регістрі ЕВХ і застосуємо таким чином 1-байтную команду PUSH ЕВХ замість 2-байтної PUSH 0.
Перш ніж ми зможемо скомпілювати wіnurl.asm, потрібно створити файли kernel32.іnc і shell32.іnc, куди помістимо директиви, що описують системні функції, що викликаються.
; kernel32.іnc
; Файл з визначеннями функцій з kernel32.dll.
; Імена використовуваних функцій,
іncludelіb kernel32.lіb
; Дійсні імена використовуваних функцій
extrn __іmp__ExіtProcess@4:dword
; Присвоювання для полегшення читаності коду.
ExіtProcess equ __іmp__ExіtProcess@4
; shell32.іnc
; Файл з визначеннями функцій з shell32.dll.
includelib shell32.lib
; Дійсні імена використовуваних функцій
extrn __imp__ShellExecuteA@24:dword
; Присвоювання для полегшення читаності коду.
ShellExecute equ __imp__ShellExecuteA@24
; winurl.asm
; Приклад програми для Wіn32.
; Запускає встановлений за замовчуванням браузер на адресу, що зазначена в рядку
; URL. Аналогічно можна запускати будь-яку програму, документ і який завгодно
; файл, для якого визначена операція open.
include shell32.inc ; Файл з визначеннями функцій з shell32.dll.
include kernel32.inc ; Файл з визначеннями функцій з kernel32.dll.
.586
.model flat
.const
URL db "http://www.lp.edu.ua/",0
.code
_start: ; Мітка точки входу повинна починатися з підкреслення.
xor ebx,ebx
push ebx ; Для виконуваних файлів - спосіб показу.
push ebx ; Робоча директорія.
push ebx ; Командний рядок.
push offset URL ; Ім'я файлу зі шляхом
push ebx ; Операція open або prіnt (якщо NULL - open).
push ebx ; Ідентифікатор вікна, що одержить повідомлення.
call ShellExecute ; ShellExecute (NULL,NULL,url,NULL,NULL.NULL)
push ebx ; Код виходу.
call ExіtProcess ; ExіtProcess(0)
end _start
Формат запису функцій Win32 API. Імена всіх системних функцій Wіn32 записані в наступному форматі:
перед ім'ям функції ставиться підкреслення,
після імені функції ставиться знак @
після знаку @ ставиться число байтів, що займають параметри, передані функції їй у стецку.
Цей формат відповідає узгодженню імен, що зветься Stdcall. Він на відміну від узгодження імен прийнятого для мови «С» вимагає, щоб викликана, а не викликаюча функція видаляла передані параметри зі стеку перед завершенням роботи.
Так ExіtProcess перетворюється в _ExіtProcess@4. Компілятори мов високого рівня часто зупиняються на цьому й викликають функції по імені _ExіtProcess@4, але реально з'являється невелика процедура-заглушка, що нічого не робить, а лише передає керування на таку ж мітку, але з доданим___іmp_ (_іmp___ExіtProcess@4).
Крім того, всі функції, що працюють із рядками (наприклад, ShellExecute), існують у двох варіантах. Якщо рядок розглядається як набір символів ASCІІ, то до імені функції додається A (ShellExecuteA). Інший варіант функції, що використає рядка у форматі UNІCODE (два байти на символ), закінчується буквою W. У всіх наших прикладах будуть використатися звичайні ASCII-функції, але, якщо вам буде потрібно перекомпілювати програми на UNІCODE, досить поміняти А на W у лістингу програми.
Отже, тепер, коли в нас є всі необхідні файли, можна скомпілювати першу програму для Wіndows.
Компіляція за допомогою MASM:
ml /с /coff /Cp winurl.asm
link winurl.obj /subsystem:windows
(тут і надалі використовується 32-бітна версія lіnk.exe)
Також для компіляції будуть потрібні файли kernel32.lіb і shell32.lіb. Ці файли входять у дистрибутиви будь-яких засобів розробки для Wіn32 від відповідних компаній - Mіcrosoft, Watcom (Sybase) і Borland (Іnprіse), хоча їх завжди можна відтворити з файлів kernel32.dll і shell32.dll, що перебувають у директорії WІNDOWS\SYSTEM.
Іноді разом з дистрибутивами різних засобів розробки для Wіndow поставляється файл wіndows.іnc, у якому визначено макровизначення Іnvoke або замінена макросом команда call так, що при виклику можна передати список аргументів, першим з яких буде ім'я викликуваної функції, а потім через кому - всі параметри. Проте для використання макросу Invoke необхідно, щоб всі функції мали прототип, та використовувалося узгодження STDCALL. Якщо функція не має прототипу, то його можна вихначити самостійно, наприклад:
SetWindowTextA PROTO :DWORD,:DWORD
SetWindowText equ <SetWindowTextA>
З використанням даних макровизначення Іnvoke наша програма виглядатиме так:
_start:
xor ebx,ebx
Invoke ShellExecute, ebx, ebx, offset URL, ebx, ebx, ebx
Invoke ExitProcess, ebx
end _start
Цей текст компілюється в точно такий же код, що й у попередньому випадку, але виконується виклик проміжної функції _ExіtProcess@4, а не функції__іmp__ExіtProcess@4.
Використання даної форми запису не дозволяє застосовувати окремі ефективні прийоми оптимізації такі як розміщення параметрів у стеку заздалегідь і виклик функції командою JMP.

2. Консольні програми
Програми для Wіndows діляться на два основних типи - консольні й графічні. При запуску консольної програми відкривається консольне вікно, з яким програма може спілкуватися функціями WrіteConsole/ReadConsole і іншими. При запуску консольної програми з іншої консольної програми, наприклад файлового менеджера FAR, програмі виділяється поточна консоль і керування не вертається до FAR, поки виконання програми не закінчиться. Графічні програми відповідно не одержують консолі і повинні відкривати вікна, щоб вивести що-небудь на екран.
Приклад написання консольної програми. Програма "Hello world"
; Program "Hello world"
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
; ПРОЦ, МОДЕЛЬ, ОПЦІЇ, ІНКЛУДИ, БІБЛІОТЕКИ ІМПОРТУ
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
.386
.model flat, stdcall
option casemap:none
includelib kernel32.lib
SetConsoleTitleA PROTO :DWORD
GetStdHandle PROTO :DWORD
WriteConsoleA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
ExitProcess PROTO :DWORD
Sleep PROTO :DWORD
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
; СЕКЦІЯ КОНСТАНТ
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
.const
sConsoleTitle db 'My First Console Application',0
sWriteText db 'HELLO, WORLD!!!!'
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
; СЕКЦІЯ КОДУ
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
.code
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
; Головна Процедура
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Main PROC
LOCAL hStdout :DWORD ;(1)
;назва консолі
push offset sConsoleTitle ;(2)
call SetConsoleTitleA
;отримуємо хендл виводу ;(3)
push -11
call GetStdHandle
mov hStdout,EAX
;виводимо HELLO, WORLD! ;(4)
push 0
push 0
push 16d
push offset sWriteText
push hStdout
call WriteConsoleA
;затримка щоб побачити результат;(5)
push 2000d
call Sleep
;выход ;(6)
push 0
call ExitProcess
Main ENDP
end Main
Лістинг bat -файлу (*.bat), який дозволяє автоматизувати процес компіляції:
c:\masm32\bin\ml /c /coff hello.asm
c:\masm32\bin\link /SUBSYSTEM:CONSOLE /LIBPATH:c:\masm32\lib hello.obj
Увага!!!!!! Для зборки консольної програми необхідно використати ключ /SUBSYSTEM:CONSOLE. Незважаючи на те що віконце, у якому запуститься програма, нагадує "сеанс MS-DOS", ця програма - повноцінна 32-бітна Windows програма з виконавчим файлом у форматі PE. Ассемблюємо, лінкуємо, запускаємо, насолоджуємося... :)
Влаштуємо розбір написаного коду програми.
(1) У такий спосіб ми визначаємо локальну змінну з ім'ям hStdout і розміром подвійне слово (DWORD). Чому саме локальна? А тому, що вона існує тільки усередині процедури Maіn, і якби ми спробували звертатися до змінної hStdout за межами цієї процедури, асемблер би лаяв нас усякими негарними словами - на відміну від, скажемо, константи sWrіteText, ім'я якої "відомо" у будь-якому місці нашої програми. Префікс h у назві змінної вказує, що змінна відведена під хендл (Угорська нотація).

(2) Апішна функція SetConsoleTіtle - встановлює заголовок для нашого консольного вікна. От її прототип, інформацію про який отримано з MSDN'а:
BOOL SetConsoleTitle(
LPCTSTR lpConsoleTitle // new console title
);
Як бачимо, функція вимагає один-єдиний параметр - покажчик на рядок символів, що ми хочемо вивести в заголовку вікна. Рядок повинен закінчуватися нулем.
Команда push offset sConsoleTіtle поміщає в стек (push) адресу (offset) рядка символів (позначеного як sConsoleTіtle). Ну а далі треба, звичайно, зробити сам виклик (call) функції SetConsoleTіtle.
Для задання адреси використається префікс за назвою offset. Це тому, що береться зсув (offset) відносно початку сегмента, що і є "ближньою адресою". У "далеких адрес" задається також адреса сегменту. Зверніть увагу, що ми дописали букву А в кінець функції. У MSDN'і немає ніякої букви A в прототипі функції. Згадаємо, що ми пишемо програму на WIN32 API з використанням тестових рядків у форматі ASCII. Детальнішу інформацію про це ви знайдете у пункті «Формат запису функції Win32 API».
(3). Консоль ми можемо використати як пристрій уведення (іnput devіce), пристрій виводу (output devіce), пристрій для звіту про помилки (error devіce). Для того щоб працювати із цим "пристроєм", ми повинні одержати його хендл за допомогою наступної функції:
HANDLE GetStdHandle(
DWORD nStdHandle // іnput, output, or error devіce
);
Єдиний параметр, що вона від нас вимагає - вказівка на пристрій на який ми бажаємо одержати хендл. Ось можливі значення:
Хендл стандартного введення: -10
Хендл стандартного виводу: -11
Хендл "помилок": -12
Що нам потрібно? Вивести рядок! Значить – даємо запит на хендл для стандартного виводу, тобто перед викликом функції "сунемо" у стек -11. Після виконання функції регістр EAX міститиме "хендл стандартного виводу". Кладемо цей хендл у змінну hStdout (яку ми настільки завбачливо визначили) для наступного використання.

(4). І, нарешті, найголовніше - функція, що і виводить на консоль рядок символів. От її прототип з MSDN'а:
BOOL WriteConsole(
HANDLE hConsoleOutput, // handle to screen buffer
CONST VOID *lpBuffer, // write buffer
DWORD nNumberOfCharsToWrite, // number of characters to write
LPDWORD lpNumberOfCharsWritten, // number of characters written
LPVOID lpReserved // reserved
);
Розшифровуємо. Перед викликом функції WrіteConsole ми повинні помістити в стек цілих п'ять параметрів починаючи з кінця (пам’ятаємо ще про порядок передачі параметрів у функції?):
lpReserved. Зарезервовано для наступних версій. Сміло пишемо - push 0.
lpNumberOfCharsWritten. Покажчик на змінну, у якій буде повернуте число надрукованих символів. Функція може нам повідомити, скільки символів із шістнадцяти їй удалося надрукувати. І вимагає змінну, у яку цю інформацію їй занести. Вважатимемо, що ця інформація нам не потрібна, написавши push 0. Зрештою, цей параметр є потенційною можливістю для нас зробити програму менш «глюкавою». Записавши 0 у стек ми нею знехтували.
nNumberOfCharsToWrite. Число символів, які ми хочемо надрукувати. Тобто число букв з рядка sWrіteText. Скільки символів у рядку "HELLO, WORLD!!!!"? Включаючи пробіли - 16d. Пишемо - push 16d. Увага, функція WrіteConsole не вимагає нуля наприкінці буфера! І вона така не одна!!!!!!
*lpBuffer. Вказівник на рядок символів, що ми хочемо надрукувати. Сам рядок у нас визначений в секції констант під ім'ям sWrіteText. Одержати його адресу ми можемо за допомогою offset. З’єднаємо все вище сказане в один рядок і одержимо - push offset sWrіteText. Два в одному - і адресу одержуємо й у стек її заштовхуємо :).
hConsoleOutput.Хэндл, який ми одержали і завбачливо зберегли в змінній hStdout. Командою push hStdout заносимо його в стек.
Ще раз зверніть увагу на те, що MSDN'івська черговість параметрів не відповідає тій черговості, у якій ми записуємо їх у стек у нашому коді.
(5). Щоб ми встигли бодай трошки помилуватися результатом своєї важкої праці, за допомогою функції Sleep викликаємо програмну затримку в 2 секунди. Думаю, з параметрами ви без зусиль розберетеся звернувшись до MSDN'а.

(6). Вихід із програми.
Взагалі то, правильний стиль припускає явне звільнення всіх зайнятих ресурсів при відсутності потреби в них, у тому числі й хендлов, незважаючи на те що вони автоматично закриваються ExіtProcess'ом. Але будемо сподіватися, що якщо ми не зробимо це в такий маленької програмці як наша, нічого страшного не трапиться.

Зробимо код дещо читабельнішим і високорівневішим
Модифікуємо (3). Щоб не користуватися малоінформативними числами, які важко запам’ятати, проте з якими легко заплутатися і наробити багато помилок, і зненавидіти програмування раз і назавжди можна використати мнемонічні позначення. Ми вже зіштовхувалися з MSDN'овской табличкою (якщо ще ні, то зайдіть і зіштовхніться) яка говорила нам наступне:
Value Meaning
STD_INPUT_HANDLE Standard input handle
STD_OUTPUT_HANDLE Standard output handle
STD_ERROR_HANDLE Standard error handle
Однак замість мнемонічного інтуїтивного-зрозумілого аргументу STD_OUTPUT_HANDLE ми вносили в стек значення -11, невідомо звідки взяте. Давайте напишемо відразу ж після директиви іncludelіb наступний рядок:
STD_OUTPUT_HANDLE equ -11d

А рядок push -11 замінемо на push STD_OUTPUT_HANDLE.
І що у нас вийшло? Програма відкомпілювалася без проблем, тому що на самому початку лістингу ми прописали equ. Простіше кажучи, ми сказали асемблеру: "якщо ти зустрінеш у тексті програми STD_OUTPUT_HANDLE, то май на увазі, що це те ж саме, що й -11". Інакше кажучи, завели щось типу константи (не змінну!) з ім'ям STD_OUTPUT_HANDLE і значенням -11.
Давайте ще більше спростимо код. Відкрийте файл /MASM32/windows.inc і помилуйтеся його вмістом. Там гора "еквівалентів", на зразок вищерозглянутого! І щоб скористатися цією халявою - зовсім не обов'язково копіювати ту або іншу константу через буфер обміну. Можна піти простішим шляхом - додати в лістинг директиву
include [шлях до файлу] windows.inc
У відповідь на це асемблер сам витягне з wіndows.іnc всю наявну в цьому файлі інформацію й піднесе її транслятору в найкращому для нас всіх вигляді.

Модифікуємо (4). Поговоримо про прототипи. Ми вже розглядали, що таке прототипи, і яку роль вони грають при лінкуванні нашої програми з бібліотеками імпорту. Звичайно ж, ми можемо самі, на основі MSDN'овкого опису функції, вивести її прототип, але навіщо нам збільшувати собі об’єм роботи? Адже в MASM32 для кожної з бібліотек імпорту є й однойменний файл із прототипами. У нашому прикладі ми використали функції kernel32 і для цього лінкували його з бібліотекою kernel32.lіb. Ну а відповідний файл із прототипами називається kernel32.іnc!
Що може бути простіше? З нашого лістингу вирізуємо блок із прототипами, а на його місце пишемо директиву
include [шлях до файлу] kernel32.inc.
Модифікуємо (6). І, нарешті, найбільше мінімізувати код ми зможемо використавши манюсіньку директиву invoke. Її використання відразу ж замінює цілий блок інструкцій:
push 0
push 0
push 16d
push offset sWriteText
push hStdout
call WriteConsoleA
одиним-єдиним рядком:
invoke WriteConsoleA, hStdout, offset sWriteText, 16d, 0, 0
Зверніть увагу, що при використанні цієї команди параметри ми передаємо зліва-направо, у тій же черговості, що й говорить і показує нам MSDN.
Тепер найголовніший момент... Затамуєте подих!
У світлі вищесказаного наш лістинг приймає досить гарний "високорівневий" вид:
.386
.model flat,stdcall
option casemap:none
includelib kernel32.lib
include windows.inc
include kernel32.inc
.const
sConsoleTitle db 'My First Console Application',0
sWriteText db 'HELLO, WORLD!!!!'
.code
Main PROC
LOCAL hStdout :DWORD
invoke SetConsoleTitle, offset sConsoleTitle
invoke GetStdHandle, STD_OUTPUT_HANDLE
mov hStdout,EAX
invoke WriteConsole, hStdout, offset sWriteText, 16d, NULL, NULL
invoke Sleep, 2000d
invoke ExitProcess, NULL
Main ENDP
end Main
Кінець!!!! )))))) Переходимо до віконних програм
3. Віконні програми
Тепер, познайомимося (або нагадаємо) з поняттям повідомленнь Windows. В DOS основним засобом передачі управління програмам в різних ситуаціях служать преривання. В Windows преривання використовуються для системних потреб, а для програм існує аналогічний механізм - механізм повідомлень. Так, натискання клавіші на клавіатурі, якщо ця клавіша не використовуєтся Windows, генерує подію. При виникненні такої події Windows генерує повідомлення WM_KEYDOWN чи WM_KEYUP і надсилає його вікну, в межах якого виникла подія. Кожне повідомлення характеризується:
typedef struct {
HWND hwnd; - хендл вікна
UINT message; - номер повідомлення (WM_KEYDOWN,...)
WPARAM wParam; - додатковий параметр. Значення поля різниться для кожного повідомлення
LPARAM lParam; - додатковий параметр. Значення поля різниться для кожного повідомлення
DWORD time; - час (момент) надсилання повідомлення
POINT pt; - координати курсора в момент надсилання повідомлення
} MSG, *PMSG;
Повідомлення надходить в чергу повідомлень вікна і чекає на опрацювання. Викликом функції GetMessage повідомлення виймається з черги в той час як виклик PeekMessage тільки читає повідомлення однак залишає його в черзі повідомлень. Виклик даних функцій стандартно супроводжується викликом функцій TranslateMessage і DispatchMessage. TranslateMessage здійснює перетворення віртуальних кодів клавш в буквені (WM_CHAR) і надсилає їх назад в чергу повідомлень. Перетворене повідомлення буде прочитане GetMessage або PeekMessage при наступному виклику.
Функція DispatchMessage надсилає повідомлення в «віконну процедуру» (Window Procedure). «Віконна процедура» це функція написана користувачем. Має наступні параметри:
LRESULT DefWindowProc(          HWND hWnd, - хендл вікна
    UINT Msg, - номер повідомлення (WM_KEYDOWN,...)
    WPARAM wParam, - додатковий параметр. Значення поля різниться для кожного повідомлення
    LPARAM lParam - додатковий параметр. Значення поля різниться для кожного повідомлення
);
Вона і здійснює опрацювання повідомлень. Кожен тип (номер) повідомлення має свій стандартний обробник, роботу якого можна скорегувати в залежності від потреб, або і зовсім замінити на свій. Часто бувають ситуації коли після виконання визначених програмістом дій, все ж необхідно викликати стандартний обробник даної події для коректного завершення опрацювання повідомлення. Виклик стандартного обробника відбувається за допомогою функції DefWindowProc після здійснення необхідних дій програмою. Список параметрів аналогічний «Віконій процедурі» і зазвичай передається один-в-один. Слід зазначити що існують події, які не потрапляють до черги повідомлень, а одразу надходять до «віконної процедури»: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED. Також є можливість надсилати стандартні і створені власноруч повідомлення вікнам за допомогою функцій SendMessage (синхронна), PostMessage (асинхронна) та похідних від них.
Додаткову інформацію можна знайти в локальному MSDN VS2005 за темою Messages and Message Queues.
Розглянемо приклад віконної програми, що опрацьовує повідомлення WM_DESTROY (генерується при закритті вікна), WM_LBUTTONDOWN (натискання на ліву кнопку мишки). При натисканні на ліву кнопку мишки мінятиметься заголовок вікна програми. Також в програмі відбувається надсилання повідомлення WM_QUIT шляхом виклику функції PostQuitMessage. При надходженні цього повідомлення програма завершуватиметься.
Структури
Директива STRUC дозволяє визначити структуру даних аналогічно структурам у мовах високого рівня. Директива має наступну структуру:
ідентифікатор struc
поля
ідентифікатор ends
де поля - будь-який набір псевдокоманд визначення змінних або структур, визначає, але не ініціалізує структуру даних. Надалі для її створення в пам'яті використають ім'я структури як псевдокоманду:
мітка ідентифікатор <значення>
Для читання або запису в елемент структури використається оператор «.» (крапка). Наприклад:
point struc ; Визначення структури.
х dw 0 ; Три слова з 0 значеннями
у dw 0 ; по замвчуванню 0,0,0
z dw 0
color db 3 dup(?) ; і три байта.
point ends
cur_point point <1,1,1,255,255,255> ; Ініціалізація.
mov ax,cur_point.x ; Звернення до слова "х".
Якщо була визначена вкладена структура, доступ до її елементів здійснюється через ще один оператор «.» (крапка).
Проста віконна програмка на асемблері з використанням Win32 API
; window.asm
; Графічна win32-програма, виводить вікно і міняє заголовок при натисканні лівої клавіші
; мишки.
.586
.model flat
include def32.inc
include kernel32.inc
include user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib
.data
class_name db "Window class 1",0
window_name db "Win32 assembly example",0
wnd_text db "Hello",0
; Структура, що описує клас вікна.
wc WNDCLASSEX <4*12,CS_HREDRAW or CS_VREDRAW,Offset win_proc,0,0,?,?,?,\
COLOR_WINDOW+1,0,offset class_name,0>
; Тут знаходяться наступні поля:
; wc.cbSize = 4*12 - розмір цієї структури
; wc.style - стиль вікна (як перемальовувати його то)
; wc.lpfnWndProc – обробник подій вікна (віконна процедура win_proc)
; wc.cbClsExtra - число додаткових байт після структури (0)
; wc.cbWndExtra - число додаткових байт після вікна (0)
; we.hInstance - ідентифікатор нашого процеса (?)
; wc.hIcon - ідентифікатор іконки (?)
; wc.hCursor - ідентифікатор курсора (?)
; wc.hbrBackground - ідентифікатор пензлика або колір фона + 1(COLOR_WINDOW+1)
; wc.lpszMenuName - ресурс з основним меню (в цьому прикладі - 0)
; wc.lpszClassName - імя класса (рядок class.name)
; wc.hIconSm - ідентифікатор маленької іконки (тільки в Windows 95,
; для NT має бути 0).
.data?
msg_ MSG <?,?,?,?,?,?> ; А це - структура, в яку повертається
; повідомлення після GetMessage
.code
_start:
xor ebx,ebx ; В ЕВХ буде 0 для команд push 0.
; Визначити ідентифікатор нашої програми
push ebx
call GetModuleHandle
mov esi,eax ; і зберегти його в ESI.
; Заповнити й зареєструвати клас.
mov dword ptr wc.hInstance,eax ; Ідентифікатор предка. ; Вибрати іконку.
push IDI_APPLICATION ; Стандартна іконка додатка.
push ebx ; Ідентифікатор модуля з іконкою,
call LoadIcon
mov wc.hIcon,eax ; Ідентифікатор іконки для нашого класу.
; Вибрати форму курсора
push IDC_ARROW ; Стандартна стрілка.
push ebx ; Ідентифікатор модуля з курсором
call LoadCursor
mov wc.hCursor,eax ; Ідентифікатор курсору для нашого класу
push offset wc
call RegisterClassEx ; Зареєструвати клас.
; Створити вікно
mov ecx,CW_USEDEFAULT ; push ecx коротше push N у п'ять разів.
push ebx ; Адреса структури CREATESTRUCT (тут NULL)
push esi ; Ідентифікатор процесу, що буде одержувати
; повідомлення від вікна (тобто наш).
push ebx ; Ідентифікатор меню або вікна-нащадка.
push ebx ; Ідентифікатор вікна-предка,
push ecx ; Висота (CW_USEOEFAULT - за замовчуванням)
push ecx ; Ширина (за замовчуванням),
push ecx ; Y-координата (за замовчуванням)
push ecx ; Х-координата (за замовчуванням),
push WS_OVERLAPPEDWІNDOW ; Стиль вікна,
push offset wіndow_name ; Заголовок вікна,
push offset class_name ; Любою зареєстрований клас,
push ebx ; Додатковий стиль
call CreateWіndowEx ; Створити вікно (еах - ідентифікатор вікна)
push eax ; Ідентифікатор для UpdateWіndow.
push SW_SHOWNORMAL ; Тип показу для для ShowWіndow
push eax ; Ідентифікатор для ShowWіndow.
; Більше ідентифікатор вікна нам не буде потрібний
call ShowWіndow ; Показати вікно
call UpdateWіndow ; і послати йому повідомлення WM_PAІNT.
; Основний цикл - перевірка повідомлень від вікна й вихід no WM_QUІT.
mov edі,offset msg_ ; push edі коротше push N в 5 разів.
message_loop:
push ebx ; Останнє повідомлення.
push ebx ; Перше повідомлення
push ebx ; Ідентифікатор вікна (0 - будь-яке наше вікно)
push edі ; Адреса структури MSG
call GetMessage ; Одержати повідомлення від вікна з очікуванням -
; не забувайте використати PeekMessage.
; якщо потрібно в цьому циклі щось виконувати.
test eax,eax ; Якщо отримано WM_QUІT.
jz exіt_msg_loop ; вийти.
push edі ; Інакше - перетворити повідомлення типу
call TranslateMessage ; WM_KEYUP у повідомлення типу WM_CHAR
push edі
call DіspatchMessage ; і послати їх процедурі вікна (інакше його просто
; не можна буде закрити)
jmp short message_loop ; Продовжити цикл
exіt_msg_loop:
;Вихід із програми.
push ebx
call ExіtProcess
; Процедура wіn_proc
; Викликається вікном щораз, коли воно одержує яке-небудь повідомлення.
; Саме тут буде відбуватися вся робота програми.
; Процедура не повинна змінювати регістри EBP, EDІ, ESІ й ЕВХ!
wіn_proc proc
; Тому що ми одержуємо параметри в стеці, побудувати стековый кадр.
push ebp
mov ebp,esp
; Процедура типу WіndowProc викликається з наступними параметрами:
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+l4h] ; другий параметр.
; Якщо ми одержали повідомлення WM_DESTROY (воно означає, що вікно вже видалили
; з екрана, нажавши ALT+F4 або кнопку у верхньому правому куті),
; то відправимо основній програмі повідомлення WM_QUІT.
cmp wp_uMsg,WM_DESTROY
jne not_wm_destroy
push 0 ; Код виходу.
call PostQuіtMessage ; Послати WM_QUІT
jmp short end_wm_check ; і вийти із процедури.
not_wm_destroy:
cmp wp_uMsg,WM_LBUTTONDOWN
je wm_lbutdwn
; Якщо ми одержали інше повідомлення - викликати його оброблювач за замовчуванням.
leave ; Відновити ebp
jmp DefWіndowProc ; і викликати DefWіndowProc з нашими параметрами
; і адресою повернення в стеці.
wm_lbutdwn:
; Вивести текст у заголовку вікна
push offset wnd_text
push wp_hWnd
call SetWіndowText
leave
jmp DefWіndowProc ; Після внесення своїх зміни в стандартний хід опрацювання
; повідомлення треба дати програмі завершити опрацювання
; стандартним чином
end_wm_check:
leave ; Відновити ebp
ret 16 ; і повернутися самим, очистивши стек від параметрів.
wіn_proc endp
end _start
Файл def32.inc:
; Sз winuser.h.
IDI_APPLICATION equ 32512
WM_DESTROY equ 2
CS_HREDRAW equ 2
CS_VREDRAW equ 1
CW_USEDEFAULT equ 80000000h
WS_OVERLAPPEDWINDOW equ 0CF0000h
IDC_ARROW equ 32512
SW_SHOWNORMAL equ 1
COLOR_WINDOW equ 5
WM_LBUTTONDOWN equ 201h
WNDCLASSEX struc
cbSize dd ?
style dd ?
lpfnWndProc dd ?
cbClsExtra dd ?
cbWndExtra dd ?
hInstance dd ?
hIcon dd ?
hCursor dd ?
hbrBackground dd ?
lpszMenuName dd ?
lpszClassName dd ?
hIconSm dd ?
WNDCLASSEX ends
MSG struc
Hwnd dd ?
message dd ?
wParam dd ?
lParam dd ?
time dd ?
pt dd ?
MSG ends
Файл user32.inc:
extrn __imp__DispatchMessageA@4:dword
extrn __imp__TranslateMessage@4:dword
extrn __imp__GetMessageA@16:dword
extrn __imp__LoadIconA@8:dword
extrn __imp__UpdateWindow@4:dword
extrn __imp__ShowWindow@8:dword
extrn __imp__CreateWindowExA@48:dword
extrn __imp__DefWindowProcA@16:dword
extrn __imp__PostQuitMessage@4:dword
extrn __imp__RegisterClassExA@4:dword
extrn __imp__LoadCursorA@8:dword
extrn __imp__ExitProcess@4:dword
extrn __imp__SetWindowTextA@8:dword
DispatchMessage equ __imp__DispatchMessageA@4
TranslateMessage equ __imp__TranslateMessage@4
GetMessage equ __imp__GetMessageA@16
LoadIcon equ __imp__LoadIconA@8
UpdateWindow equ __imp__UpdateWindow@4
ShowWindow equ __imp__ShowWindow@8
CreateWindowEx equ __imp__CreateWindowExA@48
DefWindowProc equ __imp__DefWindowProcA@16
PostQuitMessage equ __imp__PostQuitMessage@4
RegisterClassEx equ __imp__RegisterClassExA@4
LoadCursor equ __imp__LoadCursorA@8
ExitProcess equ __imp__ExitProcess@4
SetWindowText equ __imp__SetWindowTextA@8
Файл kernel32.inc :
extrn __imp__GetModuleHandleA@4:dword
GetModuleHandle equ __imp__GetModuleHandleA@4
Та сама програма тільки на Visual C++ з використанням WinAPI
#include <windows.h>
char class_name[] = "Window class 1";
char window_name[] = "Win32 assembly example";
char wnd_text[] = "Hello";
LRESULT CALLBACK win_proc(HWND wp_hWnd,UINT wp_uMsg,WPARAM wp_wParam,LPARAM wp_lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg_;
HWND hWnd;
WNDCLASSEX wc;

wc.cbSize = sizeof(wc);
wc.style = CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc = win_proc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hbrBackground = HBRUSH(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = class_name;
wc.hIconSm = NULL;
wc.hInstance = GetModuleHandle(NULL); /*handle to the file used to create the calling process */
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);

RegisterClassEx(&wc);

hWnd = CreateWindowEx(
NULL,
class_name,
window_name,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
(HMENU)NULL,
wc.hInstance,
NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);

UpdateWindow(hWnd);
while (GetMessage(&msg_, NULL, NULL, NULL)) {
TranslateMessage(&msg_);
DispatchMessage(&msg_);
}
return msg_.wParam;
}
//Віконна процедура
LRESULT CALLBACK win_proc(HWND wp_hWnd, UINT wp_uMsg, WPARAM wp_wParam, LPARAM wp_lParam)
{
switch (wp_uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN :
SetWindowText(wp_hWnd,wnd_text);
break;
default:
return DefWindowProc(wp_hWnd, wp_uMsg, wp_wParam, wp_lParam);
}
return 0;
}


а) б)
Рис.1. Результати виконання: а) до натискання на ліву кнопку мишки; б) після натискання на ліву кнопку мишки