Сказки дядюшки Римуса о x64 Что бы не было вопросов "для чего всё это?", "При чём здесь братец Кролик и ассемблер?" Считайте этот цикл пародией, которую, как бы, написал Джоэль Чандлер Харрис на "Уроки Iczelion'а", переписанные под Win x64. Рисунки из фильма "Песня Юга" (англ. "Song of the South" 1946) любезно предоставлены Глава первая. Как Братец Кролик достал пакет MASM64 Глава вторая. Прототип нашей IDE Глава третья. Как Братец Кролик уменьшал размер программы Глава четвертая. Как Братец Кролик создал универсальный bat-файл Глава пятая. Братец Кролик разбирается с потрохами MessageBox Глава шестая. Братец Кролик выводит MessageBox всеми возможными способами Глава седьмая. Как Братец Кролик создал простейшее окно Глава восьмая. Как Братец Кролик выводил текст на экран при помощи функции DrawText Глава девятая. Как Братец Кролик создал «генератор окошек» Глава десятая. Вращающийся текст Глава одиннадцатая. Как Братец Кролик выводил текст на экран всеми возможными способами Глава двенадцатая. Как Братец Кролик осваивал мышь Глава тринадцатая. Братец Кролик продолжает осваивать мышь Глава четырнадцатая. «Липкая» надпись Глава пятнадцатая. Братец Кролик изучает иконки и курсоры Глава шестнадцатая. Братец Кролик изучает меню Глава семнадцатая. Братец Кролик создает меню через функцию LoadMenu Глава восемнадцатая. Программное создание меню Глава девятнадцатая. Создание динамического меню Глава двадцатая. «Плавающее» меню Глава двадцать первая. Создание меню через Template-структуру Глава двадцать вторая. Работа с таблицей акселераторов Глава двадцать третья. Работа с инструментальной панелью (toolbar) Глава двадцать четвертая. Всплывающие подсказки Глава двадцать пятая. Инструментальная панель Глава двадцать шестая. Братец Кролик подключает стандартные диалоги к инструментальной панели Глава двадцать седьмая. Пример использования диалога «Открыть файл» Глава двадцать восьмая Часть первая. Братец Кролик выводит картинки на экран Часть вторая. Братец Кролик продолжает выводить картинки на экран Глава двадцать девятая. Братец Кролик создает дочерние окна Глава тридцатая. Братец Кролик и диалоги Глава тридцать первая. Братец Кролик разбирается с памятью и файлами Глава тридцать вторая. Братец Кролик и файлы проецируемые в память Глава тридцать третья. Братец Кролик изучает процессы Глава тридцать четвертая. Братец Кролик и треды (ветви) Глава тридцать пятая. Братец Кролик и объект события Глава тридцать шестая. Братец Кролик и динамические библиотеки Глава тридцать седьмая. Братец Кролик и элементы управления Глава тридцать восьмая. Братец Кролик и окно просмотра деревьев Глава тридцать девятая. Братец Кролик изучает сабклассинг окна Глава сороковая. Братец Кролик и пайп Глава сорок первая. Братец Кролик и суперклассинг Глава сорок вторая. Братец Кролик и иконки в system tray Глава сорок третья. Братец Кролик и Windows-хуки Глава сорок четвертая. Братец Кролик и сплэш-экран Глава сорок пятая. Братец Кролик и подсказки (toolip control) Глава сорок шестая. Братец Кролик и Контрол Listview Глава сорок седьмая. Братец Кролик создает MDI-приложение Глава сорок восьмая. Братец Кролик и немного БСДМ MSDN'а Глава сорок девятая. Память Братца Кролика Глава пятидесятая. Братец Кролик подключает к MessageBox'у кнопку «Help» Глава пятьдесят первая. Братец Кролик узнает о тексте еще больше Глава пятьдесят вторая. Братец Кролик и просмотр видео-файлов Глава пятьдесят третья. Видео-плеер Братца Кролика
Глава первая. Как Братец Кролик достал пакет MASM64 (или, как создать IDE из говна и палок) Братец КроликБратец СиБратец ДельфиВ те далекие времена в южных штатах 64-разрядный ассемблерный компилятор и линкер не распространялись отдельно, но ml64.exe и link.exe Братец Кролик получил бесплатно, потому как, прочитал и дал клятву соблюдать "Лицензионное соглашение" (), после чего (в образовательных целях) скачал комплект C++ компиляторов от от Матушки Виндоуз. Братцу Кролику потребовалось сходить по одной из двух ссылок: либо Windows Driver Kits Version 7.1.0 (WDK) либо MICROSOFT WINDOWS SOFTWARE DEVELOPMENT KIT FOR WINDOWS 7 and .NET FRAMEWORK 4 (SDK) После установки WDK Братец Кролик нашел ml64.exe и link.exe в папке C:\WinDDK\7600.16385.1\bin\x86\amd\ ― Что-то слишком просто у меня получилось ― сказал Братец Кролик и установил на всякий случай еще и SDK. В SDK ml64.exe и link.exe содержались в папках C:\Program Files\Microsoft Visual Studio\2022\Preview\VC \Tools\MSVC\14.30.30401\bin\Hostx64\x64\ Тут Братец Кролик вспомнил, ЧТО ему говорил Братец Опоссум ― Обычно используют 64-битный link из папки amd64, а ml64 уже 32-битный из папки x86_amd64. В папке amd64 есть 64-битная версия ml64, но при работе с большим количеством макросов он у Братца Опоссума почему-то зависал или просто медленно работал. В конце концов выбор остается за тобой, Братец Кролик... Братец Кролик установил Microsoft Visual Studio 22.0 Затем Братец Кролик создал каталог, который назвал masm64, а в этом каталоге создал подкаталоги bin, include, lib, samples Из интернета Братец Кролик скачал файл hiew32.exe и разместил его в masm64\bin В Program Files\Microsoft Visual Studio\2022\ Preview\VC\ Tools\ MSVC\ 14.30.30401\bin\HostX64\x64 Братец Кролик нашел файлы ml64.exe link.exe link.exe.config cvtres.exe dumpbin.exe lib.exe и скопировал их в папку masm64\bin А в папке c:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64 Братец Кролик нашел файлы rc.exe и rcdll.dll которые также поместил в masm64\bin Чтобы ml64 и link заработали на другом компьютере, где Microsoft Visual Studio 22.0 еще не установлена, понадобится целая куча вспомогательных dll'ок которые находятся в папке Windows\System32 Братец Кролик набрал в командной строке d:\masm64\bin\hiew32 ml64.exe Нажал на клавишу F4 (mode) → Decode, затем на клавишу F8 (header), затем на F7 (import), чтобы посмотреть какие функции импортируются из других файлов и наконец на F7 (DLLs), чтобы получить список dll, используемых в файле Сперва Братцу Кролику пришлось заполнить вот такую таблицу вспомогательная dllисполь- зуетсянаходится в каталогеapi-ms-win-crt-conio-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-convert-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-environment-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-filesystem-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-heap-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-locale-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-math-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-process-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-runtime-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-stdio-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-string-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-time-l1-1-0.dllml64.exe link.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64api-ms-win-crt-utility-l1-1-0.dlllink.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64msvcp140.dlllink.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64vcruntime140.dllml64.exeProgram Files\Microsoft Visual Studio\2022\ Preview\Common7\IDE\Remote Debugger\x64mscoree.dlllink.exeWindows\system32advapi32.dllml64.exe link.exeWindows\system32kernel32.dllml64.exe link.exeWindows\system32imagehlp.dlllink.exeWindows\system32mspdbcore.dlllink.exeProgram Files\Microsoft Visual Studio\2022\ Preview\VC\bin\amd64msvcdis140.dlllink.exeProgram Files\Microsoft Visual Studio\2022\ Preview\VC\bin\amd64pgodb140.dlllink.exeProgram Files\Microsoft Visual Studio\2022\ Preview\VC\bin\amd64 Затем Братец Кролик скопировал файлы api*.dll, а также msvcp140.dll и vcruntime140.dll из папки Program Files\Microsoft Visual Studio\2022\ Preview\BuildTools\Common7\IDE\Remote Debugger\x64 а также файлы mspdbcore.dll, msvcdis140.dll, pgodb140.dll из папки Program Files\Microsoft Visual Studio\2022\ Preview\BuildTools\VC\bin\amd64 в папку masm64\bin После Братец Кролик отправился на сайт dsmhelp.narod.ru где, как он слышал, можно найти набор lib- и inc-файлов для создания 64-разрядных приложений на ассемблере, а также кучу полезных макросов Как Братец Кролик создал первую программуВ папке bin Братец Кролик при помощи программы Notepad.exe ( ) создал файл asm.bat Код (DOS): :: очистка экрана cls :: имя asm-файла без расширения set filename=%~n1 :: здесь путь к папке masm64, у Вас он может быть другим set masm64_path=\masm64\ :: если существует exe-файл с таким же именем ― удаляем его if exist %filename%.exe del %filename%.exe :: компиляция :: если во время компиляции будут ошибки, тогда они будут перечислены в файле errors.txt %masm64_path%bin\ml64 /c %filename%.asm >> errors.txt :: обнаружены ошибки компиляции ― выходим if errorlevel 1 exit :: линковка :: если во время линковки будут ошибки ― тогда они будут перечислены в файле errors.txt %masm64_path%bin\link /SUBSYSTEM:WINDOWS /entry:WinMain %filename%.obj >> errors.txt :: обнаружены ошибки линковки ― выходим if errorlevel 1 exit :: раз мы здесь ― значит ошибок нет ― удаляем "программный мусор" del %filename%.obj del errors.txt Братец Кролик пишет программу на ассемблере«скелет» ассемблерного файла для x64 Код (ASM): ; служебная информация для компиляции и линковки .data ; здесь определены данные для программы .code WinMain proc ; здесь определен текст программы WinMain endp end Братец Кролик заметил, что в отличии от ассемблерного файла для 16-/32-разрядных программ в тексте отсутствует упоминание модели и типа CPU "вход" в программу определяется не по последней строчке в asm-файле "end <метка>", а по опции в bat-файле "/entry:<метка>" Текст первой программы Братца КроликаБратец Кролик набрал в блокноте () текст программы и сохранил ее с именем tut_02.asm в каталоге samples Код (ASM): OPTION DOTNAME ; разрешить использовать точку в имени переменной option casemap:none ; различать в именах Строчные и прописные буквы include \masm64\include\temphls.inc ; в этом файле описание invoke и другие высокоуровневые инструкции include \masm64\include\win64.inc ; здесь описаны константы NULL и MB_OK include \masm64\include\kernel32.inc ; здесь описаны функции из библиотеки kernel32.dll includelib \masm64\lib\kernel32.lib include \masm64\include\user32.inc ; здесь описаны функции из библиотеки userl32.dll includelib \masm64\lib\user32.lib OPTION PROLOGUE:none ; пролог функций пишем сами OPTION EPILOGUE:none ; эпилог функций пишем сами .data ; данные MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 MsgBoxText db "Win64 Assembly is Great!",0 .code ; код программы WinMain proc sub rsp,28h invoke MessageBox, NULL, &MsgBoxText, &MsgCaption, MB_OK invoke ExitProcess,NULL WinMain endp end Братец Кролик создает ассоциации ассемблерных файлов с файлом asm.bat Братец Кролик щелкнул по значку файла tut_02.asm выбрал пункт "Открыть с помощью", нашел в папке \masm64\bin файл asm.bat и выбрал его Братец Кролик кликнул по файлу tut_02.asm и получил файл tut_02.exe Дзен и Братец Кролик Братец Кролик начал медитировать и на него снизошло просветление ― Братец Кролик понял, что тоже самое можно сделать более дзенно. Братец Кролик нажал на кнопку "Пуск"→"Выполнить"→и запустил программу regedit в ветке HKEY_CLASSES_ROOT→Application Братец Кролик создал раздел asm.bat→ shell→ open→ command в ветке HKEY_CURRENT_USER→ Software→ Microsoft→ Windows→ CurrentVersion→ Explorer→ FileExts он создал раздел .asm © Mikl___ 2021
Глава третья. Как Братец Кролик уменьшал размер программы Вот пообедали Братец Кролик, и Матушка Крольчиха, и все ребятки, и никто им не мешал. А потом приходит Братец Дельфи и говорит: ― Братец Кролик, уж очень большого размера получилась у тебя программа! Ну прям, как у меня...1 Взял Братец Кролик текст своей первой программы и стал ее разглядывать Код (ASM): OPTION DOTNAME ; разрешить использовать точку в имени переменной option casemap:none ; различать в именах Строчные и прописные буквы include \masm64\include\temphls.inc ; в этом файле описание invoke и другие высокоуровневые инструкции include \masm64\include\win64.inc ; здесь описаны константы NULL и MB_OK include \masm64\include\kernel32.inc ; здесь описаны функции из библиотеки kernel32.dll includelib \masm64\lib\kernel32.lib include \masm64\include\user32.inc ; здесь описаны функции из библиотеки userl32.dll includelib \masm64\lib\user32.lib OPTION PROLOGUE:none ; пролог функций пишем сами OPTION EPILOGUE:none ; эпилог функций пишем сами .data ; данные MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 MsgBoxText db "Win64 Assembly is Great!",0 .code ; код программы WinMain proc sub rsp,28h invoke MessageBox, NULL, &MsgBoxText, &MsgCaption, MB_OK invoke ExitProcess,NULL WinMain endp end Собирал Братец Кролик свою программу при помощи bat-файла со следующим содержанием Код (DOS): :: очистить экран cls :: здесь можно указать какой-нибудь другой путь к пакету masm64 set masm64_path=\masm64\ :: здесь будет название asm-файла set filename=%~n1 %masm64_path%bin\ml64 /c /Cp %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS ^ /entry:WinMain %filename%.obj :: удалить программный "мусор" del %filename%.obj Обратите внимание на символ «^». Одним из условий удобочитаемости текста программы является его ширина: все строки должны умещаться на экране ― это примерная ширина текста в 80 символов. А при написании bat-файла основное требование ― запись команды в одну строку. Использование символа «^» позволяет устранить это противоречие. Символ «^» экранирует конец строки ― это позволяет разместить содержимое длинной строки на экране в нескольких строках, а система, как и прежде, будет считать их одной длинной строкой. В результате компиляции и линковки получил Братец Кролик программу tut_02.ехе размером в 2560 байт. Стал Братец Кролик рассматривать внутренностей ехе-файла при помощи программы hiew32, которую ему подарил Братец Ёж. И увидел Братец Кролик, что 90% содержимого программы заполнено нулями. Возник в голове Братца Кролика вопрос, а как уменьшить размер программы, но чтобы при этом не терялась ее функциональность?Шаг первый ― Братец Кролик уменьшает размер выравнивания сегментовУ программы link.exe есть ключ /ALIGN:размер Ключ /ALIGN[MENT] указывает компоновщику на необходимость выравнивания сегментов в исполняемом файле на границу, кратную значению размер. Размер ― это число равное двум возведенное в целую степень (20=1, 21=2, 22=4, 23=8, ... до 216=65536 включительно). Если об этом ключе не упоминать, то выравнивание равно 29=512 байт. Посмотреть значение выравнивания можно при помощи редактора бинарных файлов hiew32 в заголовке программы в поле «File alignment» (Открываем наш exe программой hiew32, нажимаем F4 (Mode) и выбираем Hex, нажимаем F8 (Header) в поле «File alignment» видим число 20016 = 51210). Кроме ключа /ALIGN Братец Кролик добавил в файл asm.bat еще некоторые ключи компиляции и линковки, которые позволили уменьшить количество служебной информации в asm-файлах, но привели к увеличению размера bat-файла (кого-то когда-нибудь волновал размер bat-файла? ) Добавление ключа компиляции /Cp ― «сохранить регистр символов во всех идентификаторах» позволяет не писать каждый раз в тексте ассемблерного файла «option casemap:none». Добавление ключа компиляции /I:путь ― «установить путь для включаемых файлов» позволяет нам не указывать каждый раз полный путь к inc-файлам. Ключ /LIBPATH:путь говорит линкеру, где находятся библиотеки импорта, что позволяет нам не указывать каждый раз полный путь к lib-файлам. Таким было содержимое файла asm.bat до изменений: Код (DOS): cls set masm64_path=\masm64\ set filename=%~n1 %masm64_path%bin\ml64 /c /Cp %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS ^ /entry:WinMain %filename%.obj del %filename%.obj а таким стало после изменений: Код (DOS): cls set masm64_path=\masm64\ set filename=%~n1 %masm64_path%bin\ml64 /Cp /c /I"%masm64_path%Include" %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%Lib" ^ /ALIGN:256 /entry:WinMain %filename%.obj del %filename%.obj А таким стало содержимое файла tut_02.asm после изменений Код (ASM): OPTION DOTNAME include temphls.inc include win64.inc include kernel32.inc includelib kernel32.lib include user32.inc includelib user32.lib OPTION PROLOGUE:none OPTION EPILOGUE:none .data MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 MsgBoxText db "Win64 Assembly is Great!",0 .code WinMain proc sub rsp,28h invoke MessageBox, NULL, &MsgBoxText, &MsgCaption, MB_OK invoke ExitProcess,NULL WinMain endp end при компиляции Братец Кролик получил сообщение LINK : warning LNK4108: /ALIGN specified without /DRIVER; image may not run warning означает "предупреждение" поэтому Братец Кролик не испугался, а продолжил эксперимент ― размер файла tut_02.exe изменился с 2560 до 1536 байт и, не смотря, на глупое предупреждение компилятора image may not run файл tut_02.exe переделанный Братцем Кроликом благополучно запустился. Братец Кролик педантично изменял значение /ALIGN:размер, наблюдал за уменьшением размера файла tut_02.exe, а результаты записывал в таблицу /ALIGN:размерзаголовоккодданныеимпортобщий размер tut_02.exeпри создании файла ключ /ALIGN:размер не указан10245125125122560 байтразмер = 2567682562562561536 байтразмер = 1286401281282561152 байтразмер = 645766464192896 байтразмер = 325766464192896 байтразмер = 165764864192880 байтразмер = 8――――ошибка при создании файлаtut_02.obj : fatal error LNK1164: section 0x1 alignment (16) greater then /ALIGN valueкогда выравнивание сегментов стало кратным 16 байтам, Братец Кролик понял, что в этом направлении он достиг предела и от исходной программы в 2560 байт пришел к программе размером в 880 байт. Братец Кролик сказал себе ― «Для начала и это уже не плохо!» 1 дельфи в самом худшем смысле, когда компилятор туп настолько, что по бреду, который он генерирует, его можно безошибочно узнать в листинге (© f13nd)
Шаг второй ― Братец Кролик объединяет сегмент кода и сегмент данныхПрограмма Братца Кролика использует два сегмента, сегмент кода и сегмент данных. Посмотрел Братец Кролик внимательно на свою программу через hiew32 ― и обратил внимание на прослойку из нулей между этими сегментами, а цель-то у Братца Кролика была избавится от лишних нулей. Вспомнил Братец Кролик, что во времена дядюшки DOS'а можно было создавать COM-файлы, которые в единственном сегменте содержали и код, и стек, и данные. А нельзя ли, подумал Братец Кролик, и здесь создать, что-то подобное? Посидел Братец Кролик в Интернете и выяснил, что оказывается можно ― при помощи ключа /SECTION можно присвоить секции кода с атрибутами «выполнять» и «читать» атрибуты секции данных ― «читать» и «писать». Чтобы поменьше писать в asm-файлах служебной информации Братец Кролик в папке include создал файл win64a.inc со следующим содержимым Код (ASM): OPTION DOTNAME include temphls.inc include win64.inc include kernel32.inc includelib kernel32.lib include user32.inc includelib user32.lib OPTION PROLOGUE:none OPTION EPILOGUE:none Внес Братец Кролик изменения в текст программы Код (ASM): include win64a.inc .code MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 MsgBoxText db "Win64 Assembly is Great!",0 WinMain proc sub rsp,28h invoke MessageBox, NULL, &MsgBoxText, &MsgCaption, MB_OK invoke ExitProcess,NULL WinMain endp end и изменения в файл asm.bat Код (DOS): cls set masm64_path=\masm64\ set filename=%~n1 %masm64_path%bin\ml64 /Cp /c /I"%masm64_path%Include" %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%Lib" ^ /SECTION:.text,W /ALIGN:16 /entry:WinMain %filename%.obj del %filename%.obj Опция линкера /SECTIONОпция командной строки компоновщика link.exe /SECTION:name,[[!]{DEKPRSW}][,ALIGN=#] позволяет принудительно назначать атрибуты секциям PE-файла. Для секции можно задать один или несколько атрибутов. Следует задавать все атрибуты, которые должна иметь секция; если какой-либо знак атрибута не указан, то его бит будет отключен. Если не указан атрибут R, W или E, то существующее состояние чтения, записи или исполнения остается неизмененным. Чтобы инвертировать атрибут, перед его символом указывают знак «!». С помощью параметра ALIGN=# можно задать значение выравнивания для конкретной секции. Значения знаков атрибутов приведены в следующей таблице. букваатрибутзначениепереводDDiscardableMarks the section as discardableСекция помечается как выгружаемаяEExecuteThe section is executableСекция является выполняемойKCacheableMarks the section as not cacheableСекция помечается как некэшируемаяPPageableMarks the section as not pageableСекция помечается как секция без страничной организацииRReadAllows read operations on dataДопускаются операции чтения данныхSSharedShares the section among all processes that load the imageСекция совместно используется всеми процессами, загружающими образWWriteAllows write operations on dataДопускаются операции записи данныхВ данном случае секции с именем «.text» (содержащей код программы) и уже имеющей атрибуты R (доступна для чтения) и E (исполнимая), устанавливается атрибут W (доступна для записи) /SECTION:.text,WВ результате Братец Кролик получил tut_02.exe в 848 байт заголовоккод и данныеимпортобщий размер544112192848 байтШаг третий ― Братец Кролик занимается алгоритмической оптимизациейБратец Кролик наблюдал за работой своего приложения через программу-дебаггер x64dbg и заметил, что при старте приложения на вершине стека лежит адрес функции kernel32.RtlExitUserThread, а именно ее вызывает функция ExitProcess для завершения приложения. Поэтому у Братца Кролика возник вопрос, а необходим ли в этой программе вызов функции ExitProcess? Братец Кролик заменил вызов функции ExitProcess на команду retn, которая извлекла из стека адрес функции RtlExitUserThread и передала управление программой этой функции. Код (ASM): include win64a.inc .code MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 MsgBoxText db "Win64 Assembly is Great!",0 ;------------------------------------------------ WinMain proc sub rsp,28h invoke MessageBox, NULL, &MsgBoxText, &MsgCaption, MB_OK add rsp,28h retn WinMain endp end заголовоккод и данныеимпортобщий размер544112112768 байтШаг четвертый ― «хирургический» ― Братец Кролик ампутирует у файла «хвост»Просматривал Братец Кролик свою программу через hiew32, просматривал и заметил, что в «хвосте» файла tut_02.exe, сразу за строкой «user32.dll» целых шестнадцать байт содержат нули. А не удалить ли эти нули вручную? ― подумал Братец Кролик. Сказано ― сделано! Запустил Братец Кролик файловый менеджер far, нажал на F4 (открыл файл на редактирование), нажал на Ctrl+End и перешел в конец файла tut_02.exe. Потом Братец Кролик нажимал на Backspace, удалял лишние байты, затем нажал на F2 и сохранил изменения в файле, нажал на F10 и вышел из файла ― размер tut_02.exe стал 748 байт. Нажал Братец Кролик на него и запустил tut_02.exe ― запускается нормально! Стоп-стоп! 768-748=20 байт, а должно быть 16! Посмотрел Братец Кролик на свою программу через hiew32 ― и, о, боже!, оказалось, что он отрезал вместе с нулями от «user32.dll» кусок «.dll», но файл-то всё равно работал! Запомнил Братец Кролик на будущее, что от DLL достаточно только названия, а точку и расширение файла система подставит сама. заголовоккод и данныеимпортобщий размер54411292748 байтШаг пятый ― Братец Кролик снова занялся алгоритмической оптимизациейКак устроен вызов функции в Win64? Сначала формируется стековый кадр (stack frame), то есть, область памяти в стеке, через которую передаются аргументы и другие "опции", затем устанавливаются аргументы и вызывается инструкция call. Win64 ABI требует, чтобы стековый кадр был выравнен на 16 байт. Это ключевой момент, от которого следует отталкиваться во всех последующих рассуждениях. При входе в любую функцию значение регистра RSP не кратно 16, оно смещено на 8 байт адресом возврата, который "положила" в стек инструкция call. Если поставить точку останова на любую 64-битную функцию, то при входе в нее можно увидеть, что значение RSP всегда заканчивается на 8. Вот поэтому пролог функции в Win64 делает что-нибудь в стиле "sub rsp, 28h" ― это и есть выравнивание стека на 16. Именно так, как пишут авторы многочисленных книг, то есть, до вызова других функций и до формирования стековых кадров для них. Но это еще не все. Все тот же Win64 ABI требует, чтобы на время вызова в стеке было зарезервировано 32 байта под нужды вызываемой функции. Эта область в разных источниках называется по-разному: "register space", "home area" и так далее. Предполагалось, что в этой памяти вызываемая функция сможет временно хранить значения регистров RCX, RDX, R8 и R9. Но нужен ли "home area" для функции MessageBox? Заменил Братец Кролик sub rsp,28h на sub rsp,18h и add rsp,28h на add rsp,18h (Братец Кролик соблюдал условие кратности 16 содержимого RSP), компилировал, линковал, запустил на выполнение. Программа отработала и завершилась нормально. Это вдохновило Братца Кролика и он снова заменил sub rsp,18h на sub rsp,8 и add rsp,18h на add rsp,8 Опять Братец Кролик компилирует, линкует, запускает на выполнение. Программа и на этот раз отработала и нормально завершилась. Хорошо! ― сказал Братец Кролик, почесал лоб и вспомнил, что эквивалентом sub rsp,8 является команда push reg, какой из регистров при этом будет использовать не важно, главное, что размер команды всего один байт! Эквивалент add rsp,8 ― команда pop reg. Внес Братец Кролик очередные изменения в программу, откомпилировал, слинковал, запустил программу на выполнение. Программа отработала и нормально завершилась. заголовоккод и данныеимпортобщий размер54496112752 байт
Шаг шестой ― Братец Кролик уменьшает DOS-stubКод программы Братца Кролика начинался с 0x220=544-го байта. Всё, что выше ― это заголовок EXE-файла ― вот бы его уменьшить! ― решил Братец Кролик. Заголовок файла состоит из двух частей. От строки «MZ» до строки «PE» DOS-заголовок (размер 0xC8-0=200 байт) и от строки «PE» до 0x220=544-го байта PE-заголовок (размер 0x220-0xC8=344 байт). Адрес строки «PE» содержится в DOS-заголовке в двойном слове по смещению 0x3C. Братец Кролик начал с уменьшения DOS-стаба. Взял hiew32.exe и создал с его помощью вот такой файл Братец Кролик присвоил ему имя stubby.exe, сохранил файл в папке bin и дальше использовал этой файл в качестве stub-программы. В очередной раз Братец Кролик внес изменения в файл asm.bat. Код (DOS): cls set masm64_path=\masm64\ set filename=%~n1 %masm64_path%bin\ml64 /Cp /c /I"%masm64_path%Include" %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%Lib" ^ /STUB:%masm64_path%bin\stubby.exe /SECTION:.text,W /ALIGN:16 ^ /entry:WinMain %filename%.obj del %filename%.obj заголовоккод и данныеимпортобщий размер48096112688 байтШаг седьмой ― Братец Кролик использует ключи /LARGEADDRESSAWARE, /BASE:адрес и 32-разрядную адресациюПриложение, работающее в Win64 может работать с адресами размер которых свыше 2 гигабайта. В 64-разрядных компиляторах для этого используется параметр /LARGEADDRESSAWARE В 32-разрядных компиляторах включен параметр /LARGEADDRESSAWARE:NO Параметр /BASE задает базовый адрес программы, переопределяя заданное по умолчанию расположение EXE-файла (для 32-разрядных Win = 0x400000, для 64-разрядных 0x140000000) или DLL-файла (для 32-разрядных = 0x10000000). Сначала операционная система пытается загрузить программу по указанному или используемому по умолчанию базовому адресу. Если по этому адресу недостаточно места, то система перемещает программу в памяти. Для предотвращения перемещения используется параметр /FIXED. Если указанный адрес не кратен 64 Кб, компоновщик выдает ошибку. При необходимости можно также указать размер программы, чтобы компоновщик мог выдать предупреждение в случае, если программа не помещается в заданные таким образом пределы. Внес Братец Кролик изменения в файл asm.bat Код (DOS): cls set masm64_path=\masm64\ set filename=%~n1 if exist %filename%.exe del %filename%.exe if exist %filename%.obj del %filename%.obj %masm64_path%bin\ml64 /Cp /c /I"%masm64_path%Include" %filename%.asm %masm64_path%bin\link /SUBSYSTEM:WINDOWS /LIBPATH:"%masm64_path%Lib" ^ /LARGEADDRESSAWARE:NO /BASE:0x400000 /STUB:%masm64_path%bin\stubby.exe ^ /SECTION:.text,W /ALIGN:16 /entry:WinMain %filename%.obj Братец Кролик в очередной раз внес изменения в asm-файл Код (ASM): include win64.inc .code MsgBoxText db "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 WinMain proc push rbp mov edx,offset MsgBoxText mov r8d,offset MsgCaption invoke MessageBox,NULL,,,MB_OK pop rbp retn WinMain endp end заголовоккод и данныеимпортобщий размер48096112688 байтВнес Братец Кролик изменения в asm-файл Код (ASM): include win64a.inc .code MsgBoxText db "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 WinMain proc push rbp mov edx,offset MsgBoxText lea r8d,[rdx+sizeof MsgBoxText];r8=offset MsgCaption invoke MessageBox,NULL,,,MB_OK pop rbp retn WinMain endp end заголовоккод и данныеимпортобщий размер48096112688 байтШаг восьмой ― Братец Кролик сокращает размеры заголовка и импортаТак как код программы и данные нам сократить не удастся ― поэтому сократим размеры заголовка и импорта PE-файла. Нам потребуется создать bin-файл, заголовок которого мы напишем самостоятельно. Для этого требуются ассемблеры, которые могут создавать бинарные файлы (NASM, FASM). За основу бинарного файла возьмем содержимое предыдущего tut_02.exe размером 688 байта Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 0x400000 align1 equ 0x10 use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0x3A db 0 NewExe_offset dd ntHeader times 0x48 db 0 ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw 8000h;IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd 16 export_RVA_size dq 0 import_RVA_size dd _import,user32_table2-_import;_import,0x3C;end_import-import resurce dd 0,0 exception dd 0,0 security dd 0,0 fixups_ dd 0,0 debug dd 0,0 description dd 0,0 MIPS_GP dd 0,0 TLS dd 0,0 Load_config dd 0,0 Bound_import dd 0,0 import_table1 dd Import_Table,_import-Import_Table;0x10 delay_import dd 0,0 com_runtime dd 0,0 reserved dd 0,0 ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin;0x57 .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE;0x80000020 ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd a1-Import_Table;0x62 .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA;0x40000040 ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn a0: times (($ - begin) mod align1) db 0 ;выравнивание сегмента кода и данных до значения равного align1 ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd user32_table2,0,0,user32_dll,user32_table dd 0,0,0,0,0 user32_table2: dq _MessageBox,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32.dll",0 a1: times (($ - Import_Table) mod align1) db 0 end_import: заголовоккод и данныеимпортобщий размер48096112688 байт
сократим DOS-stub до 64 байт, уменьшаем таблицу разделов, по умолчанию количество разделов должно быть равным 16, нам же требуется только секция импорта. В таблице разделов секция импорта идет сразу после секции экспорта. Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 0x400000 align1 equ 0x10 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0x3A db 0 NewExe_offset dd ntHeader ;times 0x48 db 0 ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (section_table-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,user32_table2-_import;_import,0x3C;end_import-import ;resurce dd 0,0 ;exception dd 0,0 ;security dd 0,0 ;fixups_ dd 0,0 ;debug dd 0,0 ;description dd 0,0 ;MIPS_GP dd 0,0 ;TLS dd 0,0 ;Load_config dd 0,0 ;Bound_import dd 0,0 ;import_table1 dd Import_Table,_import-Import_Table ;delay_import dd 0,0 ;com_runtime dd 0,0 ;reserved dd 0,0 ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin;0x57 .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE;0x80000020 ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd a1-Import_Table;0x62 .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA;0x40000040 ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn a0: times (($ - begin) mod align1) db 0 ;выравнивание сегмента кода и данных до значения равного align1 ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd user32_table2,0,0,user32_dll,user32_table dd 0,0,0,0,0 user32_table2: dq _MessageBox,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32.dll",0 a1: times (($ - Import_Table) mod align1) db 0 end_import: заголовоккод и данныеимпортобщий размер29696112504 байтЧто можно сократить в секции импорта? Секция импорта содержит две одинаковые таблицы (Import LookUp Table и Import Address Table), содержащие ссылки на названия импортируемых функций. Можно ли обойтись только таблицей Import Address Table? Удаляем Import LookUp Table и обнуляем ссылки на user32_table2 Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 400000h align1 equ 10h IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0x3A db 0 NewExe_offset dd ntHeader ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл. Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (section_table-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,0x3C;_import,0x3C;end_import-import ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin;0x57 .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE;0x80000020 ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd end_import-Import_Table;0x62 .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA;0x40000040 ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn a0: times (($ - begin)mod align1) db 0 ;выравнивание сегмента кода и данных до значения равного align1 ;секция импорта Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd 0,0,0,user32_dll,user32_table dq 0,0,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32" end_import: и как в четвертом шаге удаляем 16 нулевых байтов в конце файла и окончание ".dll" заголовоккод и данныеимпортобщий размер2969680472 байт
сокращаем DOS-stub на 16 байтов (3Ah-2Eh-4=10h), смещение e_lfanew оказывается внутри PE-заголовка. Так как у программы со стабом короче 64 байт смещение от начала файла 3Ch (поле e_lfanew) попадает уже внутрь PE-заголовка, то нужно, чтобы он не попал на поле PE-заголовка имеющее критическое значение при загрузке файла. Помещаем указатель на ntHeader в поле Symbol_table_offset. При размещении в нем числа 30h получаем работоспособное приложение. Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 400000h align1 equ 10h IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0x2E db 0 ;NewExe_offset dd ntHeader ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd ntHeader;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (section_table-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,0x3C ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd end_import-Import_Table .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn a0: times (($ - begin) mod align1) db 0 ;выравнивание сегмента кода и данных до значения равного align1 ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd 0,0,0,user32_dll,user32_table dq 0,0,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32" end_import: заголовоккод и данныеимпортобщий размер2809680456 байтсокращаем DOS-stub еще на 16 байтов (2Eh-1Eh=10h), смещение e_lfanew оказывается в поле Size_of_code. По одним источникам ― это поле используется для первичного отведения памяти под приложение. По другим ― не используются вообще. Если поместить туда число 20h, то практическая проверка показывает, что приложение с таким stub'ом работает нормально. Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 0x400000 align1 equ 0x10 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0x1E db 0 ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd ntHeader;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (section_table-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,0x3C ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd end_import-Import_Table .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn a0: times (($ - begin) mod align1) db 0 ;выравнивание сегмента кода и данных до значения равного align1 ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd 0,0,0,user32_dll,user32_table dq 0,0,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32" end_import: заголовоккод и данныеимпортобщий размер2649680440 байт
сокращаем DOS-stub до 16 байт, смещение e_lfanew оказывается в поле base_of_code. При размещении в нем числа 10h получаем работоспособное приложение. В начале секции импорта убрали байты для выравнивания. Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 0x400000 align1 equ 0x10 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE times 0xE db 0 ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 2;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw section_table-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd ntHeader ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd align1 file_alignment dd align1 OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (section_table-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,0x3C ;------------------------------------------------ section_table dq ".text" .virtual_size dd a0-begin .virtual_address dd begin .Physical_size dd Import_Table-begin .Physical_offset dd begin .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE ;------------------------------------------------ section_table1 dq ".rdata" .virtual_size dd end_import-Import_Table .virtual_address dd Import_Table .Physical_size dd end_import-Import_Table .Physical_offset dd Import_Table .Relocations_and_Linenumbers dq 0 .Relocations_and_Linenumbers_count dd 0 .Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA ;--------данные и код----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox,0 _import: dd 0,0,0,user32_dll,user32_table dq 0,0,0 _MessageBox db 0xE2,1,"MessageBoxA",0 user32_dll db "user32" end_import: Мы удалили таблицу, которую используют для импорта по ординалам. Внимание, вопрос ― А зачем нам в таком случае ординалы перед названием функций? Обнуляем ординалы и задаем себе следующий вопрос ― Если ординал нулевой ― нужен ли нулевой байт на конце строки? Удаляем нуль-терминаторы, а заодно и нулевые байты, которые делали адреса названий функции кратными двум, так как импортируемые функции теперь у нас заканчиваются двумя нулевыми байтами (а это расточительно!) ― помещаем в поле ординала последний символ названия функции или названия dll и нуль-терминатор количество секций делаем равным 0, удаляем секцию '.rdata' и '.text', размещаем импорт в секции кода. уменьшаем DOS-stub до 4 байт. смещение e_lfanew оказывается в поле file aligment. section aligment=file aligment=4 уменьшение импорта Код (ASM): format binary as "exe" include "win64a.inc" struc dbs [data] { common . db data .size = $ - . } IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_NT_SIGNATURE equ 00004550h PROCESSOR_AMD_X8664 equ 8664h IMAGE_SCN_CNT_CODE equ 00000020h IMAGE_SCN_MEM_WRITE equ 80000000h IMAGE_SCN_MEM_READ equ 40000000h IMAGE_SCN_CNT_INITIALIZED_DATA equ 00000040h IMAGE_SUBSYSTEM_WINDOWS_GUI equ 2 IMAGE_NT_OPTIONAL_HDR64_MAGIC equ 20Bh IMAGE_FILE_RELOCS_STRIPPED equ 1 IMAGE_FILE_EXECUTABLE_IMAGE equ 2 IMAGE_BASE equ 0x400000 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE equ 8000h use64 org 0 ;--------DOS-stub------------------------------- Signature dw IMAGE_DOS_SIGNATURE,0 ;-------PE-заголовок-------------------------------------------------- ntHeader dd IMAGE_NT_SIGNATURE;'PE' ;image_header----Файловый заголовок Machine dw PROCESSOR_AMD_X8664;Тип центрального процессора Count_of_section dw 0;Количество секций TimeStump dd 0;Информация о времени, когда был собран данный PE-файл Symbol_table_offset dd 0;Указатель на размер отладочной информации Symbol_table_count dd 0;Указатель на COFF-таблицу символов PE-формата Size_of_optional_header dw begin-optional_header;Размер опционального заголовка Characteristics dw IMAGE_FILE_RELOCS_STRIPPED or \ IMAGE_FILE_EXECUTABLE_IMAGE;Атрибуты файла ;-------Стандартные поля NT optional_header: Magic_optional_header dw IMAGE_NT_OPTIONAL_HDR64_MAGIC;Состояние отображаемого файла Linker_version_major_and_minor dw 9;Содержат версию линковщика, создавшего данный файл Size_of_code dd Import_Table-begin;Суммарный размер секций кода Size_of_init_data dd 0x70;Суммарный размер инициализированных данных Size_of_uninit_data dd 0;Суммарный размер неинициализированных данных entry_point dd start base_of_code dd begin ;------Дополнительные поля NT----------------------------------------------- image_base dq IMAGE_BASE section_alignment dd 4 file_alignment dd ntHeader OS_version_major_minor dw 5,2 image_version_major_minor dd 0 subsystem_version_major_minor dw 5,2 Win32_version dd 0 size_of_image dd end_import size_of_header dd begin checksum dd 0 subsystem dw IMAGE_SUBSYSTEM_WINDOWS_GUI DLL_flag dw IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE Stack_allocation dq 0x100000 Stack_commit dq 0x1000 Heap_allocation dq 0x100000 Heap_commit dq 0x1000 loader_flag dd 0 number_of_dirs dd (begin-export_RVA_size)/8 export_RVA_size dq 0 import_RVA_size dd _import,end_import-import ;------------------------------------------------ ;section_table dq '.text' ;.virtual_size dd a0-begin ;.virtual_address dd begin ;.Physical_size dd Import_Table-begin ;.Physical_offset dd begin ;.Relocations_and_Linenumbers dq 0 ;.Relocations_and_Linenumbers_count dd 0 ;.Attributes dd IMAGE_SCN_MEM_WRITE or IMAGE_SCN_CNT_CODE ;------------------------------------------------ ;section_table1 dq '.rdata' ;.virtual_size dd end_import-Import_Table ;.virtual_address dd Import_Table ;.Physical_size dd end_import-Import_Table ;.Physical_offset dd Import_Table ;.Relocations_and_Linenumbers dq 0 ;.Relocations_and_Linenumbers_count dd 0 ;.Attributes dd IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA ;--------данные, код и импорт----------------------------------------- begin: MsgBoxText dbs "Win64 Assembly is Great!",0 MsgCaption db "Win64 Iczelion's lesson #2: MessageBox",0 start: push rbp xor ecx,ecx mov edx,MsgBoxText+IMAGE_BASE lea r8d,[rdx+MsgBoxText.size] xor r9d,r9d call [MessageBox] pop rbp retn ;---------секция импорта--------------------------------------- Import_Table: user32_table: MessageBox dq _MessageBox _import: dd 0,0,0,user32_dll,user32_table,0 user32_dll db "user32" dd 0 _MessageBox db 0,0,"MessageBoxA" end_import: заголовоккод и данныеимпортобщий размер1568755298 байтМоральТак путем нехитрых преобразований Братец Кролик на порядок уменьшил размер ехе-файла с 2560 байт до 298 байт © Mikl___ 2021
Глава седьмая. Как Братец Кролик создал простейшее окно— Наверное, у мамы твоей гости, — сказал дядюшка Римус, когда Джоэль вбежал к нему с большущим куском слоёного пирога. — А если не гости, так, уж верно, она потеряла ключ от буфета, а ты нашёл его. — Просто, дядюшка Римус, мне мама дала пирога, а я подумал — притащу его тебе. Старик улыбнулся: — Спасибо, спасибо, сынок. Этот пирог как раз мне поможет собраться с силами, чтоб рассказать дальше про Братца Кролика и про его друзей. Тут старик замолчал и принялся за пирог. Он справился с ним очень быстро. Потом стряхнул крошки с бороды и начал: Окно ― это прямоугольная область экрана, в которой приложение отображает выходные данные и в которую позволяет пользователю вводить свои данные. Окна могут образовывать иерархию родительских и дочерних окон. Закрытие родительского окна всегда приводит к закрытию вместе с ним всех его дочерних окон. А вот закрытие дочернего окна никак не влияет на родительское. Сообщение ― это уведомление приложения о наступлении того или иного события (нажатие на кнопку, сворачивание, разворачивание, перемещение окна, ввода в поле Edit и так далее) и определенный код, переданный приложению с дополнительными параметрами, которые зависят от кода сообщения, но в большинстве сообщений они отсутствуют. Некоторые сообщения. СообщениеОписаниеWM_CLOSEЗакрытие окнаWM_COMMANDУведомление от дочернего элемента управленияWM_CREATEСоздание окнаWM_DESTROYУничтожение окнаWM_INITDIALOGОтправляется перед отображением диалогового окнаWM_KILLFOCUSПотерян фокус вводаWM_MOVEОтправляется окну после того как оно было перемещеноWM_PAINTПерерисовка окнаWM_QUITЗавершение прикладной программыWM_SETFOCUSПолучен фокус вводаWM_SHOWWINDOWПосылается перед показом или сокрытиемWM_SIZEПосылается после изменения размеров окнаWM_TIMERСообщение от таймераВсе сообщения, за исключением сообщений WM_PAINT, WM_TIMER и WM_QUIT помещаются системой в конец очереди сообщений, которая работает по принципу FIFO (First In, First Out ― первым пришел ― первым ушел). Работа любой windows-программы с графическим интерфейсом основана на обработке сообщений. В Windows у каждого процесса есть своя очередь сообщений, в нее попадают все сообщения, адресованные всем окнам данного процесса. Процесс (приложение) извлекает из этой очереди сообщение, обрабатывает его, потом берет следующее и так до бесконечности пока не будет получено сообщение WM_QUIT, требующее завершить данное приложение. В основе каждого окна лежит так называемый класс окна, который определяет все основные свойства данного окна.Регистрация класса окнаКласс окна описывается структурой WNDCLASSEX Код (ASM): WNDCLASSEX STRUCT cbSize DWORD ? ;Размер структуры style DWORD ? ;стиль класса lpfnWndProc QWORD ? ;адрес оконной процедуры cbClsExtra DWORD ? ;объем дополнительной памяти резервируемой за структурой (обычно 0) cbWndExtra DWORD ? ;объем дополнительной памяти за экземпляром окна оконной процедуры (обычно 0) hInstance QWORD ? ;дескриптор модуля, в котором описана оконная процедура hIcon QWORD ? ;дескриптор иконки (если не используется иконка по умолчанию) hCursor QWORD ? ;дескриптор курсора (если не используется курсор по умолчанию) hbrBackground QWORD ? ;дескриптор кисти или системный цвет фона lpszMenuName QWORD ? ;адрес строкового имени ресурса меню (NULL если меню нет) или его идентификатор lpszClassName QWORD ? ;адрес строкового имени класса hIconSm QWORD ? ;дескриптор малой иконки, ассоциированной с классом WNDCLASSEX ENDS Стиль класса (CLASS STYLE ― CS_) ― это один из предопределенных стилей классов окна или их сочетание. Существуют следующие стили классов: Стиль классаЗначениеОписаниеbinHexCS_VREDRAW00000000000000011Перерисовывать окно при изменении вертикальных размеровCS_HREDRAW00000000000000102Перерисовывать все окно при изменении шириныCS_KEYCVTWINDOW00000000000001004В окне выполняется преобразование виртуальных клавишCS_DBLCLKS00000000000010008Посылать сообщение от мыши при двойном щелчке в пределах окнаCS_000000000001000010?CS_OWNDC000000000010000020У каждого окна уникальный контекст устройстваCS_CLASSDC000000000100000040Контекст устройства, который будет разделяться всеми окнами класса. При нескольких потоках операционная система разрешит доступ только одному потокуCS_PARENTDC000000001000000080У дочернего окна будет область отсечки от родительского. Повышает производительностьCS_NOKEYCVT0000000100000000100Отключено преобразование виртуальных клавишCS_NOCLOSE0000001000000000200Отключить команду закрытьCS_0000010000000000400?CS_SAVEBITS0000100000000000800Позволяет сохранять область экрана в виде битовой матрицы закрытую в данный момент другим окном, используется для восстановления экранаCS_BYTEALIGNCLIENT00010000000000001000(по горизонтали) выравнивание рабочей области окна по границе байта. Влияет на ширину окна и его горизонтальное положение на экранеCS_BYTEALIGNWINDOW00100000000000002000(по вертикали) выравнивает окна по границе байтаCS_GLOBALCLASS, CS_PUBLICCLASS01000000000000004000Позволяет приложению создавать окно класса независимо от значения параметра hInstance, передаваемого в функцию CreateWindow или CreateWindowEx. Если вы не укажете этот стиль, параметр hInstance, передаваемый в функцию CreateWindow (или CreateWindowEx), должен совпадать с параметром hInstance, передаваемым в функцию RegisterClass. Вы можете создать глобальный класс, создав класс окна в динамически подключаемой библиотеке (DLL) и указав имя библиотеки DLL в реестре под следующими ключами: Создавать глобальный класс, который можно поместить в динамическую библиотеку dll.Существуют следующие системные цвета фона: Символьное обозначениеЦвет "по-умолчанию"значение в win.hCOLOR_ACTIVEBORDERБелый10COLOR_ACTIVECAPTIONЧерный_____2COLOR_APPWORKSPACEТемно-серый12COLOR_BACKGROUNDТемно-серый1COLOR_BTNFACEБелый15COLOR_BTNSHADOWСветло-серый16COLOR_BTNTEXTТемно-серый18COLOR_CAPTIONTEXTЧерный9COLOR_GRAYTEXTТемно-серый17COLOR_HIGHLIGHTТемно-серый13COLOR_HIGHLIGHTTEXTСиний14COLOR_INACTIVEBORDERСветло-серый11COLOR_INACTIVECAPTIONСиний3COLOR_MENUБледно-голубой4COLOR_MENUTEXTЧерный7COLOR_SCROLLBARНа фон попадает все то, что собой закрыло окно0COLOR_WINDOWБелый5COLOR_WINDOWFRAMEБелый6COLOR_WINDOWTEXTЧерный8 Но пользователь в любой момент может изменить эти цвета Для того, чтобы операционная система узнала о новом класса, его нужно зарегистрировать. Делается это с помощью функции RegisterClassEx(A/W). Прототип функции: Код (C): ATOM WINAPI RegisterClassEx( __in const WNDCLASSEX *lpwcx // указатель на структуру WNDCLASSEX ); В случае успеха данная функции возвращает уникальный идентификатор зарегистрированного класса. Если же функция по какой-либо причине не может выполнить свою задачу, тогда возвращаемое ею значение равно нулю. Уничтожить или «снять с регистрации» класс можно функцией UnRegisterClass(A/W) (это делается только после того, как уничтожены все окна построенные на основе этого класса), вот ее описание: Код (C): BOOL WINAPI UnregisterClass( __in LPCTSTR lpClassName, // адрес строки с именем удаляемого класса __in_opt HINSTANCE hInstance /* дескриптор модуля, в котором был создан класс */ ); Если данная функция успешно отрабатывает, то возвращается не нулевое значение. Если же функция не может выполнить возложенную на нее задачу (не найден такой класс, существуют окна, основанные на этом классе и другие) тогда возвращается ноль. Если у приложения всего одно окно, которое уничтожается по завершении работы приложения ― тогда вызывать эту функцию необязательно. Но если у приложения много окон, созданных на основе разных классов, тогда вызывать функцию UnregisterClass(A/W) необходимо ― иначе появится «утечка памяти».
Создание окна. Стили окнаПосле того как класс окна зарегистрирован, можно создать окно. Окна создаются с помощью функции CreateWindowEx(A/W). Код (C): HWND WINAPI CreateWindowEx( __in DWORD dwExStyle, // Расширенные стили окна __in_opt LPCTSTR lpClassName, /* Указатель на строку с именем зарегистрированного класса */ __in_opt LPCTSTR lpWindowName, // Указатель на строку заголовка окна __in DWORD dwStyle, // Стили окна __in int x, // Начальное положение окна по горизонтали __in int y, // Начальное положение окна по вертикали __in int nWidth, // Ширина окна __in int nHeight, // Высота окна __in_opt HWND hWndParent, // Дескриптор родительского окна __in_opt HMENU hMenu, // Дескриптор меню (NULL если его нет) __in_opt HINSTANCE hInstance, // Дескриптор модуля, связанного с окном __in_opt LPVOID lpParam /* Параметр lParam, который будет передан окну с сообщением WM_CREATE */ ); Существуют следующие стили окна (WINDOWS STYLE ― WS_), которые сочетаются друг с другом: Стиль окнаЗначениеОписаниеBinHexWS_OVERLAPPED, WS_TILED000000000000000000000000000000000Перекрывающееся окно (может располагаться поверх других окон)WS_ACTIVECAPTION000000000000000000000000000000011WS_OPENWindow is openedWS_ICONIFY000000000000000000000000000000102Window is iconifiedWS_MENU000000000000000000000000000001004Window has menuWS_TOOLBAR000000000000000000000000000010008Window has toolbarWS_GROW0000000000000000000000000001000010Window use graphic effectsWS_UNTOPPABLE0000000000000000000000000010000020Window is untoppableWS_FORM0000000000000000000000000100000040Window is a formularWS_FORMDUP0000000000000000000000001000000080Window is a duplicated formularWS_MODAL00000000000000000000000100000000100Window is modalWS_FRAME_ROOT00000000000000000000001000000000200Window is a frame rootWS_FRAME00000000000000000000010000000000400Window is a framed windowWS_CUSTOMWindow is a custom handled window (the handle does not correspond to the AES one)WS_ALLICNF00000000000000000000100000000000800All windows are iconifiedWS_FULLSIZE000000000000000000010000000000001000Window has fulled sizeWS_PEXEC000000000000000000100000000000002000Window is closed because of Pexec-call (reserved by ShelWrite)WS_FOREIGN000000000000000001000000000000004000Window is not created by application but inserted in WinDom listWS_WIN_IN_USE000000000000000010000000000000008000Window structure is in use: should not be free-ed (see mt_WindDelete())WS_DESTROYED0000000000000001000000000000000010000Window structure should be free-ed as soon as possibleWS_MAXIMIZEBOXДобавляет в заголовок окна кнопку «развернуть»WS_TABSTOPВключает обход управляющего элемента по TABWS_MINIMIZEBOX0000000000000010000000000000000020000Добавляет в заголовок окна кнопку «cвернуть»WS_GROUPПервый элемент управления в группе управляющих элементов. При указании у формы отображает кнопку «свернуть окно»WS_SIZEBOX, WS_THICKFRAME0000000000000100000000000000000040000Окно, у которого можно изменять размерWS_SYSMENU0000000000001000000000000000000080000Окно, имеющее системное меню (кнопки «закрыть», «свернуть», «развернуть»).WS_HSCROLL00000000000100000000000000000000100000Отображает горизонтальную полосу прокрутки (scroll)WS_VSCROLL00000000001000000000000000000000200000Вертикальная полоса прокруткиWS_DLGFRAME00000000010000000000000000000000400000Отображает двойную границу окна, но без заголовка (не работает при WS_CAPTION). Используется в диалогахWS_BORDER00000000100000000000000000000000800000Отображает тонкую границу окнаWS_CAPTION00000000110000000000000000000000C00000Отображает заголовок окна (автоматически устанавливает флаг WS_BORDER)WS_OVERLAPPEDWINDOW, WS_TILEDWINDOW00000000110011110000000000000000CF0000Перекрывающееся окно с включенными флагами WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX,WS_MAXIMIZEBOXWS_MAXIMIZE000000010000000000000000000000001000000Создает окно максимальных размеровWS_CLIPCHILDREN000000100000000000000000000000002000000Окно включает области занятые дочерними окнами, которые могут быть отрисованы в отсутствие родительского окнаWS_CLIPSIBLINGS000001000000000000000000000000004000000Отсекает дочерние окна друг от друга, то есть когда определенное дочернее окно получает сообщение WM_PAINT, стиль отсекает все другие дочерние окна, которые перекрывают данное дочернее окно, от той области дочернего окна, которая будет обновлена. Если стиль WS_CLIPSIBLINGS не определен и дочерние окна перекрывают друг друга, то можно при рисовании в клиентской области одного дочернего окна рисовать и в клиентской области соседнего дочернего окна.WS_DISABLED000010000000000000000000000000008000000Недоступное окноWS_VISIBLE0001000000000000000000000000000010000000Видимое окноWS_ICONIC, WS_MINIMIZE0010000000000000000000000000000020000000Отображает свернутое окноWS_CHILD, WS_CHILDWINDOW0100000000000000000000000000000040000000Создаваемое окно является дочерним. Не используется с WS_POPUPWS_POPUP1000000000000000000000000000000080000000Флаг, обратный к WS_OVERLAPPED. Создается окно, не имеющее ничего, кроме поверхности.WS_POPUPWINDOW1000000010001000000000000000000080880000Окно с включенными флагами WS_BORDER, WS_POPUP, WS_SYSMENUРасширенные стили окна Расширенный стиль окнаЗначениеОписаниеBinHexWS_EX_LEFT00000000000000000000Окно с левосторонним выравниванием элементов и текстаWS_EX_LTRREADINGТекст окна отображается слева направоWS_EX_RIGHTSCROLLBARВертикальная полоса прокрутки (scrollbar) в правой части окнаWS_EX_DLGMODALFRAME00000000000000000011окно с двойной рамкой может сочетаться с WS_CAPTIONWS_EX_00000000000000000102―WS_EX_NOPARENTNOTIFY00000000000000001004Дочернее окно, созданное с этим стилем не посылает сообщение WM_PARENTNOTIFY родительскому окну, когда оно создается или разрушаетсяWS_EX_TOPMOST00000000000000010008Определяет, что окно, созданное с этим стилем должно быть размещено выше всех, не самых верхних окон и должно стоять выше их, даже тогда, когда окно дезактивированоWS_EX_ACCEPTFILES000000000000001000010Определяет окно, способное принимать перетаскиваемые на него файлы из «Проводника Windows»WS_EX_TRANSPARENT000000000000010000020прозрачное окноWS_EX_MDICHILD000000000000100000040Создает MDI дочернее окноWS_EX_TOOLWINDOW000000000001000000080окно с тонким заголовкомWS_EX_WINDOWEDGE0000000000100000000100окно имеет рамку с выпуклым краемWS_EX_PALETTEWINDOW0000000000110001000188WS_EX_WINDOWEDGE+WS_EX_TOOLWINDOW+WS_EX_TOPMOSTWS_EX_CLIENTEDGE0000000001000000000200окно с «утопленной» клиентской частьюWS_EX_OVERLAPPEDWINDOW0000000001100000000300WS_EX_CLIENTEDGE+WS_EX_WINDOWEDGEWS_EX_CONTEXTHELP0000000010000000000400включает кнопку помощи (знак вопроса) в заголовок окнаWS_EX_0000000100000000000800―WS_EX_RIGHT00000010000000000001000Окно с правосторонним выравниванием элементов и текстаWS_EX_RTLREADING00000100000000000002000Текст окна отображается справа налевоWS_EX_LEFTSCROLLBAR00001000000000000004000Размещает вертикальную полосу прокрутки (scrollbar) в левой части окнаWS_EX_00010000000000000008000―WS_EX_CONTROLPARENT001000000000000000010000Включает возможность навигации пользователя по элементам формы с использованием клавиши TABWS_EX_STATICEDGE010000000000000000020000Окно с трехмерным стилем рамкиWS_EX_APPWINDOW100000000000000000040000на панель задач выводится кнопка окнаФункция CeateWindowEx(A/W) возвращает дескриптор созданного окна или ноль если не удалось создать окно. Уничтожается окно функцией DestroyWindow Код (C): BOOL WINAPI DestroyWindow( __in HWND hWnd // Дескриптор уничтожаемого окна ); Если функции не удается уничтожить окно, тогда возвращается ноль, иначе возвращается ненулевое значение.Цикл обработки сообщенийЦикл выполняет следующие задачи: извлекает из очереди сообщений очередное сообщение, требующее обработки; приводит полученное сообщение к аппаратно-независимому виду; передает его соответствующей оконной процедуре, которая его и обрабатывает. Все сообщения, посылаемые приложению, помещаются в очередь сообщений приложения. Из этой очереди нужно извлечь очередное обрабатываемое сообщение. Для этого используется функция GetMessage(A/W) Код (C): BOOL WINAPI GetMessage( __out LPMSG lpMsg, /* адрес структуры MSG, в которую заносится информация о сообщении извлеченном из очереди сообщений */ __in_opt HWND hWnd, /* Дескриптор окна-адресата сообщения (если NULL, то извлекаются все сообщения, адресованные всем окнам данного приложения) */ __in UINT wMsgFilterMin,// Минимальный код извлекаемого сообщения __in UINT wMsgFilterMax // Максимальный код извлекаемого сообщения ); Функция GetMessage(A/W) не вернет управление до тех пор, пока не придет какое-либо сообщение. Функция возвращает одно из трех возможных значений: Ненулевое положительное число ― если из очереди извлечено сообщение; 0 ― если получено сообщение WM_QUIT, требующее закрыть приложение; -1 ― если в процессе работы функции произошла какая-то ошибка Параметры wMsgFilterMin и wMsgFilterMax служат для отбора строго определенных сообщений, например, сообщений от клавиатуры. Структура MSG имеет следующий вид: Код (ASM): MSG STRUCT STRUCT_ALIGN hwnd QWORD ? ;Дескриптор окна, которому адресовано сообщение message DWORD ?,?;Код сообщения wParam QWORD ? ;Параметр сообщения (зависит от кода) lParam QWORD ? ;Параметр сообщения (зависит от кода) time DWORD ? ;Время, когда сообщение было помещено в очередь pt POINT <> ;Позиция курсора мыши в момент, когда сообщение было помещено в очередь DWORD ? MSG ENDS Передача полученного сообщения в оконную процедуру осуществляется функцией DispatchMessage(A/W) Код (C): LRESULT WINAPI DispatchMessage( __in const MSG *lpmsg // адрес структуры MSG с передаваемым сообщением ); Функция DispatchMessage(A/W) возвращает операционной системе значение, возвращенное оконной процедурой при обработке переданного ей сообщения.
Оконная процедураОконная процедура осуществляет непосредственную обработку полученных сообщений. Когда в цикле обработки сообщений вызывается функция DispatchMessage(A/W), операционная система находит окно, которому адресовано сообщение, находит оконную процедуру этого окна и передает ей управление. Код (C): LRESULT CALLBACK WindowProc( __in HWND hwnd, // дескриптор окна, которому адресовано сообщение __in UINT uMsg, // код сообщения __in WPARAM wParam, // параметр сообщения __in LPARAM lParam // параметр сообщения ); Пример оконного приложенияСкачайте пример здесь. Код (ASM): ; GUI # include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx ;rbx = 0 mov esi,IMAGE_BASE ; дескриптор нашей программы mov eax,10027h ; дескриптор иконки mov edi,offset ClassName; Имя нашего класса окна ; заполнение структуры wc и регистрация класса push rax ;hIconSm дескриптор малой иконки push rdi ;lpszClassName Имя класса окна push rbx ;lpszMenuName меню push COLOR_WINDOWTEXT;hbrBackground Цвет фона push 10003h ;hCursor дескриптор курсора push rax ;hIcon дескриптор иконки push rsi ;hInstance дескриптор модуля push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc ;Адрес процедуры окна, ответственной за окна, создаваемых на основе этого класса push sizeof WNDCLASSEX;cbSize & style ;Стиль окон, создаваемых из этого класса. Вы можете ;комбинировать несколько стилей вместе, используя оператор "or". invoke RegisterClassEx,esp ;rsp = addr WNDCLASSEX ;регистрация нашего класса окна ;После регистрации класса окна, мы должны вызвать CreateWindowEx, ;чтобы создать наше окно, основанное на этом классе ;--------------------------+ ; creating the main window | ;--------------------------+ push rbx ;lрParam: Опциональный указатель на структуру данных, ;передаваемых окну. Это используется окнами MDI, чтобы передать структуру ;CLIENTCREATESTRUCT. Обычно этот параметр установлен в NULL, означая, что ;никаких данных не передается через CreateWindow(). Окно может получать значение ;этого параметра через вызов функции GetWindowsLong. push rsi ;rsi=400000h hInstance: дескриптор программного модуля, создающего окно shl esi,9 ;rsi=CW_USEDEFAULT push rbx ;hMenu: дескриптор меню окна. NULL - если будет использоваться меню, ;определенное в классе окна. Взгляните на код, объясненный ранее, член структуры ;WNDCLASSEX lрszMenuName. Он определяет меню "по умолчанию" для класса окна. ;Каждое окно, созданное из этого класса будет иметь тоже меню по умолчанию, до ;тех пор пока вы не определите специально меню для какого-то окна, используя ;параметр hMenu. Этот параметр - двойного назначения. В случае, если ваше окно ;основано на предопределенном классе окна, оно не может иметь меню. Тогда hMenu ;используется как ID этого контрола. Windows может определить действительно ли ;hMenu - это дескриптор меню или же ID контрола, проверив параметр lрClassName. Если ;это имя предопределенного класса, hMenu - это идентификатор контрола. Если нет, ;это дескриптор меню окна push rbx ;hWndParent: дескриптор родительского окна (если существует). Этот ;параметр говорит Windows является ли это окно дочерним (подчиненным) другого ;окна, и, если так, кто родитель окна. Заметьте, что это не родительско-дочерние ;отношения в окна MDI (multiрly document interface). Дочерние окна не ограничены ;границами клиентской области родительского окна. Эти отношения нужны для ;внутреннего использования Windows. Если родительское окно уничтожено, все ;дочерние окна уничтожаются автоматически. Это действительно просто. Так как в ;нашем примере всего лишь одно окно, мы устанавливаем этот параметр в NULL push rsi ;X-координата верхнего левого угла окна. ;Обычно эти значения равны CW_USEDEFAULT, что позволяет Windows решить, куда ;поместить окно. nWidth, nHeight: Ширина и высота окна . Вы можете также использовать ;CW_USEDEFAULT, чтобы позволить Windows выбрать соответствующую ширину и высоту ;для вас push rsi ;Y-координата верхнего левого угла окна push rsi ;ширина окна в пикселях push rsi ;высота окна в пикселях sub esp,20h invoke CreateWindowEx,0,\;dwExStyle: Дополнительные стили окна edi,\;lpClassName: Адрес ASCIIZ строки, содержащей имя класса окна edi,\;lpWindowName: Адрес ASCIIZ строки, содержащей имя окна WS_OVERLAPPEDWINDOW or WS_VISIBLE;dwStyle: Стили окна. Вы можете ;определить появление окна здесь. Можно передать NULL без проблем, тогда у окна ;не будет кнопок изменения размеров, закрытия и системного меню. Большого прока ;от этого окна нет. Самый общий стиль - это WS_OVERLAPPEDWINDOW. Стиль окна всегда ;лишь битовый флаг, поэтому вы можете комбинировать различные стили окна с помощью ;оператора "or", чтобы получить желаемый результат. Стиль WS_OVERLAPPEDWINDOW в ;действительности комбинация большинства общих стилей с помощью этого метода. ;цикл обработки сообщений lea edi,msg @@: invoke GetMessage,edi,NULL,0,0 invoke DispatchMessage,edi jmp @b WinMain endp ;----------------------+ ; the window procedure | ;----------------------+ WndProc:cmp edx,WM_DESTROY je wmDESTROY jmp NtdllDefWindowProc_;все сообщения, не обрабатываемые в функции ;WndProc, направляются на обработку по умолчанию wmDESTROY: invoke RtlExitUserProcess,NULL; выходим из программы ;data----------------------------- ClassName db "Win64 Iczelion's lesson #3a: Simple windows",0 end Чем наши исходники будут отличаться от классических при линковке программ секции кода присвоены атрибуты чтение, запись и выполнение ― поэтому и данные и код будут размещены в единственной секции .code любая программа начинается с Код (ASM): invoke GetModuleHandle, NULL mov hInstance,rax но мы при линковке указываем ключ /BASE:400000h поэтому функция GetModuleHandle будет возвращать значение 400000h обратите внимание на этот фрагмент Код (ASM): xor ebx,ebx ;rbx = 0 WinAPI-функции сохраняют неизменными значения в регистрах RBP, RSP, RBX, RDI, RSI, R11-R15 этим мы и воспользуемся ― обнулим значение в регистре RBX и будем применять значения этого регистра везде, где понадобится нулевое значение, в RSI мы сохранили значение дескриптора программы после вызова RegisterClassA, в RDI сохраняется указатель на структуру MSG после вызовов GetMessage и DispatchMessage Переворачиваем структуру WNDCLASSEX "вверх ногами" и помещаем ее в стек. Значения, возвращаемые функциями LoadCursor и LoadIcon, смотрим заранее программой Код (ASM): ; GUI # include win64a.inc OPTION PROLOGUE:rbpFramePrologue .code WinMain proc local buffer[96]:byte local hIcon:qword local hCursor:qword invoke LoadIcon,NULL,IDI_APPLICATION mov hIcon,rax invoke LoadCursor,NULL,IDC_ARROW mov hCursor,rax invoke GetModuleHandle,NULL invoke wsprintf,&buffer,&fmt,hIcon,hCursor,rax invoke MessageBox,NULL,&buffer,&MsgCaption,MB_OK invoke RtlExitUserProcess,NULL WinMain endp ;data------------------------------------- MsgCaption db "Iczelion's tutorial #1",0 fmt db "hCursor = %08Xh",0Ah,"hIcon = %08Xh",0Ah,"hInstance =%08Xh",0 end Так как курсор и иконка передаваемые нами соответствуют стандартной стрелочке и стандартной иконке, то их идентификаторы предопределены и не изменяются. Поэтому мы и не используем LoadCursor и LoadIcon. Значение вершины стека передаем как указатель на структуру WNDCLASSEX пятым, шестым, седьмым и восьмым параметром в стек помещается значение CW_USEDEFAULT = 80000000h, то есть в коде нашей программы четыре раза появляется 5-ти байтовый код команды push 80000000h (6800000080h), но ведь в регистре RSI уже есть число 400000h (тем более значение hInst дальше нигде не используется ) 400000h × 200h = 80000000h 200h = 512 = 29 400000h << 9 = 80000000h сдвигаем hInst влево на 9 разрядов командой shl esi,9 (код 0C1E609h) и четыре раза помещаем в стек содержимое регистра RSI командой push rsi (код 56h)
После регистрации окна обычно проделывают три операции: создает окно (функция CreateWindow); Отображает его на экране (ShowWindow) Обновить содержимое окна (UpdateWindow) Код (ASM): invoke CreateWindowEx,NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ . . . invoke ShowWindow, hwnd,CmdShow ; отобразить наше окно на десктопе invoke UpdateWindow, hwnd ; обновить клиентскую область но, как показала практика, добавление к стилю окна (обычно WS_OVERLAPPEDWINDOW) во время создания окна стиля WS_VISIBLE делает ненужным вызов функций ShowWindow и UpdateWindow. Еще один фокус с CreateWindow ― правильное указание класса окна необходимо, если мы создаем элемент окна, с заранее определенными свойствами (STATIC, BUTTON, EDIT и тому подобное) правда в этом случае нам не нужен RegisterClass. Когда мы создаем наше главное окно, то его название может быть любым, в том числе и на русском или на китайском в юникоде. Экономим на этом, и название класса окна, и название имени окна, которое будет показано на title bar'е ― будут одним и тем же. Код (ASM): mov edi,offset wTitle; Имя нашего класса окна и имя окна . . . invoke CreateWindow,0,edi,edi,\ WS_OVERLAPPEDWINDOW + WS_VISIBLE,\ Модуль цикла сообщений: Код (ASM): LOCAL msg:MSG . . . .WHILE TRUE ; Enter message loop invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW Наш модуль обработки цикла сообщений. Код (ASM): lea edi,msg @@: invoke GetMessage,edi,NULL,0,0 invoke DispatchMessage,edi jmp @b Обычно к циклу сообщений добавляют еще и TranslateMessage хотя обработка нажатий на клавиши в программе и не предусматривается Единственное сообщение, которое вы ОБЯЗАНЫ обработать ― это WM_DESTROY. Это сообщение посылается вашему окну, когда оно закрывается (пользователь нажал на крестик в правом верхнем углу () или комбинацию клавиш Alt+F4 или выбрал пункт «Закрыть» в системном меню). В то время, когда процедура окна его получает, окно уже исчезло с экрана. Это всего лишь напоминание, что ваше окно было уничтожено, поэтому вы должны готовиться к выходу в Windows. Если вы хотите дать шанс пользователю предотвратить закрытие окна, тогда вы должны предусмотреть обработку сообщения WM_CLOSE. После получения сообщения WM_DESTROY программа выполнит RtlExitUserProcess. Код (ASM): WndProc: cmp edx,WM_DESTROY ; если пользователь закрывает окно je wmDESTROY jmp NtdllDefWindowProc_; все остальные сообщения wmDESTROY: invoke RtlExitUserProcess,NULL В программе предусмотрена обработка только одного сообщения, поэтому нам не нужен стандартный пролог. Стандартная обработка остальных сообщений Код (ASM): invoke DefWindowProc,hWnd,uMsg, wParam,lParam leave ret заменена на Код (ASM): jmp NtdllDefWindowProc_ Существует методика оптимизации, связанная с переходами и вызовами, которая называется «сращиванием хвостов». Она подразумевает преобразование вызовов в переходы: вызовы по самой своей природе требуют большего времени, чем переходы, поскольку помещают в стек адрес возврата и в результате требуют больше обращений к памяти. «Сращивание хвостов» ― это просто преобразование команды CALL, непосредственно за которой следует команда RET, в команду JMP. Например, последовательность: Код (ASM): Proc1 proc ... call Proc2 ret Proc1 endp Proc2 proc ... ret Proc2 endp преобразуется в более быструю: Код (ASM): Proc1 proc ... jmp Proc2 Proc1 endp Proc2 proc ... ret Proc2 endp Такая оптимизация приводит к следующему: поскольку адрес команды, вызывающей PROC1, находится в стеке на входе в PROC2, процедура PROC2 возвращается прямо к исходной вызывающей программе, тем самым устраняются лишние команды CALL и RET. Если программа PROC2 физически (в памяти) следует за программой PROC1, то можно обойтись даже без команды JMP PROC2, и за выполнением PROC1 может сразу же следовать PROC2. Для функции NtdllDefWindowProc_ передаются те же параметры, что и для функции WndProc, поэтому достаточно будет поставить jmp вместо call и ret. Так как функция WndProc была вызвана без пролога, то мы выходим из нее без эпилога "leave". Код (ASM): .ENDIF xor eax,eax ret WndProc endp end сброс регистра RAX в ноль при возврате из функции WndProc (как показатель обработанных сообщений) на самом деле не требуется (это необходимо только для диалогов или если вызывается функция SendMessage). © Mikl___ 2021
Глава восьмая. Как Братец Кролик выводил текст на экран при помощи функции DrawText Братец Лис кончил зализывать лапу и мрачно посмотрел на Братца Кролика. Братец Кролик ответил ему не менее мрачным взглядом. Он наверняка демонстративно отвернулся бы, если б не переломаная шея. ― Это неправильно. ― констатировал Братец Лис. В его голосе звучала неприкрытая неприязнь ко всем представителям кроличьего рода, и к жизни вообще, позволяющей таким вещам происходить. ― Кроликам кусаться не положено. Природой. У тебя зубы для этого не приспособлены. Сам посмотри, тебе это ни хрена не помогло, а мне теперь две недели хромать. ― В общем, конечно, ― сказал Братец Кролик, и в затухающем голосе его прозвучала задумчивость. ― Но с другой стороны, попробовать стоило... перепишем программу, отображающую текстовую строку "Win64 assembly with MASM is great and easy" в центре клиентской области. Сообщение WM_PAINT посылается в окно каждый раз, когда требуется изменить содержание рабочей области окна (client area) (рисунок 1). Рабочая область ― это пространство, которое программист заполняет самостоятельно (область внутри рамки и заголовок), в то время как Windows заботится об остальной части приложения. рисунок 1Если программист не контролирует какую-либо часть рабочей области, Windows через небольшое время закрасит эту область определенным цветом. Если окно, часть окна было перекрыто другим окном, то после того как окно "откроется", оно получит сообщение WM_PAINT, так как необходимо перерисовать область, которая была скрыта другим окном. Если окно было свернуто, то при развороте на весь экран или при восстановлении до нормального размера окно также получит сообщение WM_PAINT. Основное назначение функции BeginPaint ― обеспечить доступ к контексту устройства для окна. Контекст устройства (DC ― Device Context) представляет собой соединительное звено между программой пользователя и некоторыми внешними устройствами подключенными к компьютеру. Контексты устройств формируют интерфейс между приложением и принтером или видеокартой. Важность контекстов устройств обусловлена существованием множества различных типов видеокарт и принтеров Функция EndPaint завершает рисование на рабочей области окна. Параметрами BeginPaint и EndPaint являются HWND и адрес PAINTSTRUCT. Структура PAINTSTRUCT имеет следующие поля Код (ASM): PAINTSTRUCT STRUCT hdc QWORD ? ;дескриптор DC fErase DWORD ? ;истина, если перерисовывается заполнение окна rcPaint RECT <> ;координаты области перерисовки fRestore BOOL ? ;зарезервировано fIncUpdate BOOL ? ;зарезервировано rgbReserved BYTE 36 dup(?);зарезервировано PAINTSTRUCT ENDS Первое поле ― копия контекста для окна. Второе поле указывает должен или нет перерисовываться фон. Третье поле определяет прямоугольник, в котором происходит рисование. Оставшиеся три поля зарезервированы Windows и интереса (пока) не представляют... Функция DrawText использует в качестве параметров контекст устройства, строку, длину строки, RECT и некоторые флажки nFormat (таблица 1). Прототип функции DrawText Код (C): virtual int DrawText( LPCTSTR lpszString,\ /* Указатель на строку, которая будет выведена. Если nCount -1, строка должна быть с нулевым символом в конце */ int nCount,\ /* Определяет количество символов в строке. Если nCount -1, то lpszString принят, чтобы быть длинным указателем на строку с нулевым символом в конце, и DrawText вычисляет символьный счет автоматически */ LPRECT lpRect,\ /* Указатель на структуру RECT, который содержит прямоугольник (в логических координатах) в котором текст должен форматироваться */ UINT nFormat //Определяет метод форматирования текста ); Таблица 1. Флаги форматирования текста DT_BOTTOMОпределяет выровненный по нижней части текст. Это значение должно быть объединено с DT_SINGLELINE.DT_CALCRECTОпределяет ширину и высоту прямоугольника. Если имеются мнострочные строки текста, DrawText использует ширину прямоугольника, указанного на lpRect и расширит прямоугольник к последней строке текста. Если имеется только одна строка текста, DrawText изменит правую сторону прямоугольника так, чтобы это ограничило последний символ в строке. В любом случае, DrawText возвращает высоту форматируемого текста, но не выводит текст.DT_CENTERВыравнивает по центру текст горизонтально.DT_END_ELLIPSIS или DT_PATH_ELLIPSISЗаменяет часть данной строки с эллипсами, в случае необходимости, так, чтобы результат находился в определенном прямоугольнике. Данная строка не изменяется, если флажок DT_MODIFYSTRING не определен. Вы можете определять DT_END_ELLIPSIS, чтобы заменить символы в конце строки, или DT_PATH_ELLIPSIS, чтобы заменить символы в середине строки. Если строка содержит наклонную черту влево ("\"), символы, DT_PATH_ELLIPSIS сохраняют в максимально возможной степени текста после последней наклонной черты влево.DT_EXPANDTABSРазворачивает символы табуляции. Заданное по умолчанию число символов позиций табуляции ― восемь.DT_EXTERNALLEADINGВключает внешнюю подачу шрифта в высоте строки. Обычно, внешняя подача не включена в высоту строки текста.DT_LEFTВыравнивает влево текст.DT_MODIFYSTRINGИзменяет данную строку, чтобы согласовать к отображаемому тексту. Этот флажок не имеет никакого эффекта, если DT_END_ELLIPSIS или флажок DT_PATH_ELLIPSIS не определен. Обратите внимание, что некоторые комбинации флажков uFormat могут заставлять переданную строку изменяться. Использование DT_MODIFYSTRING или с DT_END_ELLIPSIS или DT_PATH_ELLIPSIS может заставлять строку изменяться, вызывая исключение в отмене CString.DT_NOCLIPВыводит без отсечения. Функция DrawText отрабатывает немного быстрее, когда используется флаг DT_NOCLIP.DT_NOPREFIXВыключает обработку префиксных символов. Обычно, DrawText интерпретирует символ амперсанда ("&") как директиву, чтобы подчеркнуть символ, который следует после амперсанта (&A→A), и два символа амперсанда ("&&") как директиву, чтобы печатать одиночный символ амперсанда. Если установлен флаг DT_NOPREFIX ― эта обработка амперсандтов отключена.DT_RIGHTВыравнивание теста вправоDT_SINGLELINEВывод текста как одиночная строка. Символ с кодом 0Dh ("возврат каретки") и символ с кодом 0Ah ("перевод строки") не разрывают строку.DT_TABSTOPУстанавливает табуляторы. Старший байт nFormat ― число символов для каждой позиции табуляции. По умолчанию число символов в табуляции равно восьми.DT_TOPОпределяет выровненный по верху текст (только в комбинации с флагом DT_SINGLELINE).DT_VCENTERОпределяет вертикально центрированный текст (только в комбинации с флагом DT_SINGLELINE).DT_WORDBREAKОпределяет символ, который будет использоваться для "разрыва" строки. Строки будут автоматически разбиты между такими символами, если слово простиралось бы дальше края прямоугольника, определенного lpRect. Символы перевода строки и символы возврата каретки также разорвут строкуЕсли по какой-то причине Вам лень вычислять длину выводимой строки ― тогда передайте в качестве третьего параметра -1 и тогда функция DrawText сама подберет длину строки. Параметр RECT определяет область внутрь которой будет выведена строка. Скачайте пример здесь. Код (ASM): include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov eax,10027h mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW+1;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc mov rax,((CS_HREDRAW or CS_VREDRAW)shl 32)+sizeof WNDCLASSEX push rax ;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,NULL,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD local ps:PAINTSTRUCT local expRect:RECT mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT leave jmp DefWindowProc wmDESTROY: invoke ExitProcess,NULL wmPAINT:invoke BeginPaint,,&ps invoke GetClientRect,hWnd,&expRect mov edx,offset expTxt invoke DrawText,ps.hdc,,-1,&expRect,DT_SINGLELINE or DT_CENTER or DT_VCENTER invoke EndPaint,hWnd,&ps wmBYE: leave retn WndProc endp ;--------------------------------------- ClassName db 'Win64 Iczelion''s lesson #4: Painting with Text',0 expTxt db 'Win64 assembly with MASM is great and easy',0 end Iczelion отводит в стеке место под три локальных переменных. Код (ASM): LOCAL hdc:HDC LOCAL ps:PAINTSTRUCT LOCAL rect:RECT Переменная hdc используемая для сохранения дескриптора контекста устройства, возвращенного функцией BeginPaint используется всего один раз. В варианте Iczelion'а мы вызываем GetClientRect, чтобы получить размеры клиентской области, размеры возвращаются в переменной rect, которую вы передаете функции DrawText как один из параметров. Но клиентскую область также можно обнаружить в структуре PAINTSTRUCT поэтому нам не нужна функция GetClientRect © Mikl___ 2016
Глава пятьдесят первая. Братец Кролик узнает о тексте еще больше Когда Братец Кролик Джуниор выскочил на дорогу, по которой как раз прогуливался весьма довольный собой и миром Братец Лис, потрясены и испуганы были оба. ― О великая рыжая Сила, помилуй мя грешного, свят-свят-свят... ― бормотал в истерике Братец Лис, от испуга побледнев до желтизны. Братец Кролик же застыл на середине дороги, то ли от страха, то ли прикидываясь булыжником. ― Кролик Братца Призрака... То есть, призрак Братца Кролика! ― стонал Лис. ― Чего ты хочешь от меня? Я съел тебя по всем правилам... До Братца Кролика младшего стало понемногу доходить, почему Братец Лис в таком состоянии. К сожалению, это объясняло и то, куда подевался почтенный папа-Кролик. Он уже намеревался драпануть, пока Братец Лис трясется и несет околесицу, но ему стало жалко старого Братца Лиса. И в самом деле, Братец Лис был так напуган, что инстинктивно ковырял лапами землю, явно пытаясь закопаться. ― Почтенный Братец Лис ― сказал Братец Кролик младший, гордясь собой ― Вы перепутали меня с моим отцом. И хотя, как сын, я испытываю негодование, тем не менее я счел своим долгом разве... Братец Лис рванул с места, не произнеся и звука, и Братец Кролик младший закачался в его зубах. ― Суеверия... предрашшудки... штарею... ― бормотал Братец Лис с набитым ртом. ― Хорошо хоть, быштрота реакции ошталась при мне... Добавляем в файл win64a.inc следующие строки Код (ASM): include gdi32.inc includelib gdi32.lib ;----------------------- pushadr MACRO x db 68h dd x ENDM Функция TextOut ― стандартная функция вывода текста в Windows. Выводит текст в заданных координатах, использует выбранный шрифт. Прототип функции: Код (C): BOOL TextOut( HDC hdc, int x, // логическая x-координата начала текста int y, // логическая y-координата начала текста LPCTSTR lpszString, // Указатель на символьную строку int nCount // число байт в строке ); Установка цвета текста и фонаЕсли не предпринимать никаких усилий ― тогда функция TextOut выводит текст на экран черным цветом на текущем фоне окна. Но цвет текста и фона можно изменить при помощи функций SetTextColor и SetBkColor. Прототип функции SetTextColor: Код (C): COLORREF SetTextColor(HDC hdc, COLORREF crColor); Функция устанавливает цвет текста выбранный пользователем. Система использует этот цвет текста при выводе текста на этом контексте устройства и также при преобразовании точечных рисунков между цветными и одноцветными контекстами устройства. Прототип функции SetBkColor: Код (C): OLORREF SetBkColor(HDC hdc, COLORREF crColor); Функция устанавливает цвет фона выбранный пользователем. Если функцией SetBkMode установлен режим OPAQUE, система использует цвет фона, чтобы заполнить промежутки в стилях линий, промежутки между заштрихованными линиями в кистях, и фоновом режиме в символьных ячейках. Система также использует фоновый цвет при преобразовании точечных рисунков между цветными и одноцветными контекстами устройства. Если устройство не может предоставить цвет, выбранный функциями SetTextColor и SetBkColor, тогда система установит цвет текста или цвет фона к ближайшему физическому цвету. Функция SetTextColor устанавливает текущий цвет текста для устройства, ассоциированного с контекстом hdc. Цвет задается параметром crColor (при этом устройство может выбирать реальный цвет, ближайший к заданному, который устройство в состоянии отобразить). Функция SetBkColor устанавливает цвет фона (или ближайший к нему возможный для устройства), задаваемый параметром crColor. Обе функции возвращают предыдущий цвет текста и фона, а при возникновении ошибки ― значение CLR_INVALID Цвет задается 32-разрядным значением типа COLORREF, которое кодируется следующим образом: старшиймладшийБайты3210СодержимоеЗаполнен нулямиИнтенсивность синегоИнтенсивность зеленогоИнтенсивность красногоЦвет получают как сумму основных цветовых компонент в разных соотношениях. Интенсивность каждого цветовой компоненты задается в диапазоне от 0 до 0FFh. Видеосистема компьютера (как и цветное телевидение) основывается на трехкомпонентной модели цвета (RGB-модель), которая позволяет получить большинство существующих в природе цветовых тонов сложением чистых красного, синего и зеленого цветовых тонов в разных соотношениях яркости. При одинаковой яркости всех трех компонент получается черно-белая гамма в диапазоне от черного до белого максимальной яркости. Количество градаций яркости компонентов и соответственно количество результирующих тонов зависят от видеорежима, а точнее ― от количества бит, отводимых под хранение информации под цветовую компоненту. Для выбора и использования встроенных шрифтов программа должна создать дескриптор шрифта ― переменную типа HFONT. Получить его можно при помощи функции GetCurrentObject с параметрами hdc и OBJ_FONT Затем необходимо загрузить требуемый шрифт при помощи функции SelectObject Получение метрик текста Большинство шрифтов в Windows являются пропорциональными, поэтому символы одного и того же кегля могут иметь разную ширину. Кроме того, высота символов и размер нижних выносных элементов (буквы р, ц, у, щ, д) могут быть разными у разных шрифтов. Разными могут быть и расстояния между строками текста. Windows требует, чтобы программист самостоятельно управлял практически всем процессом отображения текста. Windows обеспечивает только минимальную поддержку отображения текста в рабочей области окна. Основной функцией вывода текста является TextOut. Эта функция выводит строку текста на экран начиная с указанной позиции. TextOut никак не форматирует выводимый текст, не различает символы "возврат каретки" и "переход в начало строки". Управление выводом текста в рабочую область окна полностью возложена на программиста. Размеры шрифта могут быть разными, сами шрифты могут меняться в процессе выполнения программы. Для определения высоты шрифта и интервала между строками используют функцию GetTextMetrics. Функция имеет следующий прототип Код (C): BOOL GetMetrics( HDC hdc,// дескриптор контекста отображения LPTEXTMETRIC lpTAtrib //указатель на структуру типа TEXTMETRIC ); при успешном завершении функции структура типа TEXTMETRIC будет содержать все параметры (метрики) выбранного текста. Структура TEXTMETRIC определена следующим образом Код (ASM): TEXTMETRICA STRUCT tmHeight DWORD ?;полная высота шрифта tmAscent DWORD ?;высота над основной линией tmDescent DWORD ?;размер нижнего выступа tmInternalLeading DWORD ?;размер верхнего выступа tmExternalLeading DWORD ?;междустрочный интервал tmAveCharWidth DWORD ?;средняя ширина символа tmMaxCharWidth DWORD ?;максимальная ширина символа tmWeight DWORD ?;насыщенность шрифта tmOverhang DWORD ?;дополнительная ширина символа для специальных шрифтов tmDigitizedAspectX DWORD ?;горизонтальный аспект? tmDigitizedAspectY DWORD ?;вертикальный аспект? tmFirstChar BYTE ?;первый символ tmLastChar BYTE ?;последний символ tmDefaultChar BYTE ?;символ по умолчанию tmBreakChar BYTE ?;символ для обозначения границы слова tmItalic BYTE ?;не 0 если шрифт курсив (italic) tmUnderlined BYTE ?;не 0, если подчеркнутый шрифт tmStruckOut BYTE ?;не 0, если шрифт зачеркнутый tmPitchAndFamily BYTE ?;семейство и гарнитура tmCharSet BYTE ?;идентификатор множества символов TEXTMETRICA ENDS Большинство атрибутов получаемых программой не используются. Наиболее важные атрибуты ― для определения расстояния между строками текста. Для определения начала следующей строки вызывают GetTextMetric, получают значение высоты символа tmHeight и межстрочного интервала tmExternalLeading. Их сумма ― расстояние между строками в логических единицах. Для вычисления длинны текста в логических единицах используется функция GetTextExtentPoint32. Функция имеет следующий прототип Код (C): BOOL GetTextExtentPoint32( HDC hdc, //контекст устройства вывода LPCSTR lpszString,//указатель на строку длинну которой хотят узнать int len, //количество символов в строке LPSIZE lpSize //структура типа POINT куда будут записаны вычисленные ширина и высота ); После выхода из функции GetTextExtentPoint32 поле x структуры POINT содержит длину строки, которую можно использовать для определения начала следующей строки, если требуется выводить следующую строку, начиная с того места, где окончилась предыдущая строка. Изменение шрифтов Для создания собственного шрифта используются функции CreatFont, CreatFontIndirect, CreatFontIndirectEx. Функция CreatFont использует для создания логического шрифта 14 параметров, поэтому не слишком удобна в использовании. Вместо нее лучше использовать функцию CreatFontIndirect Эта функция получает указатель на структуру LOGFONT в которой упакованы эти же 14 параметров. Функция имеет следующий прототип Код (C): HFONT CreateFontIndirect( CONST LOGFONT* lplf ); Структура LOGFONT определена следующим образом Код (ASM): LOGFONTA STRUCT lfHeight DWORD ? lfWidth DWORD ? lfEscapement DWORD ? lfOrientation DWORD ? lfWeight DWORD ? lfItalic BYTE ? lfUnderline BYTE ? lfStrikeOut BYTE ? lfCharSet BYTE ? lfOutPrecision BYTE ? lfClipPrecision BYTE ? lfQuality BYTE ? lfPitchAndFamily BYTE ? lfFaceName BYTE LF_FACESIZE dup(?) BYTE 4 dup(?) LOGFONTA ENDS LOGFONT TYPEDEF LOGFONTA LPLOGFONT TYPEDEF PTR LOGFONT Члены структуры LOGFONT Требуемая высота шрифта задается параметром lfHeight, а ширина параметром lfWidth. Если параметр lfWidth равен нулю, Windows автоматически подставляет значение, определяемое текущими пропорциями шрифта. Значения lfHeight и lfWidth задаются в логических единицах. lfHeight ― желаемая высота символов (включая поле, отведенное для специальных знаков над символами, но не включая поле, установленное для межстрочного интервала). Поскольку размер шрифта в пунктах ― это и есть высота шрифта без величины поля, отведенного для специальных знаков над символами, то здесь вы на самом деле определяете значение межстрочного интервала. Вы можете установить значение lfHeight равным 0 для задания размера по умолчанию. Если вы зададите значение lfHeight отрицательным числом, Windows использует абсолютное значение этого числа в качестве желаемого размера высоты шрифта, а не как межстрочный интервал. Если вы хотите задать конкретное значение размера в пунктах, его надо преобразовать в логические единицы, и поле lfHeight установить в отрицательное значение результата lfWidth ― желаемая ширина символов. В большинстве случаев его устанавливают в 0 и позволяют Windows выбирать шрифт, основываясь исключительно на высоте. Угол наклона текста в окне может быть любым. Угол по отношению к горизонтальной оси задается параметром nEscapement. Для текста, отображаемого горизонтально, этот параметр должен быть равен нулю. Иначе он определяет угол наклона текста в десятых долях градуса. Значение nEscapement = 900 определяет угол наклона текста 90o. Угол наклона для каждого символа указывается при помощи параметра lfOrientation. Этот параметр задает угол наклона каждого символа по отношению к горизонтальной оси в десятых долях градуса. Параметр lfWeight задает насыщенность (жирность) шрифта и может принимать значения от 0 до 900. Для насыщенности шрифта существуют следующие константы: FW_DONTCARE0насыщенность по умолчаниюFW_THIN100FW_EXTRALIGHT, FW_ULTRALIGHT200FW_LIGHT300FW_NORMAL, FW_REGULAR400нормальная насыщенностьFW_MEDIUM500FW_SEMIBOLD, FW_DEMIBOLD600FW_BOLD700жирный шрифтFW_EXTRABOLD, FW_ULTRABOLD800FW_HEAVY, FW_BLACK900супержирный шрифт Для создания наклонного шрифта (курсив) задают ненулевое значение параметра lfItalic. Если параметр lfUnderline не равен нулю ― создается подчеркнутый шрифт. Для создания шрифта, все символы которого зачеркнуты горизонтальной линией, необходимо задать ненулевое значение для параметра lfStrikeOut. lfCharSet ― определяет множество символов шрифта ANSI_CHARSET0DEFAULT_CHARSET1SYMBOL_CHARSET2MAC_CHARSET77SHIFTJIS_CHARSET128японские шрифтыHANGEUL_CHARSET JOHAB_CHARSET130 129корейские шрифтыGB2312_CHARSET CHINESEBIG5_CHARSET134 136китайские шрифтыGREEK_CHARSET161греческие шрифтыTURKISH_CHARSET162турецкие шрифтыVIETNAMESE_CHARSET163вьетнамские шрифтыHEBREW_CHARSET177еврейские шрифтыARABIC_CHARSET178арабская вязьBALTIC_CHARSET EASTEUROPE_CHARSET186 238восточно-европейские шрифтыRUSSIAN_CHARSET204кириллические шрифтыTHAI_CHARSET222тайские шрифтыOEM_CHARSET255 lfOutPrecision ― задает точность отображения шрифта, которая определяет, насколько точно созданный шрифт соответствует задаваемым характеристикам OUT_DEFAULT_PRECIS0OUT_STRING_PRECIS1OUT_CHARACTER_PRECIS2OUT_STROKE_PRECIS3OUT_TT_PRECIS4OUT_DEVICE_PRECIS5OUT_RASTER_PRECIS6OUT_TT_ONLY_PRECIS7OUT_OUTLINE_PRECIS8 lfClipPrecision ― определяет "точность отсечения" шрифта (как будут "отсекаться" символы шрифта, не попадающие в видимую область вывода) hexbinCLIP_DEFAULT_PRECIS000000000CLIP_CHARACTER_PRECIS100000001CLIP_STROKE_PRECIS200000010CLIP_400000100CLIP_800001000CLIP_MASKF00001111CLIP_LH_ANGLES1000010000CLIP_TT_ALWAYS2000100000CLIP_4001000000CLIP_EMBEDDED8010000000 lfQuality ― определяет в какой степени создаваемый логический шрифт будет соответствовать физическим шрифтам, доступным для данного устройства вывода DEFAULT_QUALITY0DRAFT_QUALITY1PROOF_QUALITY2 lfPitchAndFamily ― задает тип и семейство шрифта. binhexописаниешрифтсемей- ствотипDEFAULT_PITCHXXXX0000X0шаг по умолчанию, который зависит от реализацииFIXED_PITCHXXXX0001X1Моноширинный, или непропорциональный шрифт, все знаки (кегельные площадки знаков) которого имеют одинаковую ширину«Courier», «Courier New», «Lucida Console», «Terminus», «Monaco»VARIABLE_PITCHXXXX0010X2шрифт, знаки (кегельные площадки знаков) которого имеют разную ширину в зависимости от пропорций буквX_PITCHXXXX0100X4?X_PITCHXXXX1000X8?FF_DONTCARE0000XXXX0Xшрифт по умолчанию, который зависит от реализацииFF_ROMAN0001XXXX1XШрифты с изменяемой шириной хода, которые пропорциональны фактической ширине глифов и имеют засечки.«MS Serif»FF_SWISS0010XXXX2XШрифты с изменяемой шириной хода, которые пропорциональны фактической ширине глифов и не имеют засечек.«MS Sans Serif»FF_MODERN0011XXXX3XШрифты с постоянной шириной хода, с или без засечек. Шрифты с фиксированной шириной обычно являются шрифтом типа «модерн»«Pica», «Elite», «Courier New»FF_SCRIPT0100XXXX4XШрифты, предназначенные для того, чтобы выглядеть как рукописные«Script», «Cursive».FF_DECORATIVE0101XXXX5Xновые шрифты.«Old English»Значения параметра формируются из FF_XX и XX_PITCH при помощи оператора «or» lfFaceName ― указатель на массив типа BYTE содержащий имя шрифта (например, «Courier New», «Arial» или «Times New Roman»). Строка не может быть длинее 32 символов При успешном завершении функции CreateFont, CreatFontIndirect, CreatFontIndirectEx возвращают дескриптор созданного логического шрифта. В случае ошибки будет возвращен 0. Шрифты, создаваемые при помощи CreateFont, CreateFont, CreatFontIndirect, CreatFontIndirectEx должны быть удалены, прежде чем программа завершит свое выполнение. Для удаления шрифта используется функция DeleteObject Для создания собственного шрифта используется функция CreatFont Код (C): HFONT CreateFont( int lfHeight, /* желаемая высота символов (включая поле, отведенное для специальных знаков над символами, но не включая поле, установленное для межстрочного интервала) в логических единицах. Поскольку размер шрифта в пунктах — это и есть высота шрифта без величины поля, отведенного для специальных знаков над символами, то здесь вы на самом деле определяете значение межстрочного интервала. Вы можете установить значение lfHeight равным 0 для задания размера по умолчанию. Если вы зададите значение lfHeight отрицательным числом, Windows использует абсолютное значение этого числа в качестве желаемого размера высоты шрифта, а не как межстрочный интервал. Если вы хотите задать конкретное значение размера в пунктах, его надо преобразовать в логические единицы, и поле lfHeight установить в отрицательное значение результата */ int lfWidth, /* желаемая ширина символов в логических единицах. В большинстве случаев его устанавливают в 0 и позволяют Windows выбирать шрифт, основываясь исключительно на высоте */ int nEscapement, /* угол по отношению к горизонтальной оси. Для текста, отображаемого горизонтально, этот параметр должен быть равен нулю. В противном случае он определяет угол наклона текста в десятых долях градуса, значение 900 определяет угол наклона, равный 90 градусам */ int lfOrientation, // определяет угол поворота отдельных символов в десятых долях градуса int lfWeight, QWORD lfItalic, // ненулевое значение поля определяет курсив QWORD lfUnderline, // ненулевое значение поля задает подчеркивание QWORD lfStrikeOut, // ненулевое значение поля определяет шрифт с зачеркиванием символов QWORD lfCharSet, QWORD lfOutPrecision, QWORD lfClipPrecision, QWORD lfQuality, QWORD lfPitchAndFamily, LPCSTR lfFaceName // указатель на массив типа BYTE содержащий имя шрифта ); Параметры функции CreateFont lfWeight ― это поле позволяет задать насыщенность (жирность) шрифта и может принимать значения от 0 до 1000. Для насыщенности шрифта существуют следующие константы: lfCharSet ― определяет множество символов шрифта lfOutPrecision ― задает точность отображения шрифта, которая определяет, насколько точно созданный шрифт соответствует задаваемым характеристикам lfClipPrecision ― определяет "точность отсечения" шрифта (как будут "отсекаться" символы шрифта, не попадающие в видимую область вывода) lfQuality ― определяет в какой степени создаваемый логический шрифт будет соответствовать физическим шрифтам, доступным для данного устройства вывода lfPitchAndFamily ― задает тип и семейство шрифта При успешном завершении CreateFont возвращает дескриптор созданного логического шрифта. В случае ошибки будет возвращен 0. Шрифты, создаваемые при помощи CreateFont, должны быть удалены, прежде чем программа завершит свое выполнение. Для удаления шрифта используется функция DeleteObject
Скачайте пример здесь. Код (ASM): ; GUI # include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov ecx,offset FileName invoke LoadCursorFromFile mov esi,IMAGE_BASE mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push COLOR_WINDOW;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub rsp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp ;---------------------------------------- WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM local ps:PAINTSTRUCT mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_CREATE je wmCREATE cmp edx,WM_PAINT je wmPAINT leave jmp NtdllDefWindowProc_ wmDESTROY:invoke SelectObject,hdc,hFont invoke DeleteObject,newFont invoke RtlExitUserProcess,0 wmCREATE:invoke GetDC mov hdc,rax invoke GetCurrentObject,eax,OBJ_FONT mov hFont,rax ;default font object mov ecx,offset lf invoke CreateFontIndirect mov newFont,rax invoke SelectObject,hdc,eax invoke SetTextColor,hdc,32C8C8h invoke SetBkColor,hdc,0FF0000h jmp wmBYE wmPAINT:lea edx,ps invoke BeginPaint mov qword ptr [rbp-60h],sizeof expTxt ;help us to count the string length mov r9d,offset expTxt invoke TextOut,hdc,0,0 lea edx,ps invoke EndPaint,hWnd wmBYE: leave retn WndProc endp ;----------------------------------------- ClassName db "Uncle Remus tales:#5d More about Text",0 expTxt db "Win64 assembly with MASM is great and easy",0 FileName db "..\Images\br_Rabbit3.cur",0 hdc HDC ? hFont dq ? newFont dq ? lf LOGFONT <26,12,0,0,400,0,0,0,OEM_CHARSET,0,0,0,DEFAULT_PITCH or FF_SCRIPT,"script"> end asm-файл программа демонстрирует работу с логическими шрифтами. При выборе команды меню сменить шрифт выбирается и отображается новый шрифт. Код (ASM): ; GUI # include win64a.inc ID_RESET equ 0 ID_FONT equ 1 ID_HELP equ 2 ID_EXIT equ 3 IDM_MENU equ 37 IDC_ICON1 equ 500 IDR_MAINACCEL equ 105 .code WinMain proc local msg:MSG xor ebx,ebx mov ecx,offset FileName invoke LoadCursorFromFile mov esi,IMAGE_BASE mov edi,offset ClassName push rax ;hIconSm push rdi ;lpszClassName push IDM_MENU;lpszMenuName push COLOR_WINDOW;hbrBackground push 10003h ;hCursor push rax ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shl esi,9 ;rsi=CW_USEDEFAULT push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE mov hwnd,rax invoke LoadAccelerators,IMAGE_BASE,IDR_MAINACCEL mov ACC,rax lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke TranslateAccelerator,hwnd,ACC,edi or eax,eax jne @b invoke TranslateMessage,edi;разрешить использование клавиатуры invoke DispatchMessage,edi jmp @b WinMain endp ;---------------------------------------- WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM local ps:PAINTSTRUCT local hDC:qword local size0:POINT local string[255]:byte local cf:CHOOSEFONT local lf:LOGFONT mov hWnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_CREATE je wmCREATE cmp edx,WM_PAINT je wmPAINT cmp edx,WM_COMMAND je wmCOMMAND leave jmp NtdllDefWindowProc_ wmDESTROY:invoke DeleteDC,memDC invoke DeleteObject,hFont invoke RtlExitUserProcess,0 wmCOMMAND:and r8d,11111y;wParam cmp r8d,ID_EXIT ja wmBYE jmp handler[r8*8] FONT: push rdi xor eax,eax mov ecx,(sizeof CHOOSEFONT)/8 lea edi,cf rep stosq pop rdi; ZeroMemory lea ecx,cf mov [rcx].CHOOSEFONT.lStructSize,sizeof CHOOSEFONT mov rax,hwnd mov [rcx].CHOOSEFONT.hwndOwner,rax lea eax,lf mov [rcx].CHOOSEFONT.lpLogFont,rax mov [rcx].CHOOSEFONT.Flags,CF_SCREENFONTS or CF_EFFECTS; invoke ChooseFont or eax,eax jz wmBYE ;удаляем если он установлен предыдущий шрифт mov rcx,hFont jecxz @f invoke DeleteObject @@: lea ecx,lf invoke CreateFontIndirect mov hFont,rax invoke SelectObject,memDC,eax ;устанавливаем заданный цвет текста и прозрачный фон invoke SetTextColor,memDC,cf.rgbColors ;получить метрики текста mov edx,offset tm invoke GetTextMetrics,memDC lea ecx,string mov edx,offset fmt1 lea r8d,lf.lfFaceName mov r9d,tm.tmHeight invoke wsprintf ;выводим строку lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax mov qword ptr [rsp+20h],sizeof string1 lea r9d,string1 invoke TextOut,memDC,X,Y ;определить длину строки mov r8d,sizeof string1 mov edx,offset string1 lea r9d,size0 invoke GetTextExtentPoint32,memDC mov eax,size0.x mov X,rax lea ecx,string mov edx,offset fmt2 mov r8d,size0.x invoke wsprintf lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax and X,0 lea ecx,string mov edx,offset fmt3 invoke wsprintf,,,maxX,maxY lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax invoke InvalidateRect,hWnd,0,1 jmp wmBYE RESET:;стираем с экрана and X,0 and Y,0 invoke PatBlt,memDC,0,0,maxX,maxY,PATCOPY invoke InvalidateRect,hWnd,0,TRUE jmp wmBYE HELP: mov ecx,offset mb invoke MessageBoxIndirect jmp wmBYE wmCREATE:invoke GetSystemMetrics,SM_CXSCREEN mov maxX,rax invoke GetSystemMetrics,SM_CYSCREEN mov maxY,rax invoke GetDC,hWnd mov hDC,rax invoke CreateCompatibleDC,eax mov memDC,rax invoke CreateCompatibleBitmap,hDC,maxX,maxY invoke SelectObject,memDC,eax invoke GetStockObject,WHITE_BRUSH invoke SelectObject,memDC,eax invoke PatBlt,memDC,0,0,maxX,maxY,PATCOPY invoke SetBkMode,memDC,TRANSPARENT invoke ReleaseDC,hWnd,hDC jmp wmBYE wmPAINT:lea edx,ps invoke BeginPaint invoke BitBlt,eax,0,0,maxX,maxY,memDC,0,0,SRCCOPY lea edx,ps invoke EndPaint,hWnd wmBYE: leave retn handler dq RESET,FONT,HELP,wmDESTROY WndProc endp ;----------------------------------------- ClassName db "Uncle Remus tales:#5e Work with Font",0 expTxt db "Win64 assembly with MASM is great and easy",0 FileName db "..\Images\br_Rabbit3.cur",0 hwnd dq ? fmt1 db 'Font height "%s" is %d pixels',0;"Высота шрифта %s равна %d пикселов",0 string1 db "This is the next line.",0;"Это следующая строка.",0 fmt2 db "Length of the previous line is %d units",0;"Длина предыдущей строки %d единиц",0 fmt3 db "Screen size is %d by %d",0;"Размер экрана %d на %d",0 ACC dq ? tm TEXTMETRICA <> X dq 0 Y dq 0 maxX dq ? maxY dq ? memDC dq ? hFont dq 0 MBText db "F1: Help",10,"F2: Choose font",10,"F3: Clear screen",10,\ "Ctrl+X: Exit",0 mb label MSGBOXPARAMS dd sizeof MSGBOXPARAMS,?;cbSize dq 0 ;hwndOwner dq IMAGE_BASE ;hInstance dq MBText ;lpszText dq ClassName ;lpszCaption dd MB_OK or MB_USERICON or MB_TOPMOST,?;dwStyle dq IDC_ICON1 ;lpszIcon dd 0,? ;dwContextHelpId dq 0 ;lpfnMsgBoxCallback dd 0,? ;dwLanguageId end rc-файл Код (C): #include "resource.h" #define ID_RESET 0 #define ID_FONT 1 #define ID_HELP 2 #define ID_EXIT 3 #define IDM_MENU 37 #define IDC_ICON1 500 #define IDR_MAINACCEL 105 IDC_ICON1 ICON DISCARDABLE "..\\Images\\br_Bear4.ico" IDM_MENU MENU { POPUP "&Font" { MENUITEM "Choose &font\tF2",ID_FONT MENUITEM "&Clear Screeen\tF3",ID_RESET MENUITEM "&Help\tF1",ID_HELP MENUITEM SEPARATOR MENUITEM "E&xit\tCtrl+X",ID_EXIT } MENUITEM "E&xit",ID_EXIT } IDR_MAINACCEL ACCELERATORS DISCARDABLE { // определение акселераторов VK_F1, ID_HELP,VIRTKEY VK_F2, ID_FONT,VIRTKEY VK_F3, ID_RESET,VIRTKEY "^X", ID_EXIT } Работа с виртуальным окном После создания виртуального окна вывод программы направляется в виртуальное окно. Код (ASM): ;получить метрики текста mov edx,offset tm invoke GetTextMetrics,memDC lea ecx,string mov edx,offset fmt1 lea r8d,lf.lfFaceName mov r9d,tm.tmHeight invoke wsprintf ;выводим строку lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax mov qword ptr [rsp+20h],sizeof string1 lea r9d,string1 invoke TextOut,memDC,X,Y ;определить длину строки mov r8d,sizeof string1 mov edx,offset string1 lea r9d,size0 invoke GetTextExtentPoint32,memDC mov eax,size0.x mov X,rax lea ecx,string mov edx,offset fmt2 mov r8d,size0.x invoke wsprintf lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax and X,0 lea ecx,string mov edx,offset fmt3 invoke wsprintf,,,maxX,maxY lea r9d,string invoke TextOut,memDC,X,Y,,rax ;рассчитываем положение следующей строки mov eax,tm.tmHeight add eax,tm.tmExternalLeading add Y,rax invoke InvalidateRect,hWnd,0,1 Весь вывод будет направлен на устройство соответствующее контексту memDC, после чего вызывается функция InvalidateRect для перерисовки реального окна. Режим рисования фона установлен в TRANSPARENT, поэтому текст отображаемый в окне, не изменяет цвет фона. При получении сообщения WM_PAINT содержимое виртуального окна копируется в реальное окно на экране Код (ASM): wmPAINT:;перерисовка окна lea edx,ps invoke BeginPaint ;копируем растр из памяти на экран invoke BitBlt,eax,0,0,maxX,maxY,memDC,0,0,SRCCOPY lea edx,ps ;освобождаем контекст устройства invoke EndPaint,hWnd при копировании изображения из memDC в DC используется функция BitBlt. Параметр SRCCOPY определяет процесс копирования "как есть". Весь вывод программы направлялся контексту memDC, после этой операции выводимая информация отображается на экране. Если окно было перекрыто другим окном, а затем восстановлено, будет получено сообщение WM_PAINT и содержимое окна автоматически восстановится. © Mikl___ 2021
Глава десятая. Вращающийся текст Понимаешь, Братец Кролик, женщины ― это... задумчиво протянул Братец Лис, подергивая левым ухом. Братец Кролик устало вздохнул. ― А может, ты просто съешь меня, и все? ― спросил он. ― Прекрати издеваться. ― Если б я хотел тебя просто съесть, я бы и съел! ― обозлился Братец Лис. ― А я хочу побеседовать. Ну, а потом уже съесть. Меня Лисица задрала, понимаешь? Эх, да что ты понимаешь... Твоя-то ― Крольчиха, а моя ― Лиса. А иногда кажется, что просто Гиена... Вынь да положь ей кролика. Каждый день ― неси ей кролика. А не принес кролика ― душу вынет... ― Да... Братец Кролик из дипломатических соображений постарался попасть в тон Братцу Лису, что было довольно трудно, так как Братец Лис придавил его к земле двумя лапами. ― Моя тоже... требует Кролика. Но... кхм... в немножко другом плане. И знаешь, тоже нелегко. Даже очень... Оба вздохнули, думая о своем. В сердце у каждого разгоралась противоестественная зависть. "Лучше бы сожрала!" ― думал Братец Кролик. "Пусть бы затрахала!" ― думал Братец Лис. Внезапно раздался грохот и над ухом у Братца Лиса просвистела пуля. Братец Кролик и Братец Лис разлетелись в разные стороны и пустились от охотников наутек. Домой. К женам. А теперь заставим текст из пятой главы вращаться. Проблема состоит в том, что Windows позволяет вращать текст вокруг левого нижнего угла указанного текста, а хотелось бы, чтобы центр вращения был в центре строки. Для начала вычислим "центр тяжести" нашей строки ― получить вертикальный и горизонтальный размер текста можно: либо добавив функцию GetTextExtentPoint32 в текст программы из четвертого урока Код (ASM): local local expSize:POINT . . . mov edx,offset expTxt mov r8d,sizeof expTxt invoke GetTextExtentPoint32,ps.hdc,,,&expSize В полях x и y структуры POINT функция GetTextExtentPoint32 вернет вертикальный и горизонтальный размер текста; либо скопировать окошко в PaintBrush и тупо подсчитать размер в пикселях по-вертикали и по-горизонтали Вертикальный размер равен высоте шрифта 26, длина строки ― 453 пиксела, "центр тяжести" лежит на середине гипотенузы 4532 + 262 = 205209 + 676 = 205885 [math]\sqrt{205885} = 453,74552339389531436197386053733[/math] 453,74552339389531436197386053733×0,5 = 226,87276169694765718098693026867 ≈ 227 При вращении текста нам приходится оперировать с двумя углами: AngleLine ― угол наклона строки и AngleChars ― угол наклона букв в строке. AngleLine опережает AngleChars на 180о. AngleChars должен быть в градусах, AngleLine ― в радианах для вычисления синуса и косинуса. При этом, также придется учесть, что повороте на угол больше 360о величину AngleChars необходимо обнулять, а вот при вычислении синуса и косинуса такую поправку вносить не нужно (команда fsincos эту поправку делает автоматически). При изменении размера экрана равномерность поворота (1,6о в 50 mSec) нарушается ― поэтому приходится синхронизировать AngleLine и AngleChars. Код (ASM): ; GUI # include win64a.inc .code WinMain proc local msg:MSG xor ebx,ebx mov esi,IMAGE_BASE mov edi,offset ClassName mov ecx,offset FileName invoke LoadCursorFromFile mov r12,rax mov ecx,0FF0000h invoke CreateSolidBrush push r12 ;hIconSm push rdi ;lpszClassName push rbx ;lpszMenuName push rax ;hbrBackground push 10003h ;hCursor push r12 ;hIcon push rsi ;hInstance push rbx ;cbClsExtra & cbWndExtra pushaddr WndProc ;lpfnWndProc push sizeof WNDCLASSEX;cbSize & style invoke RegisterClassEx,esp ;addr WNDCLASSEX push rbx push rsi ;rsi=400000h shr esi,7 ;Special CreateWindow position value CW_USEDEFAULT=8000h push rbx push rbx push rsi push rsi push rsi push rsi sub esp,20h invoke CreateWindowEx,0,edi,edi,WS_OVERLAPPEDWINDOW or WS_VISIBLE ;создаем таймер #0 на 50mSec invoke SetTimer,eax,0,50,0 ; +---------------------------+ ; | entering the message loop | ; +---------------------------+ lea edi,msg @@: invoke GetMessage,edi,0,0,0 invoke DispatchMessage,edi jmp @b WinMain endp WndProc proc hwnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM local ps:PAINTSTRUCT local newFont:qword local oldFont:qword local x1:dword local y1:dword local hDC:qword local lpPoint:POINT mov hwnd,rcx cmp edx,WM_DESTROY je wmDESTROY cmp edx,WM_PAINT je wmPAINT cmp edx,WM_SIZE je wmSIZE cmp edx,WM_TIMER je wmTIMER leave jmp NtdllDefWindowProc_ wmDESTROY:invoke KillTimer,,0;уничтожаем таймер #0 invoke RtlExitUserProcess,0 wmSIZE: mov edx,offset expRect invoke GetClientRect ;получаем размеры клиентской области. ;Размеры возвращаются в переменную expRect mov eax,expRect.bottom shr eax,1 mov y,eax ;y-координата середины экрана mov eax,expRect.right shr eax,1 mov x,eax ;x-координата середины экрана jmp wmBYE wmTIMER:fninit fld angle2;угол наклона строки в радианах fadd pi_mul_1_6_div_180 ;увеличиваем угол наклона строки на 1,6 градуса fst angle2 fmul _1800_div_pi ;переводим радианы в градусы fsub _1800 ;угол наклона букв в строке отстает от угла наклона строки на 90 градусов fld st(0) fmul one_div_3600 fsubp st(1),st(0) fistp angle ;делим угол наклона букв на 360 градусов и запоминаем остаток invoke InvalidateRect,,0,TRUE;перерисовываем текст с текущим значением угла jmp wmBYE wmPAINT:lea edx,ps invoke BeginPaint mov hDC,rax invoke SetTextColor,hDC,32C8C8h;RGB=50,200,200 золотистые буквы invoke SetBkColor,hDC,0FF0000h;RGB=0,0,255 на синем фоне ;создаем шрифт с углом вращения, указанным в lfEscapement и lfOrientation pushaddr expFont push DEFAULT_PITCH or FF_SCRIPT push rbx ;DEFAULT_QUALITY=0 push rbx ;CLIP_DEFAULT_PRECIS=0 push rbx ;OUT_DEFAULT_PRECIS=0 push OEM_CHARSET push rbx push rbx push rbx push 400 sub esp,20h invoke CreateFont,26,12,angle,r8 mov newFont,rax invoke SelectObject,hDC,eax mov oldFont,rax ;---------вывожу текст mov qword ptr [rsp+20h],sizeof expTxt;длина строки mov r9d,offset expTxt ;адрес строки ;---------рассчитываю положение начала текста fld angle2;угол наклона строки в радианах fsincos ;в st(0) синус угла, в st(1) косинус mov y1,227 ;половина гипотенузы fimul y1 ;гипотенуза * sin = x fistp x1 mov edx,x add edx,x1 fimul y1 ;гипотенуза * cos = y fistp y1 ;-y mov r8d,y sub r8d,y1 invoke TextOut,hDC invoke DeleteObject,newFont ;delete the new font invoke SelectObject,hDC,oldFont;return the old font to the system invoke EndPaint,hwnd,&ps wmBYE:leave retn WndProc endp ;--------------------------------------- ClassName db "Uncle Remus tales:#5 Painting with Rotation Text",0 expTxt db "Win64 assembly with MASM is great and easy!",0 expFont db "script",0 angle dq 0 angle2 dq 3.14159265358979323846264338328 pi_mul_1_6_div_180 dq 0.02792526803190927323077905229;pi*1,6/180 _1800_div_pi dq 572.957795130823208767981548141;1800/pi _1800 dq 1800.0 one_div_3600 dq 0.00027777777777777777777777778;1/3600 ; hIcon dq ? FileName db "br_Rabbit3.cur",0 expRect RECT <> x dd ? y dd ? end © Mikl___ 2021
Глава одиннадцатая. Как Братец Кролик выводил текст на экран всеми возможными способами Проблема не в том, что можно 10 способами вывести текст на экран. Проблема начинается тогда, когда вы пытаетесь вывести на экран длинный, многострочный текст... (фраза приписываемая Джоэлю Чандлеру Харрису — американскому журналисту, писателю и фольклористу, автору сказок о дядюшке Римусе) добавляем в win64a.inc строки Код (ASM): include comctl32.inc includelib comctl32.lib Вывод текста в дочернее окно класса "STATIC" Создаем дочернее окно класса "STATIC" и выводим текст в поле отвечающее за заголовок Код (ASM): .data expTxt0 db "Было еще не так поздно, а госпожа Салли, мама семилетнего Джоэля, уже" ... ;создания элемента STATIC push rbx push IMAGE_BASE push rbx push hWnd push BOTTOM+100 push RIGHT push TOP+25 push LEFT sub esp,20h invoke CreateWindowEx,0,&aStatic,&expTxt0,SS_LEFT or WS_CHILD or WS_VISIBLE mov hStaticText1,rax "Стереть" такой текст можно сделав окно невидимым Код (ASM): invoke ShowWindow,hStaticText1,SW_HIDE Текстовая строка понимает символы перевода и табуляции строки, автоматически переносит слова, не требует обработки сообщения WM_PAINT Вывод текста при помощи функции SendMessage Создаем еще одно дочернее окно класса "STATIC" с пустым заголовком Код (ASM): ;создания элемента STATIC для вывода текста функцией SendMessage push rbx push IMAGE_BASE push rbx push hWnd push BOTTOM push RIGHT;640 push TOP+25 push LEFT sub esp,20h invoke CreateWindowEx,0,&aStatic,0,SS_LEFT or WS_CHILD; or WS_VISIBLE mov hStaticText2,rax Когда требуется вывести текст ― вызываем функцию SendMessage с параметром WM_SETTEXT Код (ASM): SendMessage1::invoke SendMessage,hStaticText2,WM_SETTEXT,0,&expTxt7 invoke ShowWindow,hStaticText2,SW_SHOW Чтобы "стереть" текст выведенный таким образом ― вызываем функцию SendMessage с указателем на "нулевую строку" Код (ASM): invoke SendMessage,hWnd,WM_SETTEXT,0,&ClassName invoke ShowWindow,hStaticText2,SW_HIDE Текстовая строка понимает символы перевода и табуляции строки, автоматически переносит слова, не требует обработки сообщения WM_PAINT Прототип функции SendMessageA/W: Код (C): LRESULT WINAPI SendMessage( _In_ HWND hWnd, // дескриптор окна, которому посылается сообщение _In_ UINT Msg, // код посылаемого сообщения _In_ WPARAM wParam, // дополнительные параметры сообщения _In_ LPARAM lParam ); Вывод текста при помощи функции DrawTextEx и DrawTextФункции DrawTextEx и DrawText выводят отформатированный текст в заданном прямоугольнике. Форматирование текста происходит согласно заданному методу (дополнительная табуляция, выравнивание символов, переносы строк, и так далее). Прототип функции DrawTextEx Код (C): int DrawTextEx( _In_ HDC hdc, /* дескриптор контекста устройства, в котором происходит прорисовка */ _Inout_ LPTSTR lpchText,/* указатель на строку, которая содержит текст для рисования. Строка должна быть с нулевым символом в конце, если параметр cchText не равен -1 */ _In_ int cchText,/* длина строки, но если cchText = -1 и строка закончена нулевым символом - длина строки рассчитывается автоматически */ _Inout_ LPRECT lprc,/* указатель на структуру RECT, содержащую прямоугольник (в логических координатах), в который выводится отформатированный текст */ _In_ UINT dwDTFormat, // Параметры форматирования _In_ LPDRAWTEXTPARAMS lpDTParams /* указатель на структуру DRAWTEXTPARAMS которая определяет дополнительные опции форматирования. Может быть нулем */ ); Прототип функции DrawText Код (C): int DrawText( _In_ HDC hdc,/* дескриптор контекста устройства, в котором происходит прорисовка */ _Inout_ LPCTSTR lpszString, /* Указатель на строку, которая будет выведена. Если nCount = -1, строка должна быть с нулевым символом в конце */ _In_ int nCount, /* Определяет количество символов в строке. Если nCount -1, то lpszString принят, чтобы быть длинным указателем на строку с нулевым символом в конце, и DrawText вычисляет символьный счет автоматически */ _Inout_ LPRECT lpRect, /* указатель на структуру RECT, содержащую прямоугольник (в логических координатах), в который выводится отформатированный текст */ _In_ UINT nFormat // Параметры форматирования ); Позиции табуляции преобразуются в соответствующее количество пробелов, текст выравнивается влево, вправо или по центру данного прямоугольника, текст разрывается в строках, которые приспосабливаются внутри данного прямоугольника. Параметр nCount передает количество символов в строке для ANSI-строк ― количество байтов (BYTE) в строке для Unicode-строк ― количество слов (WORD) в строке Для вывода текста функции используют выбранный шрифт, цвет текста и фона из контекста устройства.
Параметры DT_XX (Draw Text) ЗначениеhexПредназначениеDT_TOP0Выравнивает текст по верху прямоугольникаDT_LEFT0Выравнивает текст слеваDT_CENTER1Центрует текст в прямоугольнике по горизонталиDT_RIGHT2Выравнивает текст справаDT_VCENTER4Центрирует текст по вертикали. Это значение используется только со значением DT_SINGLELINEDT_BOTTOM8Выравнивает текст по основанию прямоугольника. Это значение используется только со значением DT_SINGLELINE.DT_WORDBREAK10Строки автоматически разделяются по словам, если те выходят за края прямоугольника, заданного параметром lprc. Символы "Возврат каретки" и "Перевод строки" также разделяют строкуDT_SINGLELINE20Выводит текст на экране как одну строку. Символы "Возврат каретки" и "Перевод строки" игнорируютсяDT_EXPANDTABS40Увеличивает число символов в табуляции. Заданное по умолчанию число символов в табуляции ― восемьDT_TABSTOP80Устанавливает шаги табуляции. Структура DRAWTEXTPARAMS указывает, что параметр lpDTParams задает число символов средней ширины на шаг табуляцииDT_NOCLIP100Вывод текста без отсечения по границам прямоугольника. Функции DrawTextEx/ DrawText отрабатывают немного быстрее, когда используется DT_NOCLIPDT_EXTERNALLEADING200Включает межстрочное расстояние шрифта в высоту строки. Обычно, межстрочное расстояние не включается в высоту строки текста.DT_CALCRECT400Выясняет ширину и высоту прямоугольника. Если имеется несколько строк текста, функции DrawTextEx/DrawText используют ширину прямоугольника, указанную параметром lprc и продлевают основу прямоугольника, чтобы ограничить последнюю строку текста. Если есть только одна строка текста, DrawTextEx/ DrawText изменяют правую сторону прямоугольника так, чтобы она ограничивала последнюю букву в строке. И в том, и в другом случае, DrawTextEx/ DrawText возвращают значение высоты отформатированного текста, но не выводят текст на экранDT_NOPREFIX800Выключает обработку префиксных символов. Обычно, функция DrawTextEx/ DrawText воспринимает символ амперсандта (&) как директиву, чтобы подчеркнуть букву, которая стоит после амперсандта, а двойной амперсандт (&&) как директиву, чтобы отобразить на экране амперсандт (&). Установка параметра DT_NOPREFIX, эту обработку отключает. Сравнивает с флажком DT_HIDEPREFIX и DT_PREFIXONLYDT_INTERNAL1000Использует системный шрифт, чтобы рассчитать метрики текстаDT_EDITCONTROL2000Дублирует характеристики отображения текста в многострочном поле редактирования текста. А именно, средняя ширина символа рассчитывается в той же самой манере, как и для поля редактирования текста, и функция не показывает на экране частично видимую последнюю строкуDT_PATH_ELLIPSIS4000Для отображаемого на экране текста, заменяет буквы (символы) в середине строки на тире так, чтобы результат вместился в заданный прямоугольник. Если строка содержит знак наклонной черты влево ("\"), флажок DT_PATH_ELLIPSIS сохраняет в максимально возможной степени текст после последней наклонной черты влево. Строка не модифицируется, если флажок DT_MODIFYSTRING не установлен. Сравнивается с флажками DT_END_ELLIPSIS и DT_WORD_ELLIPSISDT_END_ELLIPSIS8000В отображаемом на экране тексте конец строки заменился "эллипсисами" так, чтобы результат вместился в заданный прямоугольник. Любое слово (не в конце строки) которое выходит за пределы прямоугольника, обрезается без эллипсисов (тире). Строка не модифицируется, если DT_MODIFYSTRING не установлен. Сравнивается с DT_PATH_ELLIPSIS и DT_WORD_ELLIPSISDT_MODIFYSTRING10000Изменяет заданную строку, чтобы согласовать отображаемый на экране текст. Это значение не имеет никакого действия, если не установлен флажок DT_END_ELLIPSIS или DT_PATH_ELLIPSISDT_RTLREADING20000Изменяет порядок вывода символов на экран на вывод текста справа налево для двунаправленного текста, если шрифт, выбранный в hdc ― арабский или иврит. Заданный по умолчанию порядок чтения для всех текстов ― слева направоDT_WORD_ELLIPSIS40000Обрезает любое слово, которое не вмещается в прямоугольник и добавляет эллипсисы. Сравнивается с флажками DT_END_ELLIPSIS и DT_PATH_ELLIPSISDT_NOFULLWIDTHCHARBREAK80000Не допускает перенос строки в DBCS (символьная строка двойной ширины), так, чтобы правило переноса строки было эквивалентно SBCS строкам (символьная строка одинарной ширины). Например, это свойство может быть использовано в окнах с корейскими шрифтами, для большей удобочитаемости корейских символов. Это значение не имеет никакого действия, если флажок DT_WORDBREAK не установленDT_HIDEPREFIX100000Игнорирует в тексте символ амперсанда (&), но не игнорирует символ двойного амперсанда (&&). Буква, которая следует за амперсандом, не будет подчеркнута, но двойной амперсанд обрабатывается. Например: строка "A&bc&&d" преобразуется функцией DrawText/DrawTextEx в строку "Abc&d", а функция DrawText/DrawTextEx с параметром DT_HIDEPREFIX сформирует строку "Abc&d". Сравнивается с DT_NOPREFIX и DT_PREFIXONLY.DT_PREFIXONLY200000Рисует только подчеркивание в позиции буквы, следующей за амперсантом (&). Не выводит никакие другие символы в строке. Например, введенная строка: "A&bc&&d" преобразуется функцией DrawText/DrawTextEx в строку "Abc&d", а функция DrawText/DrawTextEx с флажком DT_PREFIXONLY сформирует строку " _ " Сравнивает с флажками DT_HIDEPREFIX и DT_NOPREFIXЕсли не используется формат DT_NOCLIP, DrawTextEx и DrawText отсекают текст так, чтобы текст не появился снаружи данного прямоугольника. Если выбран формат DT_SINGLELINE, тогда символ переноса строк игнорируется. Если выбранный шрифт слишком большой для определенного прямоугольника, функции DrawTextEx и DrawText не пытаются заменять его меньшим шрифтом. Если определен флажок DT_CALCRECT, прямоугольник, определенный lpRect будет модифицироваться, чтобы отразить ширину и высоту, необходимую, чтобы вывести текст. Если установлен флажок выравнивания текста TA_UPDATECP, DrawText отобразит текст, начинающийся в текущей позиции, скорее чем налево от данного прямоугольника. DrawText не будет переносить по словам текст, когда установлен флажок TA_UPDATECP (тогда флажок DT_WORDBREAK не будет иметь никакого эффекта). Код (ASM): DrawTextEx1::;вывод текста при помощи функции DrawTextEx invoke DrawTextEx,MemDC,&expTxt5,-1,&expRect,DT_LEFT or DT_WORDBREAK or DT_EXPANDTABS or \ DT_END_ELLIPSIS or DT_MODIFYSTRING or DT_WORD_ELLIPSIS,0 Код (ASM): DrawText1::;вывод текста при помощи функции DrawText invoke DrawText,MemDC,&expTxt1,-1,&expRect,DT_WORDBREAK Вывод текста при помощи функции TextOutПрототип функции TextOut Код (C): BOOL TextOut( __in HDC hdc,/* дескриптор контекста устройства, в котором происходит прорисовка */ __in int x, //логическая x-координата начала строки __in int y, //логическая y-координата начала строки __in LPCTSTR lpszString, //указатель на строку __in int nCount //число символов в строке ); Записывает строку в определенное место экрана, используя выбранный шрифт. Если программа должна модифицировать текущую позицию строки, тогда перед вызовом TextOut, вызывают функцию SetTextAlign с параметром nFlags равным TA_UPDATECP. Когда этот флажок установлен, Windows игнорирует значение параметров x и y при последующих обращениях к TextOut, используя текущую позицию строки. Для вывода нескольких строк с использованием функции TextOut, необходимо знать вертикальный размер символов шрифта. Следующие друг за другом строки текста размещаем с учетом высоты символа. Размеры символов можно получить с помощью функции GetTextMetrics, ее возвращаемым значением является информация о шрифте, выбранном в данное время в контексте устройства.
Прототип функции GetTextMetrics Код (C): BOOL GetTextMetrics( __in HDC hdc, // дескриптор контекста устройства __in LPTEXTMETRIC lpTAttrib // указатель на структуру TEXTMETRIC ); Так как размеры системного шрифта не меняются в течение одного сеанса ― можно вызвать GetTextMetrics один раз при обработке сообщения WM_CREATE Код (ASM): TEXTMETRICA STRUCT tmHeight DWORD ? ;полная высота шрифта tmAscent DWORD ? ;высота над основной линией tmDescent DWORD ? ;размер нижнего выступа tmInternalLeading DWORD ? ;величина пустого пространства, ;отведенного для указания специальных знаков над символом. Если это значение равно 0, ;то помеченные прописные буквы делаются немного меньше, чтобы специальный символ ;поместился внутри верхней части символа tmExternalLeading DWORD ? ;величина пустого пространства, ;которое разработчик шрифта установил для использования между строками символов. tmAveCharWidth DWORD ? ;усредненная ;ширина символов строки tmMaxCharWidth DWORD ? ;ширина самого широкого символа шрифта tmWeight DWORD ? ;насыщенность шрифта tmOverhang DWORD ? ;дополнительная ширина символа - для специальных шрифтов tmDigitizedAspectX DWORD ? ;горизонтальный аспект tmDigitizedAspectY DWORD ? ;вертикальный аспект tmFirstChar BYTE ? ;первый символ tmLastChar BYTE ? ;последний символ tmDefaultChar BYTE ? ;символ по умолчанию tmBreakChar BYTE ? ;символ для обозначения границы слова tmItalic BYTE ? ;не ноль если шрифт курсив (italic) tmUnderlined BYTE ? ;не ноль если буквы подчеркнуты tmStruckOut BYTE ? ;не ноль если буквы зачеркнуты tmPitchAndFamily BYTE ? ;семейство и гарнитура tmCharSet BYTE ? ;идентификатор множества символов TEXTMETRICA ENDS Код (ASM): local tm:TEXTMETRIC ;определили структуру которая будет содержать все параметры выбранного шрифта local y:QWORD ... mov y,100;y-координата первой строки invoke GetTextMetrics,hdc,&tm; получаем информацию о текущем шрифте mov eax,tm.tmHeight add eax,tm.tmExternalLeading mov font_height,eax xor ebx,ebx mov edi,font_height mov y,ebx . . . . TextOut1::;вывод текста при помощи функции TextOut @@: mov r9d,stringtable3[rbx] mov eax,stringtable3[rbx+4] or eax,eax jz a1 sub eax,r9d mov [rsp+20h],rax invoke TextOut,MemDC,LEFT,y add y,edi add ebx,4 jmp @b a1: .... Вывод текста при помощи функции TabbedTextOutЧтобы создать многоколонную таблицу с выровненными столбцами текста используют TabbedTextOut Код (C): LONG TabbedTextOut ( _In_ HDC hDC, _In_ int x, //логическая x-координата начала строки _In_ int y, //логическая y-координата начала строки _In_ LPCTSTR lpszString, //указатель на строку _In_ int nCount,//число символов в строке. Если nCount = -1, тогда длина вычисляется _In_ int nTabPositions, //число значений в массиве позиций позиции табуляции. _In_ const LPINT lpnTabStopPositions,/* Указатель на массив, содержащий позиции позиции табуляции (в логических модулях). Табуляторы должны сортироваться в увеличивающемся порядке, самое маленькое x-значение должно быть первый элементом массива */ _In_ int nTabOrigin /* x-координата исходной позиции, из которой позиции табуляции расширены (в логических модулях) */ ); Вызов этой функции для записи символьной строки в определенном расположении, разворачивая позиции табуляции к значениям, определенным в массиве позиций позиции табуляции. Текст написан в настоящее время выбранном шрифте. Если nTabPositions = 0, и lpnTabStopPositions = NULL, позиции табуляции расширены до восьми раз средней символьной ширины. Если nTabPositions = 1, табуляторы отделяются расстоянием, определенным первым значением в lpnTabStopPositions массиве. Если lpnTabStopPositions массив содержит больше чем одно значение, табулятор установлен для каждого значения в массиве, до номера, определенного nTabPositions. nTabOrigin параметр позволяет прикладной программе называть функцию TabbedTextOut несколькими разами для одиночной строки. Если вызовы из прикладной программы больше чем один раз с набором nTabOrigin к тому же самому значению каждый раз, функция разворачивают позиции табуляции относительно позиции, определенной nTabOrigin. По умолчанию, текущая позиция не используется или модифицируется функцией. Если программа должна модифицировать текущую позицию, тогда перед вызовом TabbedTextOut вызывают функцию SetTextAlign функцию с параметром nFlags равным TA_UPDATECP. Когда этот флажок установлен, Windows игнорирует значение параметров x и y при последующих обращениях к TabbedTextOut, используя текущую позицию Код (ASM): TabbedTextOut1::;вывод текста при помощи функции TabbedTextOut @@: mov r9d,stringtable4[rbx] or r9,r9 jz a1 invoke TabbedTextOut,MemDC,LEFT,y,,-1,0,0,0 add y,edi add ebx,4 jmp @b a1: ... Вывод текста при помощи функции PolyTextOutPolyTextOut рисует несколько строк используя шрифт и текущие цвета текста выбранные в контексте устройства. Код (C): BOOL PolyTextOut( HDC hdc, // дескриптор контекста устройства CONST POLYTEXT *pptxt, /* Указатель на массив структур POLYTEXT, описывающих строки, которые будут выведены. Массив содержит одну структуру для каждой строки, которая будет выведена */ int cStrings // число структур POLYTEXT в массиве pptxt ); Код (ASM): POLYTEXTA STRUCT x DWORD ? y DWORD ? n DWORD ? lpStr DWORD ? uiFlags DWORD ? icl RECT <> pdx DWORD ? POLYTEXTA ENDS Каждая структура POLYTEXT содержит координаты опорной точки, которую Windows использует для, выравнивания соответствующей строки текста. Приложение может устанавливать опорную точку, используя вызов функции SetTextAlign. Приложение может выяснить текущие настройки выравнивания текста для заданного контекста устройства при помощи функции GetTextAlign. Код (ASM): .data pptxt POLYTEXT <0,TOP+16*0, expTxt6b-expTxt6a,expTxt6a,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*1, expTxt6c-expTxt6b,expTxt6b,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*2, expTxt6d-expTxt6c,expTxt6c,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*3, expTxt6e-expTxt6d,expTxt6d,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*4, expTxt6f-expTxt6e,expTxt6e,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*5, expTxt6g-expTxt6f,expTxt6f,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*6, expTxt6h-expTxt6g,expTxt6g,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*7, expTxt6i-expTxt6h,expTxt6h,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*8, expTxt6j-expTxt6i,expTxt6i,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*9, expTxt6k-expTxt6j,expTxt6j,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*10,expTxt6l-expTxt6k,expTxt6k,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*11,expTxt6m-expTxt6l,expTxt6l,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*12,expTxt6n-expTxt6m,expTxt6m,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*13,expTxt6o-expTxt6n,expTxt6n,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*14,expTxt6p-expTxt6o,expTxt6o,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*15,expTxt6q-expTxt6p,expTxt6p,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*16,expTxt6r-expTxt6q,expTxt6q,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*17,expTxt6s-expTxt6r,expTxt6r,ETO_OPAQUE,{0,0,100,200},0> POLYTEXT <0,TOP+16*18,aStatic -expTxt6t,expTxt6s,ETO_OPAQUE,{0,0,100,200},0> expTxt6a db 'Раз после ужина мальчик прибежал к старому негру, чтобы послушать ещё про Братца Кролика и его' expTxt6b db ... PolyTextOut1::;вывод текста при помощи функции PolyTextOut invoke PolyTextOut,MemDC,&pptxt,19 Вывод текста при помощи функции ExtTextOutФункция ExtTextOut выводит текст используя текущий выбранный шрифт, цвета фона и текста. Вы можете произвольно предоставить размеры, которые используются для отсечения, окраски, или обоих параметров. Прототип функции ExtTextOut Код (ASM): BOOL WINAPI ExtTextOut( _In_ HDC hdc, // дескриптор DC _In_ int x, // логическая x-координата первого символа строки _In_ int y, // логическая y-координата верхней части первого символа строки _In_ UINT nOptions, /* тип прямоугольника. Может быть ETO_CLIPPED либо ETO_OPAQUE, либо ETO_CLIPPED+ETO_OPAQUE, либо 0 */ _In_ const RECT *lpRect,/* Указатель на структуру RECT, которая определяет размерности ;прямоугольника. Параметр может быть равен NULL */ _In_ LPCTSTR lpszString,// Указатель на символьную строку _In_ UINT nCount, // число символов в строке _In_ const INT lpDxWidths/* Указатель на массив значений, которые указывают расстояние между происхождением смежных символьных ячеек. Например, lpDxWidths логические модули отделит происхождение символьной ячейки и символьная ячейка + 1. Если lpDxWidths = NULL, ExtTextOut использует значение по умолчанию, располагающее между символами */ ); Константы ExtTextOut ― ETO_XX
ЗначениеhexbinПредназначениеETO_000000000000000ETO_GRAYED100000000000001недокументированETO_OPAQUE200000000000010Текущий фоновый цвет заполняет прямоугольник. Можно устанавливать и делать запрос текущего фонового цвета функциями SetBkColor и GetBkColorETO_CLIPPED400000000000100текст отсечен к прямоугольникуETO_800000000001000?ETO_GLYPH_INDEX1000000000010000Массив lpString относится к массиву, возвращаемому функцией GetCharacterPlacement, и должен анализироваться непосредственно графическим интерфейсом устройств (GDI), поскольку никакая более поздняя ориентированная на конкретный язык обработка не требуется. Глиф индексируется только тогда, когда применяются шрифты TrueType, но флажок может использоваться для растровых и векторных шрифтов, чтобы указать, что в более поздней обработке языка нет необходимости, а графический интерфейс устройств должен обработать строку непосредственно. Заметьте, что все индексы глифа ― 16-разрядные значения даже при том, что строка предназначена для растровых шрифтов быть массивом значений из 8 битов. Функция ExtTextOutW, индексы глифа сохраняет в метафайле. Однако, чтобы показать на экране правильные символы, метафайл должен быть воспроизведен, используя тот же самый шрифт. Функция ExtTextOutA, индексы глифа не сохраняетETO_2000000000100000?ETO_4000000001000000?ETO_RTLREADING8000000010000000Если это значение установлено, в контексте устройства выбираются и еврейский, и арабский шрифт, строка выводится, используя порядок зеркального изображения. Если это значение не определено, строка выводится в порядке слева направо. Тот же самый результат может быть достигнут установкой значения TA_RTLREADING в параметре функции SetTextAlign. Это значение сохраняется для совместимости внизETO_10000000100000000ETO_20000001000000000ETO_NUMERICSLOCAL40000010000000000показывать числа с использованием цифр, применяемых в данной странеETO_NUMERICSLATIN80000100000000000показывать числа с использованием римских цифрETO_IGNORELANGUAGE100001000000000000Зарезервирован для системного использования. Если приложение устанавливает этот флажок, оно теряет поддержку национальных языков, и, в некоторых случаях, оно не может показать на экране никакого текста вообщеETO_PDY200010000000000000Когда он устанавливается, массив, на который указывает параметр lpDx содержит пары значений. Первое значение каждой пары, как обычно, расстояние между началами координат смежных символьных ячеек, а второе значение - смещение по вертикальному направлению шрифтаЗначения ETO_GLYPH_INDEX и ETO_RTLREADING не могут использоваться вместе. Поскольку ETO_GLYPH_INDEX подразумевает, что вся обработка языка была закончена, функция игнорирует флажок ETO_RTLREADING если он также установлен. Вызов функции, записывает строку внутрь прямоугольной области, использующей в настоящее время выбранный шрифт. Прямоугольная область может быть непрозрачна (заполнена текущим фоновым цветом), и это может быть область отсечения. Если nOptions = 0, и lpRect = NULL, текст выводиться в контекст устройства без использования прямоугольной области. По умолчанию, текущая позиция не используется или модифицируется функцией. Если программа должна модифицировать текущую позицию, тогда перед вызовом ExtTextOut вызывают функцию SetTextAlign с параметром nFlags равным TA_UPDATECP. Когда этот флажок установлен, Windows игнорирует значения параметров в x и y при последующих обращениях к ExtTextOut и использует текущую позицию. Когда прикладная программа использует TA_UPDATECP, чтобы модифицировать текущую позицию, ExtTextOut устанавливает текущую позицию или к концу предыдущей строки текста или к позиции, определенной последним элементом массива, указанного в lpDxWidths. Код (ASM): .data expTxt2A db 'На другой день мальчик пришёл к дядюшке Римусу послушать, чем кончилась история с лошадью Братца' expTxt2B db 'Кролика. Но дядюшка Римус был не в духе.' expTxt2C ... .... stringtable2 dd expTxt2A,expTxt2B,expTxt2C,expTxt2D,expTxt2E,expTxt2F, expTxt2G,expTxt2H,expTxt2I,expTxt2J,expTxt2K,expTxt2L dd expTxt2M,expTxt2N,expTxt2O,expTxt2P,expTxt2Q,expTxt2R,expTxt2S, expTxt2T,expTxt2U,expTxt2V,expTxt2W,expTxt2X,expTxt2Y dd expTxt2Z,expTxt2a,expTxt2b,expTxt2c,expTxt2d,expTxt2e,expTxt2f,expTxt2g, expTxt2h,expTxt2i,expTxt2j,expTxt2k,expTxt2l dd expTxt2m,expTxt2n,expTxt2o,expTxt2p,expTxt2q,expTxt2r,expTxt2s,expTxt2t, expTxt2u,expTxt2v,expTxt2w,expTxt2x,expTxt2y dd expTxt2z,expTxt2@,0 .code ExtTextOut1::;вывод текста при помощи функции ExtTextOut @@: mov r10d,stringtable2[rbx] mov eax,stringtable2[rbx+4] or eax,eax jz a1 sub eax,r10d invoke ExtTextOut,MemDC,LEFT,y,0,&expRect,r10,rax,0 add y,edi add ebx,4 jmp @b Изменение заголовка окнаМожно сделать двумя способами: Для того чтобы изменить заголовок окна используется функция SetWindowTextA/W Прототип функции: Код (C): BOOL WINAPI SetWindowText( _In_ HWND hWnd, //Дескриптор окна _In_opt LPCTSTR lpString //Адрес строки с новым заголовком ); Код (ASM): Title1::invoke SetWindowText,hWnd,&expTxt8 Вывод текста в заголовок окна через SendMessage Код (ASM): Title2::invoke SendMessage,hWnd,WM_SETTEXT,0,&expTxt9