Віконний додаток на асемблері, Блог по Windows
За основу для вивчення я взяв стандартний шаблон 32-бітного віконного додатка на ассемблері з ім'ям template.asm , що поставляється у складі пакета FASM і розміщується в піддиректорії \EXAMPLES\TEMPLATE\ і трохи модифікував його для деякої наочності. Для початку представимо вихідний код програми:
Ідеологія програмування під Windows
Для взаємодії з користувачем (обміну даних), коду програми користувача в операційній системі Windows не потрібно робити викликів будь-яких спеціалізованих функцій, що очікують введення (натискання клавіш клавіатури/миші, введення символів у поле введення) від користувача, як це практикувалося за часів MSDOS . Фактично у Windows відсутні функції, які зчитують рядок символів з клавіатури або чекають на введення будь-якого числового значення.
Підхід до програмування серед Windows змінився і тепер основна концепція програмування орієнтована так звані події. Це означає, що ядро системи стежить за апаратною/програмною активністю, і у відповідь генерує спеціальні повідомлення, які потім передає (через спеціальні системні механізми) додатків, які очікують на наступ цих подій. Іншими словами, можна стверджувати, що програми в Windows керуються подіями. З подіями асоціюється будь-яка користувальницька/системна активність: переміщення вікон, натискання клавіш клавіатури/миші, зміни стану буфера обміну, зміни апаратурної конфігурації, зміни статусу енергоспоживання, зміна значень таймерів та інша. Тому, коли відбувається будь-яка подія (до яких належить і будь-які дії користувача), система сама "надає" для користувача вхідні дані, і робить вона це за допомогоюпередачі повідомлень. А прикладна програма, у свою чергу, складається з обробників вхідних повідомлень, які необхідним чином реагують на них.
Як тільки програма завершує обробку події, керування повертається ядру системи. Це кардинально змінює підхід до написання програм, оскільки в MSDOS програма сама контролювала власні дії/події, тепер же операційна система передає програмі користувача управляючі повідомлення, які активують ті чи інші функції обробки.
Але крім обробки введення або реакцію інші вхідні системні повідомлення, прикладної програмі потрібно виконувати й інші дії над об'єктами операційної системи. З метою забезпечення доступу програм користувача до всього спектру виконуваних компонентів Windows, надається так званий програмний інтерфейс додатків (API). Це означає, що весь функціонал операційної системи доступний через функції, і щоб програмісту що-небудь зробити треба викликати функцію відповідного призначення.
| MZ | Mark Z bikowski | формат 16-бітових файлів з розширенням .exe для ОС MSDOS |
| PE PE64 | P ortable E xecutable | формат 32/64-бітних виконуваних файлів з розширенням .exe для Windows |
| COFF MS COFF MS64 COFF | C ommon O bject F ile F ormat | формат об'єктного файлу, що містить проміжне представлення коду програми, призначений для поєднання з іншими об'єктними файлами (проектами/ресурсами) з метою отримання готового модуля, що здійснюється. |
| ELF ELF64 | E xecutable and L inkable F ormat | формат файлів систем сімейства UNIX, що виконуються. Об'єктний файл (.obj) для компілятораgcc. |
| ARM | A dvanced R ISC M achine | формат файлів, що виконуються під архітектуру ARM (?) |
| Binary | файли бінарної структури. Що поставите, те й збереться. Наприклад, виставивши зсув 100h (org 100h) від початку, можна отримати старий-добрий .com-файл під MSDOS. Формат має ряд аналогічних застосувань для створення довільних бінарних програм або файлів даних. |
Другим параметром (після вказівки формату файлу, що виконується) директиви format може вказуватися тип підсистеми для створюваної програми:
| GUI | Графічне (віконне) додаток. Вихідний файл, що передбачає створення типових віконних додатків та ініціалізацію на початковій стадії всіх відповідних бібліотек Win32 API. Вихідний виконуваний файл, у якого в структурі PE-заголовка IMAGE_NT_HEADERS , підструктурі OptionalHeader значення поля Subsystem = 2 (воно ж IMAGE_SUBSYSTEM_WINDOWS_GUI). |
| console | Консольний додаток. Вихідний файл, що передбачає виконання коду в консолі, без участі віконного інтерфейсу. Вихідний виконуваний файл, у якого в структурі PE-заголовка IMAGE_NT_HEADERS , підструктурі OptionalHeader значення поля Subsystem = 3 (воно ж IMAGE_SUBSYSTEM_WINDOWS_CUI). |
| native | Рідний/нативний додаток. Вихідний виконуваний файл, у якого в структурі PE-заголовка IMAGE_NT_HEADERS , підструктурі OptionalHeader значення поля Subsystem = 1 (воно ж IMAGE_SUBSYSTEM_NATIVE). Подібне значення поля зазвичай характерне для драйверів, бібліотек та програм режиму ядра, яким не потрібна ініціалізація підсистеми Win32 на стадії підготовки образу до виконання. |
| DLL | Динамічна бібліотека. Особливий формат вихідного виконуваного файлу, що призначається для експорту (надання) функцій стороннім додаткам, у якого в структурі PE-заголовка IMAGE_NT_HEADERS , підструктурі IMAGE_FILE_HEADER , поле Characteristics включений прапор IMAGE_FILE_DLL ( 2000h ). |
| WDM | Системний драйвер побудований на основі моделі WDM (Windows Driver Model). |
| EFI EFIboot EFIruntime | UEFI-додаток. Вихідний виконуваний файл, у якого в структурі PE-заголовка IMAGE_NT_HEADERS , підструктурі OptionalHeader , значення поля Subsystem = 10 11 12 13 (воно ж IMAGE_SUBSYSTEM_EFI_APPLICATION, IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE VER, IMAGE_SUBSYSTEM_EFI_ROM). Подібне значення поля потрібне для створення UEFI-додатків різних стадії/типу: завантаження, виконання та драйвера. |
Роль даного параметра досить велика, оскільки саме він визначає, яка саме підсистема буде викликатися для запуску файлу, що виконується, тобто фактично визначає програмне оточення при запуску процесу. Якщо використовується тип програми GUI, необхідно уточнювати мінімальну версію системи (у нас: 4.0), під яку створюється наш модуль, що виконується. Потім, у рядку під номером 2 у нашому вихідному коді розташовується директива з ім'ям entry, яка визначає точку входу в програму.
У нашому випадку підключається файл %include%\win32a.inc , який, у свою чергу, містить посилання на інші файли, що підключаються, що містять визначення ключових структур, необхідних для компіляції нашої програми: макросів, типів даних, констант, системних структур. Без включення цього файлу у нас просто не пройде процес компіляції нашого вихідного коду, тобтофайл, що виконується, не буде створений (не збереться).
З можливими варіантами стилів можна ознайомитись на відповідній сторінці, яка описує стилі вікна.
Безпосередньо за визначальними заголовком директивами, слідує вихідний код, розділений на області, звані секціями . Придивіться до наведеного вихідного коду і ви побачите, що весь лістинг фактично розділений на своєрідні логічні блоки, що починаються з директиви section і звані секціями. Поряд із заголовком, секції є невід'ємними складовими частинами як файлу вихідного коду, так і виходить на виході компілятора виконуваного PE-файлу.
Використання секцій регламентовано структурою формату виконуваних PE-файлів, що використовуються у системі Windows. Саме специфікація формату PE визначає вимоги до наявності певних структур у виконуваних файлах та наказує використання тих чи інших секцій для поділу інформаційних блоків. Відразу після директиви section в одинарних лапках (апостроф) задається ім'я (назва) секції та ряд параметрів: тип секції, прапори (атрибути) секції.
Прапори можуть приймати такі значення: code , data , readable , writeable , executable , shareable , discardable , notpageable , крім них можуть використовуватися специфікатори секції даних, такі як export , import , resource , fixups , які визначають структуру (будова) сек. Типи секцій, прапори та їх комбінації я звів у таблицю:
Однак на практиці це зовсім не суворе правило, оскільки як мінімум можна навести один виняток - розміщення даних у секції коду. Однак, подібне змішування коду та даних може призвести до проблем з безпекою (порушення прав доступу, якщо секція коду не маркована для запису), так само як і проблем кешування нарівні процесора, що може позначитися на зниженні швидкодії програми. Інших винятків із правила та прикладів я назвати не можу, оскільки просто не тестував. Щодо кількості однотипних секцій у вихідному коді можна сказати, що всі вони збираються в єдину секцію на етапі компіляції вихідного коду.
Секція коду (code)
Мабуть, без перебільшення, цю секцію можна сміливо найбільш значущою, оскільки саме вона визначає всю логіку роботи програми, яку ми створюємо. Саме в секції code міститься опис того, як саме працює і що робить наш віконний додаток на асемблері, тобто саме секцією коду визначається алгоритм роботи програми. Як Ви вже зрозуміли, тестовий приклад, що вивчається нами, досить простий і всю його логіку можна описати наступним невеликим списком:
- Отримуємо дескриптор екземпляра поточного процесу (в контексті якого виконується наш код);
- Реєструємо клас вікна. Реєстрація власного класу потрібна у всіх випадках за винятком тих, коли Ви використовуєте стандартні (передбачені, що надаються системою) типи вікон;
- Створюємо вікно на основі щойно зареєстрованого класу;
- Відображаємо вікно на екрані (виклик додаткової функції, яка в нашому випадку не використовується. Це зовсім не означає, що вікно з нашого прикладу не відображається на екрані, просто воно відображається за допомогою основних функцій);
- Обновляємо клієнтську область вікна (у разі не використовується, оскільки ми займаємося перемальовкою клієнтської області, наш приклад для цього занадто простий);
- Входимо в нескінченний цикл обробки повідомлень для всіх вікон, що належать до нашого процесу. У цьому прикладі обробляються повідомлення лише до одного основноговікно;
- Повідомлення, що надходять для будь-якого з вікон, що контролюються нами, обробляються спеціальною функцією;
- Виходимо з програми натискання користувачем кнопки Закрити (X) або комбінації клавіш Alt + F4 ;
Візуальним результатом нашої програми є виведення на робочий стіл звичайного вікна з єдиною системною кнопкою (закрити) у правому верхньому кутку.
