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

    В далеком 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
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 22

    • НЛО прилетело и опубликовало эту надпись здесь
        +5
        Шутки-шутками, но лучший макроассемблер на практике получается из C++.
        Уже в нескольких проектах использовал ассемблер, написанный на C++ в виде кодогенерирующих функций типа

          void mov(Register dst32, int imm32)     { emit8(0xb8 + dst32); emit32(imm32); }
          void mov(Register dst32, Address src32) { emit8(0x8b); emit_addr(src32, dst32); }
          void push(Register dst32)               { emit8(0x50 + dst32); }
          void call(Address dst32)                { emit8(0xff); emit_addr(dst32, 2); }
          void call(Label target)                 { emit8(0xe8); emit_label(rel32); }
          void build_frame(int size)              { push(epb); sub(esp, size); }
          и т.д.


        В результате код на таком «ассемблере» выглядит примерно так:

          build_frame(32);
          Register osr_buf = eax;
          for (int i = 0; i < num_locks; i++) {
            int slot_offset = monitor_offset - i * 8;
            cmp(Address(osr_buf, slot_offset + 4), 0);
            jcc(eq, done);
            mov(ebx, Address(osr_buf, slot_offset));
            mov(address_of_lock(i), ebx);
          }
          bind(done);
          0
          А почему не на C или на чистом ASM (__asm{...})?
          Что полезного дает такой подход?
            +1
            Видно же что можно скажем в цикле генерировать код (развернутый цикл, и тп). Это DSL для генерации кода, не непосредственно сам код.
              0
              О! Да это дает потрясающие средства генерации машинного кода с использованием всех прелестей объектно-ориентированного подхода. Пожалуй, стоит отдельную статейку на эту тему написать. В рамки одного комментария я точно не уложусь :)
              Вкратце: динамическая кодогенерация, использование особенностей аппаратной платформы, независимость от синтаксиса встроенного ассемблера, будь то GCC, MSVC или что еще, тесная интеграция с кодом на C/C++.
                0
                Не заметил, что это все кодогенерация, подумал, что это просто обычный цикл :)
              0
              Примерно так же выглядит JIT-компилятор HotSpot для Java.
            +1
            Последний раз в институте писал на masm32, причем как раз под винду. Действительно очень похоже на C получается. Кстати, в качестве IDE могу посоветовать RadASM — довольно удобная штука.
              0
              Да, masm хорошая вещь. Но радасм мне не нравится… По факту я пишу в фаре, а вот IDE лучше winasm studio. Замечательная, быстроразвивающаяся и очень удобная.
              +2
              читал похожие статьи на wasm.ru лет 7 назад когда писал на асме под Win32 :) а именно туториалы от Iczelion wasm.ru/publist.php?list=1
                +2
                Да, в свое время тоже много читал. Iczelion был практически первым, что я читал по этому делу.
                  +2
                  Да-да-да, книжка (в формате chm по-моему) от Iczelion до сих пор лежит в документации у меня на винте :)
                    0
                    Там все практически без макросов, чистый ассемблер. Некоторые вещи просто классные.
                0
                А какой смысл использовать устаревшего, в общем-то, монстра MASM — когда есть мощный, простой, и удобный fasm?
                  0
                  в принципе масм развивается и с каждой версией визуал студио приходит новая версия. Но в данном случае, посмотрите первую строчку «В далеком 2001-ом году я проводил»… Последнее время я редко занимаюсь ассемблером, только как хобби. Но фасм… Не знаю, на любителя.
                  0
                  Любите ассемблер? Велкам на микроконтроллеры. Там и ограничение ресурсов и низкое быстродействие. И оптимизация всего и вся. А для PC даже как хобби не актуально уже :) ИМХО.
                    0
                    Ну уж в крайности то кидаться не надо? Может тогда микрокалькуляторы программируемые? Там вообще от 98 до 105 шагов программы. Микроконтроллеры не так интересны, как хотелось бы. А вот по винду вполне себе интересно покопаться во внутренностях, чтобы понимать, как что устроено. Например была у меня программа, переделка одного раздела из статьи Рихтера по перехвату апи внутри программы… Вот лежит, думаю тоже сюда выложить. С Си переделал на ассемблер + полезная информация о строении исполняемых файлов + интересная отладка )
                      +1
                      Почему в крайности? Очень перспективная и быстро развивающаяся ниша. И ковыряться в них и отлаживать не менее интересно, а с практической точки зрения так куда более полезно.
                        0
                        Может быть… Последний раз я сталкивался с микроконтроллерами лет 7-8 назад. Когда в принципе и прекратил свои изыски в ассемблере. Сейчас асп.нет + джаваскрипт.
                      0
                      Да что вы говорите? А ну ка все забудут ассемблер, дядя Вася будет компиляторы писать?
                        0
                        Такое не забывается. Ассемблер вне времени )
                          0
                          Совершенно согласен! И тем более, это до сих пор машинный код! :)

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое