Win32asm hello, world і три халяви masm32
Win32ASM: "Hello, World" і три халяви MASM32
# 1. З легкої лівої руки Дениса Річі повелося починати освоєння нової мови програмування з створення найпростішої програми "Hello, World". Ніщо людське нам не чуже - давайте і ми зробимо цей гріх.
У позапр ошлом випуску я вже розповів про те, як працювати в асемблері з апішнимі функціями, однак ви напевно не зрозуміли;). Це нормально, і не потрібно через це турбуватися. Все стане більш ніж ясним після того як ми з вами напишемо одну-дві простенькі програмки і розберемо їх по рядках.
Заново перечитайте "Мінімальна додаток" і набийте наступний исходник:
Ось два рядки з мого батника (* .bat), який дозволяє не "париться" з командним рядком:
Звертаю увагу, що для складання консольного застосування необхідно використовувати ключ / SUBSYSTEM: CONSOLE. Незважаючи на те що віконце, в якому воно запуститься, до болю нагадує "сеанс MS-DOS", що вийшла програма - повноцінне віндозное 32-бітове додаток у форматі PE. Ассембліруем, лінкуем, запускаємо, насолоджуємося.
# 2. А тепер давайте влаштуємо цього ісходнику розбирання.
Геп 1. Таким чином ми визначаємо локальну змінну з ім'ям hStdout і розміром подвійне слово (DWORD). Чому локальна? А тому, що вона існує тільки всередині процедури Main. і якби ми спробували звертатися до змінної hStdout за межами цієї процедури, асемблер б лаяв нас всякими нехорошими словами - на відміну від, скажімо, константи sWriteText. ім'я якої "відомо" в будь-якому місці нашої програми.
Зверніть увагу на префікс h в назві змінної. Це я просто залишив для себе пам'ятку, що змінна заведена під хендл.
Геп 2. Апішная функція SetConsoleTitleA - встановлюємо титл (заголовок) для нашого консольного віконця. Ось витяг з MSDN'а:
Геп 3. Консоль ми можемо використовувати як пристрій введення (input device), пристрій виведення (output device), пристрій для звіту про помилки (error device). Для того щоб працювати з цим "девайсом", ми повинні отримати його хендл за допомогою наступної функції:
Єдиний параметр, який вона від нас вимагає - вказівка, на який пристрій ми бажаємо отримати "квиток" -хендл. Ось табличка:
Хендл стандартного введення
Хендл стандартного виводу
Що нам потрібно? Вивести рядок! Значить - запитуємо хендл для стандартного виводу, тобто перед викликом функції "суем" в стек -11. Після виконання функції регістр EAX містить такий бажаний "хендл стандартного виводу". Кладемо цей хендл в змінну hStdout (яку ми настільки завбачливо визначили на геп 1) для подальшого використання.
- Це ж що за неподобство? - викликніть ви. - Що це за таблиця така нездорова? Якісь негативні числа, які ні в жисть не запам'ятати! Хочемо таблицю як в MSDN'е! Щоб не -10, -11, -12, а довгі мнемонічні STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE!
Спокійно! Исходник, який ми зараз розглядаємо, досить точно відображає реальні процеси, що відбуваються в програмі. Трохи пізніше ми наведемо його до варіанту в стилі Сі і подивимося, як можна використовувати деякі високорівневі конструкції, що значно полегшують життя низкорівневому програмісту.
Геп 4. Ну нарешті, найголовніше - функція, яка, власне, і виводить на консоль рядок символів. Ось її опис:
Тепер, коли ми розібрали всі параметри, зверніть увагу на те, що MSDN'овская черговість параметрів не відповідає тій черговості, в якій ми записуємо їх в стек в нашому исходнике. Поверніться ще раз до Мінімального додатком, п.12 і уважно прочитайте пункти угоди stdcall. Тепер зрозуміло?
Геп 5. Щоб ми встигли помилуватися результатом своєї праці праведних, за допомогою функції Sleep викликаємо програмну затримку в 2 секунди. Думаю, з параметрами ви без зусиль розберетеся.
І, нарешті, геп 6 - вихід з програми.
Взагалі-то, правильний стиль передбачає явне звільнення всіх зайнятих ресурсів по мінованію потреби в них, в тому числі і хендлом, незважаючи на те що вони автоматично закриваються ExitProcess 'ом. Але будемо сподіватися, що якщо ми не зробимо це в такій маленькій программуліна як наша, нічого страшного не трапиться. Природно, "формат ЦЕ" не береться до уваги.
# 3. Тепер робимо перший крок по приведенню нашого сирцю в більш Новомосковскбельний вид.
Отже, перше, з чим ми ознайомимося - це еквіваленти, прописані у файлі /MASM32/windows.inc.
Ми вже стикалися з MSDN'овской табличкою:
Standard input handle
Standard output handle
Standard error handle
Однак замість мнемонічного інтуїтивно-зрозумілого аргументу STD_OUTPUT_HANDLE вносили в стек значення -11, невідомо звідки взяте. Давайте напишемо відразу ж після директиви includelib наступний рядок:
А рядок push -11 замінимо на push STD_OUTPUT_HANDLE.
Що вийшло? Програма відкомпілювати без проблем, бо на самому початку лістингу ми прописали equ [валент]. Простіше кажучи, ми сказали асемблеру: "якщо ти зустрінеш в тексті програми STD_OUTPUT_HANDLE. То май на увазі, що це те ж саме, що і -11". Іншими словами, завели щось типу константи (не буде змінена!) З ім'ям STD_OUTPUT_HANDLE і значенням -11.
Тепер відкрийте файл windows.inc і помилуйтеся його вмістом. Там ціла купа "еквівалентів", на зразок вищерозглянутого! І щоб скористатися цією халявою - зовсім не обов'язково копіювати ту чи іншу константу через буфер обміну. Можна вчинити набагато простіше - додати в исходник директиву
У відповідь на це асемблер сам витягне з windows.inc всю наявну в цьому файлі інформацію і піднесе її транслятора на блюдечку з блакитною облямівкою.
# 4. Друга халява, якої ми з вами скористаємося - це "інклуд" (давайте саме так будемо називати файли * .inc) з прототипами функцій. Ми вже розглядали, що таке прототипи, і яку роль вони відіграють при лінковке нашої програми з бібліотеками імпорту. Звичайно ж, ми можемо самі, на основі MSDN'овкого опису функції, вивести її прототип, але навіщо нам примножувати сутності понад необхідного? Адже в MASM32 для кожної з бібліотек імпорту є і однойменний файл з прототипами. У нашому прикладі ми використовували функції kernel32 і для цього лінкуватися його з бібліотекою kernel32.lib. Ну а відповідний файл з прототипами називається kernel32.inc!
Що може бути простіше? З нашого исходника вирізаємо під три чорти блок з прототипами, а на його місце ліпимо директиву include [шлях] kernel32.inc. Компільо, і, як кажуть по телику, "тепер ви можете забути про ці незручних промокають:" (ууупс. Знову пішли брутальні фантазії; час починати новий абзац.).
Тепер, мабуть, прийшов час стримати свою обіцянку і пояснити - якого біса ми до кінця функції WriteConsole приліпили букву "А". Пояснюю - а тому що немає в винде функції WriteConsole!
# 5. зате є функції WriteConsoleA і WriteConsoleW. "A" - це якщо ви хочете надрукувати рядок у форматі ASCII (тобто кожен знак займає один байт), а "W" - якщо в Unicode (W - від wide, широкий. В Unicode знаки не 8-бітові, а 16-бітові, і займають два байта). Подібні закінчення мають лише ті функції, які тим чи іншим чином працюють зі строковими значеннями. Функція ExitProcess. наприклад, подібного літерного закінчення не має - поміркуйте самі, чи не все одно, на якому національною мовою завершувати роботу програми?
Відкриємо файл kernel32.inc і пильно подивимося на його вміст, зокрема, на наступне:
Як бачимо, команда розробників MASM32 подбала не лише про простирадлі прототипів, але і про "незалежність" нашого исходника від обраної кодування. Тобто для того, щоб "перезаточіть" програму під UNICODE, нам зовсім не потрібно замінювати закінчення A на W в імені функції. Досить просто пріінклюдіть інший файл з прототипами і еквівалентами на зразок
і не "паритися" з переписуванням исходника.
Треба відзначити, в MASM32 подібного "Юнікодние" інклуд немає, проте ви легко можете зробити його самі.
# 6. І, нарешті, третя, найбільша "халява" - це маленька фенечка, використання якої відразу ж перетворює макроассемблер з мови кодування в мову програмування!
За допомогою цієї "фенечки" цілий блок інструкцій:
ми з легкістю можемо замінити однією-єдиною рядком:
Зверніть увагу, що при використанні цієї команди параметри ми передаємо зліва направо, в тій же черговості, що і віщає нам MSDN. На відміну від простирадла "пушей" c "Кали" в кінці.
# 7. Тепер найголовніший момент. Затамуйте подих!
У світлі вищесказаного, вишераспісанного і вишерасжеванного наш исходник приймає вельми красивий "високорівнева" вид: