Здравствуйте, уважаемые дZенствующие! Прошу понять и простить, но несколько месяцев поиска, чтения и осмысления документации, примеров и форумов, а также тупого перебора вариантов не принесли результата. Поэтому пишу сюда. Нужно создать простейшее, тривиальное MDI-приложение для платформы win64. Двигаюсь двумя путями - имею две группы проблем: 1) MASM не создаёт окно фрейма, хотя после CreateWindowExA rax != 0, но GetLastError возвращает success; 2) fasm создаёт окно фрейма, но я не могу понять, как передаются параметры в процедуру ChildProc. Окно фрейма рисуется, меню фрейма работает прекрасно, продедура ChildProc реагирует на команды меню, но параметров не видно ни в регистрах, ни в стеке, окно Child не рисуется. Код прилагаю... Помогите разобраться в дебрях... Добра Вам!
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms632680(v=vs.85).aspx GetLastError должен возвращать ERROR_SUCCESS при rax != 0
Уважаемый Mikl__ ! Спасибо Вам за Вашу работу и за персональную помощь! Благодаря Вам всё получилось! Для тех, кому интересно, привожу результат на fasm - см. вложение к посту. Программа изначально задумывалась как шаблон, поэтому основной целью было обеспечение наглядности и расширяемости... В итоге получилось 440 строк, включая комментарии и ресурсы. ▼
А вот ещё простое MDI-приложение: перевод на fasm для Win64 примера из учебника Чарльза Петцольда. Несколько упрощённое (без массива цветов и рандомного механизма), т.к. основная цель - исследовать работу MDI.
Перевод указанного примера Петцольда на MASM/WASM для win64 зашёл в тупик. Причина следующая: полное непонимание требования о выравнивании стека. 1. Дизассемблирование собранной Fasm программы показывает, что sub rsp, 8 - выравнивание стека с учётом адреса возврата - подставляется ассемблером в начале процедур WinMain и ChildWndProc. В конце тех процедур, где было выравнивание, стек не восстанавливается. Команда add rsp, 8 не встречается нигде. При этом в процедурах FrameWndProc и CloseEnumProc стек не выравнивается ни в начале, ни в конце. 1.1. В чём логика выравнивания? 1.2. Почему пользуются вычитанием sub rsp, 8, а не универсальным and rsp, -16? 2. Зачем макрос invoke создаёт буфер для каждого вызова функции? Нельзя ли один раз в начале процедуры сделать sub rsp, 20h или даже 60h, или сразу 80h, а в конце восстановить add rsp, 80h? Уж слишком часто между соседними вызовами call стоят бессмысленные add rsp, 20h sub rsp, 20h...
ml64, так реализован МАКРОС invoke в FASM, не нравится ― перепиши тело макроса, который внутри твоей процедуры отыскивал бы вызов WinAPI с максимальным количеством параметров и подставлял бы в качестве эпилога push rbp/mov rbp,rsp/sub rsp,XX а в конце leave, а не выставлял бы вокруг каждого вызова WinAPI "бессмысленные add rsp,20h и sub rsp,20h" а hiew32 говорит. что выравнивание есть начало FrameWndProc Код (ASM): 402453: push rbp <-- выравнивание push reg = sub rsp,8/mov [rsp],reg mov rbp,rsp sub rsp,8 <-- эта строка нужна, так как в стек отправляется содержимое нечетного числа регистров push rbx push rsi push rdi конец FrameWndProc Код (ASM): 40291F: pop rdi pop rsi pop rbx leave <-- возвращаем содержимое rsp в исходное состояние retn Вспомнил! В FASM есть макросы frame/endf которые тебе нужны. Пример использования есть в fasm\examples\win64\opengl
Попробую сделать проще: аналог invoke, который загружает параметры в регистры, но не меняет rsp. Спасибо! Ну конечно!!! Вот решение моей проблемы: Если процедура имеет параметры, например: WndProc proc hWnd:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD то при ассемблировании MASM вставляет строки: push rbp mov rbp,rsp ...и тогда выравнивание не требуется. Но если процедура не имеет параметров: WndProc proc то при ассемблировании MASM ничего не добавляет, и требуется выравнивание: sub rsp, 8 ... add rsp, 8
попробуй, только не забывай, что в регистры rcx, rdx, r8, r9 загружаются первые 4 параметра, а что делать если параметров больше 4? Например, как будет выглядеть invoke для CreateFile или для CreateWindowEx?
Для таких функций буду использовать стандартный invoke А чтобы не считать байты стека в прологе и эпилоге: Код (ASM): mov rax,rsp and rax,0Fh sub rsp,rax mov StackMask,rax ... add rsp,StackMask
Всё запустилось, работает стабильно. На все вопросы топика ответы получены. Ещё раз спасибо, Mikl__! Применённые приёмы обусловлены следующими ссображениями: 1. На адрес вершины стека влияют: адрес возврата (8 байт), сохранённые регистры, параметры процедуры, локальные переменные. Их размеры различны, но ассемблер всё равно выделяет память квантами, равными 8 байтам (проверял, но могу ошибаться: для локальной переменной типа BYTE ассемблер выделил 8 байт). Выделение памяти происходит в самом начале процедуры. 2. Перед вызовом API-функций стек дожен быть выровнен по 10h. Можно вычислять адрес вершины стека, а можно просто обнулить младший байт: and rsp,0Fh. Никакие данные от этого не потеряются, т.к. стек растёт вниз. 3. Не нужно запоминать, на сколько байт был смещён стек при выравнивании, т.к. ассемблер в прологе процедуры помещает инструкции push rbp mov rbp,rsp, а в эпилоге - инструкцию leave, т.е. при выходе из процедуры стек всё равно вернётся в исходное состояние. 4. Также перед вызовом API-функций необходимо зарезервировать 20h байт в стеке для возвращаемых параметров. 5. Перед вызовом каждой функции этого делать не обязательно, т.к. вызовы функций происходят очень часто, и частое перемещение вершины стека нерационально. Достаточно один раз зарезервировать 80h байт в стеке в прологе процедуры. Этого хватит на 16 параметров по 8 байт
Здесь досадная очепятка. Обнулить младший байт: and rsp, -16 (или, что то же самое, and rsp, 0FFFF FFFF FFFF FFF0h) В остальном указанные пять пунктов вроде как работоспособны. За две недели не подводили. Сделал. Работает. Но не один макрос, а пять: call1, call2, ... call5 - по числу параметров. Так проще для машины и удобнее для меня. Код (кому интересно) прилагаю
На всякий случай похвастаюсь - может, кому пригодится. Работающий перевод 18 урока из книги Чарльза Петзольда на fasm. Что удалось выяснить: 1) переменная static - это глобальная переменная! Только этой глобальной переменной пользуется всего одна процедура; Иными словами, она служит для хранения данных, общих для всех окон одного класса. 2) для хранения индивидуальных данных каждого окна выделяется heap, который привязан к дескриптору этого окна. 3) чтобы получить случайное число в диапазоне [0, x], надо: сгенерировать случайное число (на Ivy Bridge и новее есть инструкция RdRand), разделить полученное число на x и взять остаток. Инструкция div помещает остаток от деления в регистр rdx.