Pull to refresh

Использование макросов в MASM на примере создания окна

Reading time 6 min
Views 10K
В далеком 2001-ом году я проводил много времени за изучением ассемблера под Win32. Тогда после долгих мучений с написанием одного и того же кода по сотне раз я взялся написать для себя небольшую библиотеку макросов. В итоге удалось достаточно серьезно облегчить себе судьбу и уменьшить необходимость повторять огромные полотенца кода, при необходимости написать простейшую программу с одним окном.

Недавно наткнулся на те проекты и решил выложить некоторые из них, может кому пригодится…



Состав проекта

Итак приступим. Проект, прикрепленный внизу, имеет следующую структуру.

\Macros Каталог с макросами, используемыми в приложении
Macros.Inc Здесь находятся основные макросы, которые нужны при написании любой Win32 программы. Здесь макросы для выделения памяти, облегчения включения файлов, макросы для определения данных и проч.
Window.Mac Макросы, которые облегчают создание окон
Status.Mac Макросы для создания и использования статус строк
Menu.Mac Макросы для создания и использования меню
Quake.Bmp Изображение, которое будет загружено и отображено в окне программы
Scull.Ico Иконка изображения (просто черепок)
Rsrc.rc Файл определения ресурсов
Window.Asm Основной файл программы
Window.Exe Откомпилированная программа
WndExample.Asm В данном файле находится исходный код для обработки сообщений идущих к окну «Example» нашей программы

При запуске Window.Exe отображаемое окно будет выглядеть следующим образом:

image

Простейшая программа, без окна


include macros\macros.inc

@Start
@Uses  kernel32

.code
WinMain Proc

    invoke ExitProcess, 0
WinMain Endp
        End WinMain


Первой строкой здесь включение основных макросов, далее идет макрос Start, который создает начало программы и подставляет информацию о модели памяти, об используемом процессоре и проч. Далее идет макрос Uses он включает необходимую библиотеку в программу. В данном случае мы будем использовать kernel32.dll так как именно она содержит в себе используемую нами функцию завершения процесса ExitProcess.

Далее следует блок кода указанный с помощью .code, в котором находится основная процедура программы. По факту сама процедура может называться как угодно и название WinMain я дал ей просто от балды. Главное, чтобы в конце файла была строка End {Имя_функции_точки_входа}

Эта программа не несет никакой функциональной нагрузки, поэтому после запуска она ничего не будет делать — просто завершит свою работу. Теперь исходный код программы, приведенной в архиве:
include macros\macros.inc

IDC_MAINSTATUS   Equ 1

IDC_MENUEXIT     Equ 10

@Start
@Uses gdi32, user32, comctl32, kernel32

.xlist
include macros\Menu.mac
include macros\Window.mac
include macros\Status.mac
.list

.data?
hIcon           Dd ?
hBrush         Dd ?
hCursor       Dd ?
hImage        Dd ?
hInstance    Dd ?

@DefineMenu    Menu
@DefineStatus  Example
@DefineWindow  Example

.code
; Main program cycle
WinMain Proc

  mov    hInstance, @Result(GetModuleHandle, NULL)
  mov    hIcon, @Result(LoadIcon, hInstance, 100)
  mov    hCursor, @Result(LoadCursor,NULL,IDC_ARROW)
  mov    hBrush, @Result(GetSysColorBrush, COLOR_APPWORKSPACE)

  @CreateWindow  Example, hInstance, NULL,'Example_wnd', \
                 WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_VISIBLE, \
                 WS_EX_APPWINDOW, 'Example', \
                 hIcon, hBrush, hCursor, NULL

  @SetWndSize Example, 700, 600
  @MoveWnd Example, 100, 100

  @CreateMenu Menu
  @AppendMenu Menu, 'Exit', IDC_MENUEXIT
  @AttachMenu Example, Menu

  @CreateStatus Example, Example, IDC_MAINSTATUS
  @SetStatusParts Example, 2,300,-1,0,0,0,0,0,0,0,0
  @SetStatusText Example, 'Example program window...', 0, 0
  @SetStatusText Example, 'The CHEMI$T Copyright(C)2001', 0, 1

  @ProcessMsgs Example, FALSE

  @DestroyMenu Menu
  @DestroyWindow Example
  invoke ExitProcess, 0
WinMain Endp
        End WinMain


Теперь объясню поэтапно, что происходит в этом исходнике. Первое, включаются макросы реализации меню, статус-строки и оконного функционала. Они обрамлены спец командами макроассемблера .xlist (отключение листинга) и .list(включение листинга) это было сделано только для того, чтобы в случае выдачи листинга макроассемблером, не было кода из этих файлов /ибо только лишние полотенца кода/ Далее идет описание блока неинициализированных данных .data?, переменные в данном блоке не инициализируются, система просто выделяет память не обнуляя ее. Такие переменные без инициализации использовать чревато, ибо в памяти находится может что угодно. Здесь же выделяется место под переменные, которые в первых строках метода WinMain принимаю значения загруженных ресурсов и инстанса самого приложения.
Макросы @DefineMenu, @DefineStatus и @DefineWindow производят первоначальную инициализацию переменных, в которых будут хранится параметры объектов /меню, статус строки и окна соответственно/
И уже после всех инициализаций идет самое интересное.
Четыре первых строки
  mov    hInstance, @Result(GetModuleHandle, NULL)
  mov    hIcon, @Result(LoadIcon, hInstance, 100)
  mov    hCursor, @Result(LoadCursor,NULL,IDC_ARROW)
  mov    hBrush, @Result(GetSysColorBrush, COLOR_APPWORKSPACE)

Инициализируют переменные /инстанс приложения, иконка, курсор, кисть для отрисовки окна/. Здесь используется приятный макрос Result. Этот макрос выполняет указанный вызов API с переданными параметрами и возвращает содержимое регистра EAX, который служит для возврата результатов функции. Если бы не было данного макроса, то каждая строка разбивалась на подобный код :
  invoke GetModuleHandle, NULL
  mov     hInstance, eax

Макросы для создания и работы окна должны вызываться последовательно, @CreateWindow — создает окно, @SetWndSize — выставляет размер окна, @MoveWnd перемещает окно в нужные координаты на экране, @ProcessMsgs отрабатывает основной цикл обработки сообщений, идущих к вашему окну, @DestroyWindow — удаляет окно. Когда вы создаете окно, вам необходимо создать файл с обработчиками событий данного окна. В приведенном проекте это файл WndExample.Asm. Данное имя задано с тем, что файл обработчик событий включается автоматически по маске Wnd<Имя_окна>.Asm
Макросы для создания меню и для создания статус-строки я особо не доделывал тогда, сделал только до необходимого мне функционала.
Макросы работы с меню:
@CreateMenu {Имя_меню}
Создание меню с нужным именем

@AppendMenu {Имя_меню}, {Заголовок_пункта_меню}, {Код_сообщения}
Добавление пункта меню с нужным заголовком. По нажатии на данный пункт меню в очередь сообщений попадет код сообщений.

@AttachMenu {Имя_окна}, {Имя_меню}
Добавление меню к указанному окну.

Макросы для работы со статус строкой /Необходим ComCtl32/
@CreateStatus {Имя_статус_строки}, {Имя_окна}, {ID_статус-строки}
Создание статус строки у указанного окна

@SetStatusParts {Имя_статус_строки}, {Кол-во_частей}, {Ширина_части}, {}, {}, {}, {} /До десяти частей, последняя указывается размер=-1, т.е. растянуть/
Разделение на несколько частей, данный макрос можно было бы доработать, но как то видимо я этого тогда не сделал

@SetStatusText {Имя_статус_строки}, {Текст}, {Стиль /уже не помню для чего/}, {Часть_статус_строки}
Выставление статуса в нужную часть статус строки


Файл с обработчиками событий

В данном файле указывается исходный текст основной процедуры окна, в которой регистрируются пользовательские обработчики и в которой также должны быть зарегистрированы обработчики событий меню. Каждый обработчик события окна выглядит так:
@WndHandlerStart {Имя_окна}, {Имя_обработчика}

mov eax, TRUE
@WndHandlerEnd {Имя_окна}, {Имя_обработчика}

Основная процедура в приведенном проекте находится в конце файла и выглядит так:
@WndProcedureBegin Example, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT

  ; Menu handlers
  @WndMenuHandler  IDC_MENUEXIT, Exit

  ; Sample user handler
  @WndUserHandler  Example, WM_SIZING
@WndProcedureEnd   Example

Здесь назначается обработчик пункта меню, которому назначен Код_сообщения IDC_MENUEXIT обработчик с именем Exit. А также регистрируется пользовательский обработчик сообщения WM_SIZING. Пользовательский обработчик событий должен иметь имя сообщения, которое он обрабатывает. Все события, которые заранее прописаны в окне можно посмотреть в файле Window.Mac, в макросе @WndProcedureBegin. Список этих событий таков: Close, Paint, Help, Activate, Deactivate, SysCommand, Show, Hide, Create, Destroy, KeyDown, KeyUp, Resize, DblClick, MouseUp, MouseDown, WheelDown, WheelUp. Примеры данных обработчиков включены в исходник проекта и вы можете понажимать F1 в окне и покрутить колесо мыши.

В принципе все, что связано с этими событиями можно посмотреть в MSDN и исходниках, в этом нет ничего сложного и в данном описании я не буду включать.


Компиляция программы


Для компиляции необходим пакет masm32 (можно найти здесь) После установки желательно внести путь до каталога masm32\bin в переменную окружения Path и отредактировать файл masm32/bin/build.bat исправив вызов компилятора ml и линкера, чтобы добавить пути библиотек и включаемых файлов и не приходилось постоянно данные пути прописывать в коде.

Так в вызов ML.Exe нужно добавить еще один параметр /IF:\masm32\include — вместо F:\masm32 вам нужно указать путь, куда установился пакет masm32 у вас. А в два вызова линкера Link.exe нужно добавить путь к библиотекам с помощью параметра /LIBPATH:F:\masm32\lib. Опять же путь замените на тот, который соответствует вашему.

Далее, в каталоге с проектом даем две команды: bres (bres.bat производит компиляцию файла ресурсов rsrc.rc в текущем каталоге) и следом за ним build window (build.bat — производит компиляцию и линковку проекта).

Проекты переложил на GitHub
Tags:
Hubs:
+50
Comments 22
Comments Comments 22

Articles