Написал сюда, потому что мне нужна помощь, у меня куча вопросов.
Очень надеюсь на ваше понимание и помощь, и пожалуйста не отсылайте меня к мануалу как это делают на некоторых форумах, если вы сами в силах мне помочь.
Начну с самого главного:
Я сразу скажу что в программировании на Ассемблере я новичек в общем освоил я основы (вроде), правда есть некоторые вопросы из-за дурацких высокоуровневых директив. Вот начал изучение Asm под Win32 и начал я с уроков Iczelion`на. И вот на 5 уроке у меня возникла проблема.
Дело в том что я не использую директив ветвления (не хочу), а пользуюсь стандартными средствами Ассемблера. Вот переписал программу из 5 урока и запустил.
Программа выводит текст и тут же вызывает ошибку (которую мелкософт предлагает оправить). Смотрел в отладчике, ошибка вроде заключается в том что программа пытается вернутся (ret) от куда-то на адрес 00000000 (то есть ошибка из-за стэка?).
Причем если переменную для временного сохранения хэндла логического шрифта сделать не локальной то есть не в стэке, а поместить в секции .data?, то все работает как надо. Пожалуйста, кто-нибудь может объяснить чайнику почему так происходит?
Вот исходник:
.386
.model flat,stdcall
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm
.DATA
class_name db "win_class",0
window_name db "Title",0
font_name db "script",0
out_string db "Win32 assembly is great and easy!",0
; структура, описывающая класс окна.
wc WNDCLASSEX <4*12,CS_HREDRAW or CS_VREDRAW,offset win_proc,0,0,?,?,?,\
COLOR_WINDOW+1,0,offset class_name,0>
.DATA?
msg_ MSG <> ; структура, в которой возвращается
; сообщение после GetMessage
ps PAINTSTRUCT <> ; структура для граф. функций
.CODE
_start:
xor ebx,ebx ; в EBX будет 0 для команд push 0
; (короче в 2 раза)
; определим идентификатор нашей программы
push ebx
call GetModuleHandle
mov esi,eax ; и сохраним его в ESI
; заполним и зарегестрируем класс
mov dword ptr wc.hInstance,eax ; идентификатор предка
; выберем иконку
push IDI_APPLICATION ; стандартная иконка приложения
push ebx ; идентификатор модуля с иконкой
call LoadIcon
mov wc.hIcon,eax ; идентификатор иконки для нашего класса
; выберем форму курсора
push IDC_ARROW ; стандартная стрелка
push ebx ; идентификатор модуля с курсором
call LoadCursor
mov wc.hCursor,eax ; идентификатор курсора для нашего класса
push offset wc
call RegisterClassEx ; зарегистрируем класс
; создадим окно
mov ecx,CW_USEDEFAULT ; push ecx короче push N в пять раз
push ebx ; адрес структуры CREATESTRUCT (здесь NULL)
push esi ; идентификатор процесса, который будет получать
; сообщения от окна (то есть, наш)
push ebx ; идентификатор меню или окна-потомка
push ebx ; идентификатор окна-предка
push ecx ; высота (CW_USEDEFAULT - по умолчанию)
push ecx ; ширина (по умолчанию)
push ecx ; y-координата (по умолчанию)
push ecx ; x-координата (по умолчанию)
push WS_OVERLAPPEDWINDOW ; стиль окна
push offset window_name ; заголовок окна
push offset class_name ; любой зарегистрированный класс
push ebx ; дополнительный стиль
call CreateWindowEx ; создать окно (eax - идентификатор окна)
push eax ; идентификатор для UpdateWindow
push SW_SHOWNORMAL ; тип показа для для ShowWindow
push eax ; идентификатор для ShowWindow
; больше идентификатор окна нам не потребуется
call ShowWindow ; показать окно
call UpdateWindow ; и послать ему сообщение WM_PAINT
; основной цикл - проверка сообщений от окна и выход по WM_QUIT
mov edi,offset msg_ ; push edi короче push N в 5 раз
message_loop:
push ebx ; последнее сообщение
push ebx ; первое сообщение
push ebx ; идентификатор окна (0 - любое наше окно)
push edi ; адрес структуры MSG
call GetMessage ; получить сообщение от окна с ожиданием
; - не забывайте использовать PeekMessage
; если нужно в этом цикле что-то выполнять
test eax,eax ; если получено WM_QUIT
jz exit_msg_loop ; выйти
push edi ; иначе - преобразовать сообщения типа
call TranslateMessage ; WM_KEYUP в сообщения типа WM_CHAR
push edi
call DispatchMessage ; и послать их процедуре окна (иначе его просто
; нельзя будет закрыть)
jmp message_loop ; продолжить цикл
exit_msg_loop:
; выход из программы
push ebx
call ExitProcess
;////////////////////////////////////////////////////////////////////////
; процедура win_proc
; процедура не должна изменять регистры EBP,EDI,ESI и EBX
;
win_proc proc
; так как мы получаем параметры в стеке, построим стековый кадр
push ebp
mov ebp,esp
push ebx ; функция должна сохр. этот регистр,
; а мы его используем поэтому сохраняем его
; процедура типа WindowProc вызывается со следующими параметрами
hWnd equ dword ptr [ebp+08h] ; идентификатор окна
uMsg equ dword ptr [ebp+0Ch] ; номер сообщения (код сообщеиия)
wParam equ dword ptr [ebp+10h] ; первый параметр
lParam equ dword ptr [ebp+14h] ; второй параметр
; локальные переменные
sub esp,8 ; 2 X dword
hdc equ dword ptr [ebp+18h] ; хэндл контекста устройства (для граф. функций)
hfont equ dword ptr [ebp+1Ch] ; переменная для сохранения (восст.) измененяемого шрифта
; вот если эту переменную объявит не тут а в .data?
; то все работает
; если мы получили сообщение WM_DESTROY (оно означает что окно уже удалили
; с экрана, нажав alt-F4 или кнопку в верхнем правом углу)
; то пошлем нам же сообщение WM_QUIT
cmp uMsg,WM_DESTROY
jne not_wm_destroy
push 0 ; код выхода
call PostQuitMessage ; послать WM_QUIT
jmp end_wm_check ; и выйти из процедуры
not_wm_destroy:
cmp uMsg,WM_PAINT ; сообщение WM_PAINT ?
jne not_wm_paint ; Нет - прыгаем дальше
; получим хэндл контекста устр-ва
mov ebx,offset ps ; push ebx быстрее чем push N
push ebx
push hWnd ; хэндл окна в кот. будем рисовать
call BeginPaint ; в eax хэндл контекста устр-ва
mov hdc, eax
; создадим логический шрифт и получим его хэндл
mov ebx,offset font_name ; push ebx быстрее чем push N
push ebx ; смещение строки названия гарнитуры шрифта
mov ebx,DEFAULT_PITCH or FF_SCRIPT
push ebx ; питч и семейство шрифта (объед. or' ом)
push DEFAULT_QUALITY ; качества вывода
push CLIP_DEFAULT_PRECIS ; что делать с символами, котоpые
; вылезают за пpеделы отpисовочного pегиона
push OUT_DEFAULT_PRECIS ; насколько должен близко должен
;пpиближаться фонт к хаpактеpистикам,
; котоpые мы указали
push OEM_CHARSET ; символьный набоp фонта]
mov ebx,0 ; push ebx быстрее чем push N
push ebx ; признак ПЕРЕчеркнутости
push ebx ; признак ПОДчеркнутости
push ebx ; признак курсива
push 400 ; устанавливает толщину линии
; 400 - нормальная толщина
push ebx ; насколько символ должен быть повеpнут (0.1 deg)
push ebx ; указывает оpиентацию вывода следующего символа,
; относительно пpедыдущего
push 16 ; желаемая шиpина символов
push 24 ; желаемая высота символов
call CreateFont ; eax = хэндл логического шрифта
; выберем наш шрифт для контекста устр-ва
push eax ; хэндл логического шрифта
push hdc ; хэндл контекста устр-ва
call SelectObject
; сохраним хэндл предидущего шрифта (он в eax)
mov hfont, eax
; установим цвет шрифта
RGB 200,200,50 ; цвет
push eax
push hdc
call SetTextColor ; устанавливаем цвет текста
; установим цвет фона
RGB 0,0,255 ; цвет
push eax
push hdc
call SetBkColor ; устанавливаем цвет фона
; нарисуем текст в окне
push SIZEOF out_string ; длина строки для вывода
push offset out_string ; смещение строки
mov ebx,0
push ebx ; Y координата
push ebx ; X координата
push hdc
call TextOut ; нарисуем текст
; восстановим старый хэндл шрифта
push hfont ; сохраненный хэндл старого шрифта
push hdc ; хэндл контекста устройства
call SelectObject
; освободим хэндл контекста устр-ва
mov ebx,offset ps ; push ebx быстрее чем push N
push ebx
push hWnd
call EndPaint
not_wm_paint:
; если мы получили другое сообщение - вызовем его обработчик по умолчанию
pop ebx ; мы его сохраняли
leave ; восстановим ebp
jmp DefWindowProc ; и вызовем DefWindowProc с нашими параметрами
end_wm_check:
pop ebx ; мы его сохраняли
leave ; восстановим ebp
ret 16 ; и вернемся сами, очистив стек от параметров
win_proc endp
;////////////////////////////////////////////////////////////////////////
end _start
И еще: пожалуйста, если кто может объсните можно ли делать так:
объявить процедуру не просто так вот:
Some_Proc proc
..
Some_Proc endp
а вот так:
Some_Proc proc param1:DWORD, param2:DWORD
..
Some_Proc endp
и обращаться к переданым параметрам не через стэк, а прямо по именам?
И надо ли после такого объявления указывать в ret кол-во удаляемых из стэка байт?
И зачем нужен прототип процедуры? Только для того чтобы ее вызывать через invoke
или это еще что-то дает, и почему когда используют высокоуровневые директивы
не пишут пролога и эпилога процедуры и не указывают кол-во удаляемых байт из стэка в ret?
Надеюсь на вашу помощь =)