ЛАСКАВО ПРОШУ
на мій сайт
© ihorweb@ukr.net
Головна | Програми | Програмування | Айкідо | Гриоріанський vs юліанський календар | Додатковий заробіток | Тимошенко vs Ющенко | Вибори на Тернопіллі 2009 | Детальна карта-план Тернополя | На форум | Нічого доброго ;-) | Розваги: mp3 avi | Свято
Програмування > укр
Програмування на асемблері під Windows
MASM32 для Win32



Вступ

Вас вчили програмувати на Бейсіку, Паскалі і асемблері для DOS, а Ви бажаєте програмувати вікна під Windows з малюнками і написами різними шрифтами? Ви не впевнені яку мову програмування вибрати? Ви зайшли туди, куди треба.

Цей опус призначений не для нулячих початківців (але і для них це все стане у нагоді). Нагадати все, що читач забув про асемблер під DOS, він може за допомогою викладених тут книжечок (зокрема, Справочник по системе программирования ТУРБО АССЕМБЛЕР 2.0, глави з 4 до 5 вистачить для початку), а вже потім перейти до уроків для Windows. Одразу зазначу, що про сегменти пам'яті, сегментні регістри і переривання DOS читати не обов'язково, бо їх вже давно нема. Уроки - це засильно сказано :-D . На сторінці є дійсно хороші уроки Ісзеліона для MASM32 (ICZELION_chm.zip), але вони дуже великі. І тут шановному читачеві прийшов на допомогу я :-) . Виклав свої елементарні програмки, до кожного нового рядка - пояснення, просто копіюєте, асемблюєе - і готово. Для програмування на асемблері для Win32 систем стане у нагоді Справочная система по языку ассемблера IBM PC, Юров В.И.. Корисна вона тим, що там все описано із врахуванням 32-бітності регістрів (не тільки для dx:ax, а й для edx:eax).

Чому саме на асемблері?.. Бо іноді краще зробити свій велосипед, ніж купляти чужий і довго розбиратися чого він не їздить... ;-) Асемблер - це програмування з нуля. З асемблером Ви керуєте комп'ютером, а не навпаки: комп'ютер керує, а Ви - просто юзер (позорне слово таке - фе! на "лузер" схоже).

Отже, для початку треба навчитися набирати текст в Блокноті (notepad.exe). Хто подумав про паперову книжечку - топайте писати вірші, нашо вам то програмування :-) . Потім треба потягнути з нету MASM32 (Microsoft assembler Win32) або як я вважав раніше - Macro Assembler для Win32 :-) . Витягнути його можна з masm32.com. Потім просто запустіть INSTALL.EXE, вкажіть диск, на який хочете поставити асемблер і чекайте, поки програма інсталяції завершить свою роботу. На вказаному диску створюється каталог MASM32, в якому є багато чого. З того, що я використовую - все є в підкаталозі BIN:
ML.EXE - перетворює ваш код набраний в Блокноті і збережений з розширенням *.ASM в "допоміжний" файл з розширенням *.OBJ - його я завжди знищую вкінці, він мені ще ні разу не став у нагоді, і я не буду писати, що це таке;
RC.EXE, CVTRES.EXE - роблять те саме з вашим файлом з розширенням *.RC (допоміжний файл при програмуванні, в якому записуються малюнки, меню і таке інше - ресурсний файл, він може бути відсутнім);
LINK.EXE - лінкує допоміжні файли *.OBJ вихідного коду і файлу ресурсів в один *.EXE файл.

Щоб з тим всім не паритися, створюєте в каталозі \MASM32\BIN\ папку з любим іменем, наприклад, моїм :-) - io, і вже тут будете писати свої програми *.ASM, а асемблювати-лінкувати їх в EXE-шки будете за допомогою *.BAT файлу:
- якщо файла ресурсів немає, то вміст пакетного файлу такий 00_asm.bat;
- якщо ж файл ресурсів є, то вміст пакетного файлу такий 00_asmrc.bat.
Там є всякі різні параметри - колись розберетеся... Я ті всі параметри знав, а тепер дивлюся і половини не пам'ятаю - все на світі знати не будеш :-). Також я називаю файли з моїм асеблерним кодом 00_asm.asm (основна програма), 00_rc.rc (файл ресурсів), 00_dll.asm (код DLL бібліотеки). Щоб не змінювати пакетний файл, різні програми в мене мають одинакове ім'я файлу (00_asm.asm), а коли програма вже зроблена, я її просто переміщаю в \masm32\bin\io\<ім'я папки для конкретної програми\>. Як читач організує свою роботу - се його особиста справа :-)

Таким чином, після інсталяції безкоштовного пакету MASM32:
1. Створюємо каталог для своїх програм (\masm32\bin\io).
2. Створюємо файли компіляції наших програм (00_asm.bat і 00_asmrc.bat).
3. Набираємо першу програму в текстовому файлі 00_asm.asm.
4. Запускаємо 00_asm.bat - компілюємо нашу програму в EXE-шку (00_asm.exe).
5. Запускаємо готову EXE-шку, милуємося результатом.
6. Якщо все нормально, копіюємо 00_asm.asm i 00_asm.exe в папку \masm32\bin\io\first_asm_prg\.

Урок 1.
Структура асемблерної програми на MASM32

Різні процесори мають різні команди. Ми використовуватимемо команди процесорів починаючи з і386. Пам'ять у Win32 систем плоска 32-бітна, а не сегментна 16-бітна як в ДОС. Тобто адрЕс пам'яті типу 00f0h:0100h вже давно нема. До пам'яті звертаються за допомогою єдиного конкретного 32-бітного числа. Також у Windows параметри процедур передаються в стекові (push останній параметр, push передостанній параметр, ... , push перший параметр, виклик процедури call my_proc). Передача параметрів в MASM32 реалізована непомітно для програміста за допомогою чудового оператора invoke (invoke my_proc,param1,param2...). Процедури і функції бібліотек DLL Windows описані в довідниках API функцій: WinAPI_Help_rus.rar, Win32_API_hlp_eng_structured.rar - ті, якими користуюся я... а взагалі їх є багато, пошукаєте в неті. Ну все, тепер дивимося на структуру програми:
01_structura.rar - з описом трохи багато виглядає, а тому в архіві є без опису (01_structura2.asm).

Урок 2.
Типи даних і директиви MASM32.

Типи даних точнісінько такі ж як і в асемблері для DOS:
db - байт
dw - два байти (слово)
dd - чотири байти (подвійне слово)
...і інші. Але вони можуть бути представлені у Windows-варіанті, наприклад: HINSTANCE, DWORD - це подвійні слова, WORD - це слово, BYTE - байт і т.д.:
n_dilene BYTE 10.
При описі параметрів процедур і локальних змінних процедур використовуються тільки Windows-варіанти типів даних.
Змінні через крапку - це структури:
MY_STRUCT STRUCT (опис власної структури)
  my_x dd 0
  my_y dd 0
MY_STRUCT ENDS
st_myXY MY_STRUCT <> (змінна власної структури)
st_point POINT <> (змінна стандартної структури Windows)
...
mov eax,st_myXY.my_x .
Опис стандартних структур Windows шукати в файлі \masm32\include\windows.inc і в довідниках API.
Найкорисніші директиви masm32:
.IF-.ELSEIF-.ELSE-.ENDIF та цикл .WHILE-.ENDW - їхнє застосування показано в прикладі другого уроку:
02_dani.rar
Також я позначаю всі змінні в залежності від типу: n_... - числова, s_... - стрічкова, st_... - структура (незавжди), p_... вказівник (pointer) на щось (p_s_... - на стрічку, p_st_... - на структуру) і т.д.

Урок 3.
Вікно-повідомлення. Робота з файлами: DOS метод.

Перейдемо до створення найпростішої програми, яка вже щось робить, а саме: виводить повідомлення, читає і створює файл).
03_msgbox_files.rar.
Ця програма вже є, фактично, інтерактивною, бо стандартна функція MessageBox вертає в регістрі eax значення, яке залежить від кнопки, яку на вікні-повідомленні натиснув користувач. Які саме значення вертають функції - все шукайте в довідниках Win32 API, або в інтернеті. Робота з файлами у цьому прикладі здійснюється за допомогою застарілих функцій _lopen, _lclose, але використовувати нові функції для початку - занадто складно, і не для початку теж складно - особисто я Windows-методом (через CreateFile) практично не користуюся (лише для прямого доступу до секторів диску). Кому цікавий Windows-метод, пошукайте в уроках ICZELIONa.
В MASM32 використовують два оператори для передачі вказівки (ссилки, адреси, поінтера) на щось (на стрічку, наприклад). Крім звичного в асемблері OFFSETа використовується ще ADDR. Останній працює лише в межах оператора INVOKE або для локальних змінних процедури (offset для локальних змінних не працює), причому addr s_1 працює, якщо змінна s_1 описана вище, і не працює, якщо вона описана нижче по коду програми (offset s_1 працюватиме в обох випадках), хоча я завжди описую змінні зверху програми, так що addr завжди працюватиме в invoke. Увага! Передача в параметрах процедури змінної і адреси (вказівки) змінної - це абсолютно різні речі. Уважно читайте в довідниках API що саме треба передати параметром в процедурі.

Урок 4.
Загальне вікно. Повідомлення операційної системи вікну.

Вікно створюється стандартною процедурою CreateWindowEx. Перед його створенням треба заповнити певну структуру - клас вікна - і зареєструвати цей клас в операційній системі. Після створення, вікно за допомогою спеціального циклу отримує від Windows повідомлення різного типу (WM_...): яку кнопку мишки натиснули в вікні, яку клавішу натиснули на клавіатурі при активному вікні програми і багато-багато інших (шукайте в довідниках), або система переключається на іншу програму, якщо з вікном нічого не відбувається. Заповнення структури класу вікна, створення вікна і запуск нескінченного циклу для отримання повідомлень утворюють практично незмінний код для багатьох програм, тому всі ці речі, як правило (і я йому підкоряюся), пишуться в окремій процедурі WinMain. Для обробки повідомлень (при клікові мишкою, наприклад) в Віндовс передбачено спеціальний спосіб: виклик колл-бек процедури вікна (callback). Виклик цей здійснює операційна система. Ця процедура може мати довільну назву (в мене - WndProc), але адреса її вказується при створенні вікна, щоб система знала що викликати, коли для вікна є повідомлення. Отже, спочатку заповнюємо деякі змінні, тоді викликаємо WinMain з її вічним циклом який чекає на повідомлення, поки не отримає нуль-повідомлення 0, яке означає, що вікно закривається - тоді цикл обривається і відбувається вихід з процедури вікна WinMain.
І, нарешті, код програми: 04_windowMessages.rar.
Хто злякався вигляду коду з поясненнями - в архіві є без пояснень (04_windowMessages2.asm).
Нагадую! Детальний опис стандартних процедур і функцій Windows (CreateWindowEx, ShowWindow, GetMessage, TranslateMessage, DispatchMessage, PostQuitMessage, DefWindowProc...) шукайте в довідниках API.

Урок 5.
Повідомлення від клавіатури і мишки. Контекстне меню.

Повідомлення від операційної системи наше вікно вже отримувало на попередньому уроці, тому нічого складного на цьому уроці не буде. Просто, коли над вікном рухають мишкою, натискають на кнопки миші і клавіатури при активному вікні, система посилає цьому вікну повідомлення: WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_KEYDOWN, WM_CHAR і т.д. При отриманні таких повідомлень, можемо реагувати на них в залежності від потреби. Список всіх констант шукайте в довідниках, як взнати яку саме кнопку клавіатури натиснуто, показано в програмі. Також в ній показано як створювати контекстне меню і відслідковувати натиски на конкретному пункті цього меню. При виборі певного пункту меню, при натисненні на кнопку (дочірнє вікно у даному вікні), при введенні тексту в едіт-контрол (edit-control), при відміченні галочкою чек-бокса (checkbox) і при дії з іншими елементами управління, розташованими у вікні, система посилає вінку повідомлення WM_COMMAND. Це повідомлення посилається із заповненими параметрами процедури вікна lParam і wParam, значення яких будуть описані в прикладах до уроків, але краще читати в довідниках. Отже, код програми до уроку та відповідна екзешка:
05_mouseKeyboard_kontextMenu.rar.
Також я написав програму, в якій обробляється подія WM_MOUSEMOVE, але там багато всього (в тому числі і незнайомого як на 5-й урок), тому в архіві пропоную читачеві лише екзешку (05_mouseActions.exe).

Урок 6.
Меню вікна.

Меню вікна (горизонтальна полоска зверху) можна створити трьома способами. В програмі до цього уроку показано спосіб, що нагадує створення контекстного меню. Звичайне меню вікна створюється за допомогою CreateMenu, конкретні пункти меню в нього додаються аналогічно (AppendMenu), тоді меню прикріпляється до вікна функцією SetMenu, після якої меню постійно відображається у вікні (якщо не відображається або меню змінилося є функція DrawMenuBar - перемалювати стрічку меню).
Є ще два способи створення меню вікна. Обидва методи базуються на описі меню в файлі ресурсів (00_rc.rc), де меню отримує ім'я, за яким в програмі його можна завантажити функцією LoadMenu або вказати це ім'я при реєстрації класу вікна (mov wc.lpszMenuName,offset s_menuName). В останньому випадку це меню матимуть всі вікна даного зареєстрованого класу. Файл ресурсів (06_rc.rc) є в архіві уроку (доречі програма з файлом ресурсів компілюється іншим пакетним файлом - 00_asmrc.bat - я думаю, що читач пам'ятає). Хендл створеного (або завантаженого) меню можна передати як параметр в функції створення вікна CreateWindowEx. Де конкретно цей хендл вставляти, шукайте в довідниках. У запропонованому файлі ресурсів описано інше меню, ніж в програмі до уроку, але там немає нічого складного, розберетеся самостійно, якщо саме цей метод створення меню вікна припаде комусь до душі.
06_menu.rar.

Урок 7.
Системний трей.

Системний трей - це область справа на панелі задач (там, де є годинник і інші значки). Вікно не може попасти в системний трей - воно туди не влізе :-) . Але вікно можна при згортанні заховати (ShowWindow,h_wnd,SW_HIDE), а в системному треї додати іконку, кліки по якій посилають вікну спеціальне повідомлення користувача. Як це зробити - показано в прикладі до уроку:
07_trey.rar.

Урок 8.
Малювання у вікні. Малювання тексту.

Малювати у вікні - це взагалі-то неправильне формулювання. Правильно казати малювати на контексті вікна. Отримується дескриптор контексту вікна функцією GetDC або функцією BeginPaint. Остання функція разом з функцією EndPaint використовується в межах повідомлення WM_PAINT, яке вікно отримує, коли воно перший раз показується після створення, коли вікно змінює розміри або перекривається іншим вікном (і в інших випадках). Функцію GetDC можна викликати при потребі де завгодно (як відповдь на вибір пункту меню тощо), але все намальоване після виклику даної функції не зберігатиметься, якщо вікно змінить розміри і таке інше. Повідомлення WM_PAINT можна викликати штучно функцією InvalidateRect та деякими іншими. До уроку написано декілька прикладів. У першому показано як малювати примітивні фігури та текст різними шрифтами і кольором. У другому прикладі показано як відображати у вікні BMP малюнки, як змінити вигляд курсору, як завантажити власну іконку до програми; ці графічні файли вставляються в спеціальну секцію в EXE-файлі, а потім завантажуються з виконуваного модуля функціями LoadBitmab, LoadIcon, LoadCursor. Не те шо описати, а навіть перерахувати всі графічні функції видається мені заняттям не дуже цікавим. Тут я вкотре відправляю читача шукати все в довідниках API функцій.
В архіві 08_draw.rar є такі файли: 08_draw.asm, 08_draw.exe - малювання примітивних фігур і тексту;
08_drawbmp.asm, 08_drawbmp.rc, 08_drawbmp.exe - малювання картинок (з використанням файлу ресурсів!); ці файли потрібні для компіляції: 08_proba.bmp, 08_myIcon.ico, 08_myCursor.cur - вони потрібні лише під час компіляції пакетним файлом 00_asmrc.bat, після компіляції вони не потрібні, бо всі як один будуть поміщені всередину EXEшки.
08_drawdesktop.asm, 08_drawdesktop.exe - а це ще один приклад - малюваня безпосередньо на робочому столі. Функція GetDC вертає контекст області користувача вікна (програміст не зможе малювати по системних кнопках вікна - хрестик і ін.). Функція GetWindowDC вертає дескриптор контексту всього вікна разом з заголовком, системними кнопками, рамкою вікна. Щоб малювати на десктопі, викликати треба GetWindowDC.
08_rotateTransform.exe, 08_drawToParalelohram.exe - екзешки, які показують ще дві з нескінченних можливостей графічних API функцій: зміна системи координат (поворот) і малювання прямокутного бітмапа в межах паралелограма (PlgBlt).
08_wndrgn.asm, 08_wndrgn.exe - і наостанок - вікно непрямокутної форми (використовується створення області - region, - а це належить до графічного інтерфейсу, тому в уроці по малюванню).

Урок 9.
Елементи управління: кнопки, поля вводу тексту, чекбокси...

Windows - це Вікна, на шо не плюнь - все вікна! Елементи управління в вікні - це теж вікна, це вікна стандартних класів, це дочірні вікна в основному вікні. Для створення власного вікна треба створити його клас і зареєструвати в системі. Щоб створити кнопку у вікні, не потрібно реєструвати клас кнопки, треба просто вказати ім'я класу "button" при виклику CreateWindowEx. Усі інші елементи управління теж створюються як дочірні вікна основного вікна. При створенні елемента управління (кнопки, списку тощо), в CreateWindowEx обов'язково вказується клас елемента (button, edit, listbox...), хендл батьківського вікна (яке буде приймати повідомлення від елементів управління) і унікальний в межах програми ідентифікатор елемента. При дії над елементом управління (натиснення на кнопку), система посилає основному вікну повідомлення WM_COMMAND. Як відрізнити від якого саме елемента управління прийшло повідомлення показано в прикладі до уроку. Там також є найуживаніші імена стандартних класів вікон. Інші імена є переаховані в уроках ICZELIONa, а також їх можна знайти в довідниках, якщо добре пошукати :-). Знищувати елементи управління (контроли) не потрібно, бо всі дочірні вікна знищуються системою разом із закриттям основного вікна. На цьому уроці читач також познайомиться із новими функціями: SetWindowText, GetWindowText та SendMessage з новими константами (WM_SETFONT, BM_GETCHECK, LB_GETCURSEL, LB_ADDSTRING).
09_controls.rar.
І ще трохи про контроли (елементи управління). Програміст просто створює текстове поле (едіт-контрол), а операційна система повністю бере на себе обробку подій в ньому (натиснення на клавішу, вивід символів, переміщення курсору, знищення символів, там навіть є контекстне меню: видєліть-копіровать-вставіть, тощо). Але програміст може втрутитися в обробку подій системою. Для цього існує суперклассінг вікна, або як я це називаю - перехват повідомлень контрола. Оскільки елемент управління - це вікно (стандартного класу, але це вікно), то в нього є процедура вікна (WndProc). От цю процедуру можна написати свою, і наш едіт-контрол буде, наприклад, виводити тільки знаки оклику при введенні будь-яких символів:
09_superclassing.asm, 09_superclassing.exe - все в архві.

Урок 10.
Процеси і потоки.

Процес - це процес :-) Кожна окрема програма - це процес. З одного процесу (програми) можна запустити інший процес (програму). Робиться це викликом функції CreateProcess із вказанням імені процесу (наприклад, "00_asm.exe") і інших параметрів. Детально про інші параметри шукайте в довідниках. Програма до уроку запускає стандартну програму Windows "Блокнот" (notepad.exe <ім'я текстового файлу>).
Процесор виконує програму покроково: одна команда, потім друга, третя і т.д. Якщо, програміст запрограмував здоровенний цикл, то нічого більше в цій програмі не може виконуватися аж поки цей цикл не закінчиться. Така програма називається однопотоковою. У програмі можна запускати декілька потоків, які будуть виконуватися псевдоодночасно. Процесор переключається з одної частини програми (процедури) на іншу, потім назад на першу, тому в користувача складається враження, що процедури виконуються одночасно. Коли програма запускається, вона має свій потік (один). Створити ще один потік в програмі можна функцією CreateThread. Детальний опис в довіднику.
10_ProcessThread.rar.

Урок 11.
Таймер. Звук.

Таймер використовується для посилання вікну повідомлення WM_TIMER з певною періодичністю. На моєму процесорі мінімальна затримка між повідомленнями таймера складає 0.01 с (соту долю секунди). Для одного вікна можна встановити декілька таймерів. Встановлюється таймер функцією SetTimer, знищується функцією KillTimer. У програмі до уроку показано простий приклад створення таймера вікна. Також у програмі є рядок із функцією sndPlaySound, що запускає з поточного каталогу звуковий WAV файл. Параметри нових процедур шукайте в довідниках.
11_TimerSound.rar.

Урок 12.
Створення DLL бібліотеки.

Ми вже давно користуємося стандартними динамічними бібліотеками функцій Windows. Наприклад, постійно присутня в усіх програмах функція ExitProcess викликається з бібліотеки kernel32.dll. Прикольно було б, якби програмісту довелося копіювати в свій код внутрішній код процедури ExitProcess, замість простого invoke ExitProcess... Отже, бібліотеки DLL - штука не просто потрібна, а конче необхідна. Програміст в результаті роботи понаписує багато процедур, які буде часто використовувати. Для цього ці процедури зручно розмістити у власній динамічній бібліотеці (ба, навіть, у декількох бібліотеках). Внутрішня структура асемблерного коду DLL така ж як і звичайних програм. Тільки замість точки входу (start:), пишеться процедура ініціалізації (dllProc), яка мусить вернути в eax TRUE, якщо ініціалізація пройшла успішно, і FALSE - в іншому випадку. Для компіляції асемблерного коду бібліотеки у власне бібліотеку використовується трошечки інший пакетний файл 00_dll.bat. Крім того, для компіляції коду необхідно створити файл *.DEF, у якому вказати внутрішнє ім'я майбутньої DLL і імена всіх функцій, які можна буде викликати з даної динамічної бібліотеки (12_my_dll.def в архіві до цього уроку - 12_DLL.rar). В прикладі до уроку створюється динамічна бібліотека 12_my_dll.dll, яка містить єдину функцію для перетворення числа типу DWORD у стрічку. Отже:
1) створюємо 12_my_dll.asm (містить робочу функцію makeStringFromNumber), 12_my_dll.def (містить внутрішнє ім'я DLL і імена функцій DLL) і компілюємо за допомогою 00_dll.bat. Компілятор створить 12_my_dll.dll і 12_my_dll.lib.
2) створюємо 12_invokeDLL.asm (викликає функцію makeStringFromNumber), 12_my_dll.inc (містить PROTO-опис функції makeStringFromNumber, щоб полегшити виклик її за допомогою оператора invoke) і компілюємо в екзешку за допомогою стандартного пакетного файлу 00_asm.bat. Для цієї компіляції необхідні файли *.INC і *.LIB. Для виконання EXEшки необхідна обов'язкова присутність в поточному каталозі (або в системних папках Windows) файлів *.DLL i *.LIB.
Також є динамічні бібліотеки особливого призначення. Про хуки в Windows (hooks) читач дізнається з відповідного уроку ICZELIONa.

Урок 13.
Робота з мережею.

На цій щасливій цифрі і неймовірно цікавій темі я поки-що завершую. Можливо колись ще повернусь до мого улюбленого асемблера.

При копіюванні матеріалів прохання посилатися на цей сайт

Google


    Rambler's Top100  
Сайт создан в системе uCoz