Ассемблер под Windows для чайников. Часть 19
В прошлый раз мы научились выводить bitmap в качестве фона диалогового окна. Сегодня продолжим разговор об использовании графических элементов в окне программы. Рассмотрим отображение графики в обычном окне. Этот способ несколько сложнее, чем отображение картинки в диалоговом окне, ввиду того, что некоторые вещи, которые выполнялись системой автоматически, нам придется выполнить самостоятельно. Зато мы сможем более подробно рассмотреть механизм отрисовки графики в окне и лучше понять, как это реализуется в окнах.
Итак, если для отображения картинки в диалоговом окне нам было достаточно указать в статическом элементе идентификатор ресурса с картинкой и размеры отображаемого рисунка, то теперь надо будет при создании окна загрузить картинку из ресурсов в память функцией LoadBitmap. Она работает аналогично функциям LoadIcon и LoadCursor и имеет те же два параметра: дескриптор исполняемого модуля, из ресурсов которого будет загружена картинка, и идентификатор ресурса с картинкой. При указании нуля в первом параметре можно указать во втором параметре значение стандартного системного изображения (от 32734 до 32767), некоторые из которых могут отсутствовать в современных ОС, да и вообще это уже достаточно редко используемый вариант. После загрузки изображения мы, как и в случаях с иконками и курсорами, получаем его дескриптор. Однако если дескриптор загруженной иконки или курсора мы можем сразу поместить в соответствующий элемент структуры WNDCLASS непосредственно перед регистрацией класса окна, то дескриптор загруженного изображения является промежуточным идентификатором отображаемой картинки. Картинка выводится в окно достаточно долгими окольными путями. Включите мозги на максимальное восприятие — сейчас будет нехило!
Если в окне требуется отрисовка какого-либо изображения или чего-либо еще, то она производится по получению окном сообщения WM_PAINT. Получили сообщение — начинаем рисовать. Для этого вызываем функцию BeginPaint, которая подготавливает окно к перерисовке и заполняет структуру PAINTSTRUCT необходимыми данными. Функция имеет два параметра: дескриптор перерисовываемого окна и указатель на структуру PAINTSTRUCT. А возвращает она дескриптор контекста графического устройства (handle to a display device context) перерисовываемого окна. Этот труднопроизносимый "контекст графического устройства" в народе называют не иначе как DC, а дескриптор (handle) на него — HDC. DC представляет собой сложную структуру данных, которая в нашем случае используется для хранения текущего графического содержимого окна. Чтобы что-либо отрисовать в окне, мы должны получить доступ к DC его клиентской области. Функция BeginPaint как раз и предоставляет нам этот доступ. Главное после отрисовки — не забыть освободить DC окна функцией EndPaint. Рисовать прямо в DC не принято. Обычно для вывода изображения на экран используется метод двойной буферизации. Создается невидимая на экране копия DC перерисовываемого окна (вторичный буфер), в ней рисуют все, что душе угодно, а потом этот вторичный буфер быстренько копируют в первичный. Таким образом реализуется быстрый вывод изображения на экран. Допустим, мы получили HDC нашего окна. Теперь при помощи функции CreateCompatibleDC мы создадим совместимый DC, он же вторичный буфер. Эта функция имеет всего один параметр — дескриптор DC, для которого будет создана совместимая копия. В случае успеха функция возвращает дескриптор на область памяти вторичного буфера. Сохраним его в переменную "hMemDC" и идем дальше.
Функция SelectObject выбирает объект для отображения в указанном DC. Она имеет два параметра: дескриптор DC и дескриптор отображаемого объекта. Картинки могут быть отображены только на виртуальном холсте, то есть только на вторичном буфере. Это еще раз объясняет, почему мы используем двойную буферизацию и рисуем картинку сначала в копию DC. Еще один момент, который важно запомнить сейчас, чтобы потом часами не отлавливать глюки программы, — это то, что каждая картинка одновременно может быть выбрана лишь для одного буфера DC. Хотите выбрать одну и ту же картинку сразу в несколько DC — загрузите ее из ресурсов несколько раз или скопируйте из одного DC в другой. Возвращаемые функцией SelectObject значения рассматривать будем при более благоприятных обстоятельствах вместе с ее остальными возможностями — такими, как выбор кисти, шрифта, пера и региона. А пока не забиваем переполненный мозг второстепенной информацией и двигаемся вперед.
Функция GetClientRect сообщает в указанную во втором параметре структуру RECT координаты клиентской области окна. В первом параметре, как вы могли догадаться, указывается дескриптор этого окна. Координаты left и top (левый верхний угол) будут равны нулю ввиду того, что они отсчитываются как раз относительно левого верхнего угла клиентской области окна. А вот right и bottom (правый нижний угол) будут соответствовать ширине и высоте клиентской области. Собственно, они нам и понадобятся для использования в следующей функции. Сейчас поймете, почему. Функция BitBlt (bit-block transfer) осуществляет передачу битовых блоков данных о цветах пикселей из одного DC в другой. Ее параметры следующие:
1. Дескриптор DC приемника.
2,3. Координаты X и Y левого верхнего угла прямоугольника, в который будет передано изображение.
4,5. Ширина и высота передаваемой прямоугольной области.
6. Дескриптор DC источника.
7,8. Координаты X и Y левого верхнего угла прямоугольника, из которого будет передано изображение.
9. Значение, определяющее способ передачи битовых блоков.
Способы передачи битовых блоков могут быть следующими:
BLACKNESS — закрасить выбранный прямоугольник DC приемника в черный цвет.
DSTINVERT — инвертировать выбранный прямоугольник DC приемника.
MERGECOPY — смешать посредством оператора AND цвета прямоугольника DC источника с цветом кисти, выбранной для DC приемника.
MERGEPAINT — смешать посредством оператора OR цвета инвертированного прямоугольника DC источника с цветами прямоугольника DC приемника. NOTSRCCOPY — скопировать инвертированный прямоугольник DC источника в приемник.
NOTSRCERASE — объединить цвета прямоугольников DC источника и приемника посредством оператора OR, а затем инвертировать цвета результата. PATCOPY — закрасить прямоугольник DC приемника цветом кисти, выбранной для DC приемника.
PATINVERT — объединить посредством оператора XOR цвета выбранного прямоугольника DC приемника с цветом кисти, выбранной для DC приемника. PATPAINT — объединить посредством оператора OR цвета инвертированного прямоугольника DC источника с цветом кисти, выбранной для DC
приемника. А полученный результат еще объединить с цветами прямоугольника DC приемника посредством того же OR.
SRCAND — объединить цвета прямоугольников DC источника и приемника посредством оператора AND.
SRCCOPY — скопировать прямоугольник источника в прямоугольник приемника.
SRCERASE — объединить посредством оператора AND инвертированные цвета прямоугольника DC приемника с цветами прямоугольника DC источника. SRCINVERT — объединить цвета прямоугольников DC источника и приемника посредством оператора XOR.
SRCPAINT — объединить цвета прямоугольников DC источника и приемника посредством оператора OR.
WHITENESS — закрасить выбранный прямоугольник DC приемника в белый цвет.
Для начала нам бы хватило и способа SRCCOPY — простого копирования битовых блоков, но вдруг кто-то захочет самостоятельно попробовать другие способы? Тем более, что скоро некоторые из них нам уже смогут пригодиться. После копирования прямоугольника с картинкой размером с клиентскую область нашего окна из вторичного буфера в первичный мы уже сможем видеть ее на экране. Теперь нам останется лишь удалить наш вторичный буфер при помощи функции DeleteDC, чтобы он не занимал лишнюю память. И закончим отрисовку вызовом EndPaint, которая обязательно должна вызываться каждый раз после BeginPaint. Параметры функции: дескриптор перерисованного окна и указатель на структуру PAINTSTRUCT, использованную в BeginPaint. Теперь можете ознакомиться с примером программы:
format PE GUI 4.0
entry start
include 'win32a.inc'
HTCAPTION = 2
section '.data' data readable writeable
_class TCHAR 'FASMWIN32',0
_title TCHAR 'Картинка в окне',0
_error TCHAR 'Ошибка запуска.',0
wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,COLOR_BTNFACE+1,NULL,_class
msg MSG
ps PAINTSTRUCT
rect RECT
hBitmap dd ?
hdc dd ?
hMemDC dd ?
section '.code' code readable executable
start:
invoke GetModuleHandle,0
mov [wc.hInstance],eax
invoke LoadIcon,[wc.hInstance],17
mov [wc.hIcon],eax
invoke LoadCursor,[wc.hInstance],27
mov [wc.hCursor],eax
invoke RegisterClass,wc
test eax,eax
jz error
invoke CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,326,271,NULL,NULL,[wc.hInstance],NULL
test eax,eax
jz error
msg_loop:
invoke GetMessage,msg,NULL,0,0
cmp eax,1
jb end_loop
jne msg_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop
error:
invoke MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK
end_loop:
invoke ExitProcess,[msg.wParam]
proc WindowProc hwnd,wmsg,wparam,lparam
push ebx esi edi
mov eax,[wmsg]
cmp eax,WM_CREATE
je .wmcreate
cmp eax,WM_PAINT
je .wmpaint
cmp eax,WM_DESTROY
je .wmdestroy
cmp eax,WM_LBUTTONDOWN
je .wmlbuttondown
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
jmp .finish
.wmcreate:
invoke LoadBitmap,[wc.hInstance],37
mov [hBitmap],eax
jmp .finish
.wmpaint:
invoke BeginPaint,[hwnd],ps
mov [hdc],eax
invoke CreateCompatibleDC,[hdc]
mov [hMemDC],eax
invoke SelectObject,[hMemDC],[hBitmap]
invoke GetClientRect,[hwnd],rect
invoke BitBlt,[hdc],0,0,[rect.right],[rect.bottom],[hMemDC],0,0,SRCCOPY
invoke DeleteDC,[hMemDC]
invoke EndPaint,[hwnd],ps
jmp .finish
.wmlbuttondown:
invoke ReleaseCapture
invoke SendMessage,[hwnd],WM_NCLBUTTONDOWN,HTCAPTION,0
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
xor eax,eax
.finish:
pop edi esi ebx
ret
endp
section '.idata' import data readable writeable
library gdi32,'GDI32.DLL',\
kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'
include 'api\gdi32.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'
section '.rsrc' resource data readable
…
RT_CURSOR,cursors,\
RT_GROUP_CURSOR,group_cursors,\
…
resource cursors,\
2,LANG_NEUTRAL,cursor_data
resource group_cursors,\
27,LANG_NEUTRAL,main_cursor
…
cursor main_cursor,cursor_data,'cursor.cur'
…
Секция ресурсов отличается от примера из прошлой программы отсутствием шаблона диалогового окна и наличием ресурса описания курсора, так что я не стал приводить ее полностью — думаю, догадаетесь, что к чему. Заметьте, что для использования некоторых графических функций мы подключаем в секцию импорта библиотеку GDI32.DLL. Для возможности перетаскивания окна за его клиентскую область оставлен обработчик сообщения WM_LBUTTONDOWN.
На сегодня все. Экспериментируйте с новыми функциями, изучайте новые возможности. В следующий раз продолжим разговор про отображение графики в окошках. Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт
BarMentaLisk, SASecurity gr. q@sa-sec.org
Итак, если для отображения картинки в диалоговом окне нам было достаточно указать в статическом элементе идентификатор ресурса с картинкой и размеры отображаемого рисунка, то теперь надо будет при создании окна загрузить картинку из ресурсов в память функцией LoadBitmap. Она работает аналогично функциям LoadIcon и LoadCursor и имеет те же два параметра: дескриптор исполняемого модуля, из ресурсов которого будет загружена картинка, и идентификатор ресурса с картинкой. При указании нуля в первом параметре можно указать во втором параметре значение стандартного системного изображения (от 32734 до 32767), некоторые из которых могут отсутствовать в современных ОС, да и вообще это уже достаточно редко используемый вариант. После загрузки изображения мы, как и в случаях с иконками и курсорами, получаем его дескриптор. Однако если дескриптор загруженной иконки или курсора мы можем сразу поместить в соответствующий элемент структуры WNDCLASS непосредственно перед регистрацией класса окна, то дескриптор загруженного изображения является промежуточным идентификатором отображаемой картинки. Картинка выводится в окно достаточно долгими окольными путями. Включите мозги на максимальное восприятие — сейчас будет нехило!
Если в окне требуется отрисовка какого-либо изображения или чего-либо еще, то она производится по получению окном сообщения WM_PAINT. Получили сообщение — начинаем рисовать. Для этого вызываем функцию BeginPaint, которая подготавливает окно к перерисовке и заполняет структуру PAINTSTRUCT необходимыми данными. Функция имеет два параметра: дескриптор перерисовываемого окна и указатель на структуру PAINTSTRUCT. А возвращает она дескриптор контекста графического устройства (handle to a display device context) перерисовываемого окна. Этот труднопроизносимый "контекст графического устройства" в народе называют не иначе как DC, а дескриптор (handle) на него — HDC. DC представляет собой сложную структуру данных, которая в нашем случае используется для хранения текущего графического содержимого окна. Чтобы что-либо отрисовать в окне, мы должны получить доступ к DC его клиентской области. Функция BeginPaint как раз и предоставляет нам этот доступ. Главное после отрисовки — не забыть освободить DC окна функцией EndPaint. Рисовать прямо в DC не принято. Обычно для вывода изображения на экран используется метод двойной буферизации. Создается невидимая на экране копия DC перерисовываемого окна (вторичный буфер), в ней рисуют все, что душе угодно, а потом этот вторичный буфер быстренько копируют в первичный. Таким образом реализуется быстрый вывод изображения на экран. Допустим, мы получили HDC нашего окна. Теперь при помощи функции CreateCompatibleDC мы создадим совместимый DC, он же вторичный буфер. Эта функция имеет всего один параметр — дескриптор DC, для которого будет создана совместимая копия. В случае успеха функция возвращает дескриптор на область памяти вторичного буфера. Сохраним его в переменную "hMemDC" и идем дальше.
Функция SelectObject выбирает объект для отображения в указанном DC. Она имеет два параметра: дескриптор DC и дескриптор отображаемого объекта. Картинки могут быть отображены только на виртуальном холсте, то есть только на вторичном буфере. Это еще раз объясняет, почему мы используем двойную буферизацию и рисуем картинку сначала в копию DC. Еще один момент, который важно запомнить сейчас, чтобы потом часами не отлавливать глюки программы, — это то, что каждая картинка одновременно может быть выбрана лишь для одного буфера DC. Хотите выбрать одну и ту же картинку сразу в несколько DC — загрузите ее из ресурсов несколько раз или скопируйте из одного DC в другой. Возвращаемые функцией SelectObject значения рассматривать будем при более благоприятных обстоятельствах вместе с ее остальными возможностями — такими, как выбор кисти, шрифта, пера и региона. А пока не забиваем переполненный мозг второстепенной информацией и двигаемся вперед.
Функция GetClientRect сообщает в указанную во втором параметре структуру RECT координаты клиентской области окна. В первом параметре, как вы могли догадаться, указывается дескриптор этого окна. Координаты left и top (левый верхний угол) будут равны нулю ввиду того, что они отсчитываются как раз относительно левого верхнего угла клиентской области окна. А вот right и bottom (правый нижний угол) будут соответствовать ширине и высоте клиентской области. Собственно, они нам и понадобятся для использования в следующей функции. Сейчас поймете, почему. Функция BitBlt (bit-block transfer) осуществляет передачу битовых блоков данных о цветах пикселей из одного DC в другой. Ее параметры следующие:
1. Дескриптор DC приемника.
2,3. Координаты X и Y левого верхнего угла прямоугольника, в который будет передано изображение.
4,5. Ширина и высота передаваемой прямоугольной области.
6. Дескриптор DC источника.
7,8. Координаты X и Y левого верхнего угла прямоугольника, из которого будет передано изображение.
9. Значение, определяющее способ передачи битовых блоков.
Способы передачи битовых блоков могут быть следующими:
BLACKNESS — закрасить выбранный прямоугольник DC приемника в черный цвет.
DSTINVERT — инвертировать выбранный прямоугольник DC приемника.
MERGECOPY — смешать посредством оператора AND цвета прямоугольника DC источника с цветом кисти, выбранной для DC приемника.
MERGEPAINT — смешать посредством оператора OR цвета инвертированного прямоугольника DC источника с цветами прямоугольника DC приемника. NOTSRCCOPY — скопировать инвертированный прямоугольник DC источника в приемник.
NOTSRCERASE — объединить цвета прямоугольников DC источника и приемника посредством оператора OR, а затем инвертировать цвета результата. PATCOPY — закрасить прямоугольник DC приемника цветом кисти, выбранной для DC приемника.
PATINVERT — объединить посредством оператора XOR цвета выбранного прямоугольника DC приемника с цветом кисти, выбранной для DC приемника. PATPAINT — объединить посредством оператора OR цвета инвертированного прямоугольника DC источника с цветом кисти, выбранной для DC
приемника. А полученный результат еще объединить с цветами прямоугольника DC приемника посредством того же OR.
SRCAND — объединить цвета прямоугольников DC источника и приемника посредством оператора AND.
SRCCOPY — скопировать прямоугольник источника в прямоугольник приемника.
SRCERASE — объединить посредством оператора AND инвертированные цвета прямоугольника DC приемника с цветами прямоугольника DC источника. SRCINVERT — объединить цвета прямоугольников DC источника и приемника посредством оператора XOR.
SRCPAINT — объединить цвета прямоугольников DC источника и приемника посредством оператора OR.
WHITENESS — закрасить выбранный прямоугольник DC приемника в белый цвет.
Для начала нам бы хватило и способа SRCCOPY — простого копирования битовых блоков, но вдруг кто-то захочет самостоятельно попробовать другие способы? Тем более, что скоро некоторые из них нам уже смогут пригодиться. После копирования прямоугольника с картинкой размером с клиентскую область нашего окна из вторичного буфера в первичный мы уже сможем видеть ее на экране. Теперь нам останется лишь удалить наш вторичный буфер при помощи функции DeleteDC, чтобы он не занимал лишнюю память. И закончим отрисовку вызовом EndPaint, которая обязательно должна вызываться каждый раз после BeginPaint. Параметры функции: дескриптор перерисованного окна и указатель на структуру PAINTSTRUCT, использованную в BeginPaint. Теперь можете ознакомиться с примером программы:
format PE GUI 4.0
entry start
include 'win32a.inc'
HTCAPTION = 2
section '.data' data readable writeable
_class TCHAR 'FASMWIN32',0
_title TCHAR 'Картинка в окне',0
_error TCHAR 'Ошибка запуска.',0
wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,COLOR_BTNFACE+1,NULL,_class
msg MSG
ps PAINTSTRUCT
rect RECT
hBitmap dd ?
hdc dd ?
hMemDC dd ?
section '.code' code readable executable
start:
invoke GetModuleHandle,0
mov [wc.hInstance],eax
invoke LoadIcon,[wc.hInstance],17
mov [wc.hIcon],eax
invoke LoadCursor,[wc.hInstance],27
mov [wc.hCursor],eax
invoke RegisterClass,wc
test eax,eax
jz error
invoke CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU,128,128,326,271,NULL,NULL,[wc.hInstance],NULL
test eax,eax
jz error
msg_loop:
invoke GetMessage,msg,NULL,0,0
cmp eax,1
jb end_loop
jne msg_loop
invoke TranslateMessage,msg
invoke DispatchMessage,msg
jmp msg_loop
error:
invoke MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK
end_loop:
invoke ExitProcess,[msg.wParam]
proc WindowProc hwnd,wmsg,wparam,lparam
push ebx esi edi
mov eax,[wmsg]
cmp eax,WM_CREATE
je .wmcreate
cmp eax,WM_PAINT
je .wmpaint
cmp eax,WM_DESTROY
je .wmdestroy
cmp eax,WM_LBUTTONDOWN
je .wmlbuttondown
.defwndproc:
invoke DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
jmp .finish
.wmcreate:
invoke LoadBitmap,[wc.hInstance],37
mov [hBitmap],eax
jmp .finish
.wmpaint:
invoke BeginPaint,[hwnd],ps
mov [hdc],eax
invoke CreateCompatibleDC,[hdc]
mov [hMemDC],eax
invoke SelectObject,[hMemDC],[hBitmap]
invoke GetClientRect,[hwnd],rect
invoke BitBlt,[hdc],0,0,[rect.right],[rect.bottom],[hMemDC],0,0,SRCCOPY
invoke DeleteDC,[hMemDC]
invoke EndPaint,[hwnd],ps
jmp .finish
.wmlbuttondown:
invoke ReleaseCapture
invoke SendMessage,[hwnd],WM_NCLBUTTONDOWN,HTCAPTION,0
jmp .finish
.wmdestroy:
invoke PostQuitMessage,0
xor eax,eax
.finish:
pop edi esi ebx
ret
endp
section '.idata' import data readable writeable
library gdi32,'GDI32.DLL',\
kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'
include 'api\gdi32.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'
section '.rsrc' resource data readable
…
RT_CURSOR,cursors,\
RT_GROUP_CURSOR,group_cursors,\
…
resource cursors,\
2,LANG_NEUTRAL,cursor_data
resource group_cursors,\
27,LANG_NEUTRAL,main_cursor
…
cursor main_cursor,cursor_data,'cursor.cur'
…
Секция ресурсов отличается от примера из прошлой программы отсутствием шаблона диалогового окна и наличием ресурса описания курсора, так что я не стал приводить ее полностью — думаю, догадаетесь, что к чему. Заметьте, что для использования некоторых графических функций мы подключаем в секцию импорта библиотеку GDI32.DLL. Для возможности перетаскивания окна за его клиентскую область оставлен обработчик сообщения WM_LBUTTONDOWN.
На сегодня все. Экспериментируйте с новыми функциями, изучайте новые возможности. В следующий раз продолжим разговор про отображение графики в окошках. Все приводимые примеры были протестированы на правильность работы под Windows XP и, скорее всего, будут работать под другими версиями Windows, однако я не даю никаких гарантий их правильной работы на вашем компьютере. Исходные тексты программ вы можете найти на форуме: сайт
BarMentaLisk, SASecurity gr. q@sa-sec.org
Компьютерная газета. Статья была опубликована в номере 43 за 2008 год в рубрике программирование