Трансляция программы на Рис. 3.5.1.
MASM32:
ml /c /coff /DMASM tray.asm rc tray.rc link /subsystem:windows tray.obj tray.resTASM32:
tasm32 /ml tray.asm brcc32 tray.rc tlink32 -aa tray.obj,,,,,tray.res
В связи с программой на Рис. 3.5.1 хочу особо акцентировать Ваше внимание на
сообщении WM_SIZE
. Весьма полезное сообщение, я Вам скажу.
Представьте, что у себя в окне Вы расположили какую-то информацию. Если окно
допускает изменение размеров, то Вам придется решать проблему размещения
информации в случае, если размер окна изменился. Так вот, аккуратно все
перерисовать и отмасштабировать можно как раз, если использовать данное
сообщение. Подчеркну, что сообщение посылается, когда размер окна уже изменился.
При этом WPARAM
содержит признак того, что произошло с окном, a
LPARAM
- новый размер окна (младшее слово - ширина, старшее -
высота).
II
В. Есть ли дополнительные средства, упрощающие файловую обработку?
Да, таким средством, в частности, являются файлы, отображаемые в память. Побайтное чтение из файла действительно не всегда удобно. Конечно, можно подойти к этой проблеме несколько иначе. Можно выделить в памяти достаточно большой буфер и прочитать туда файл, а затем работать с ним, как с большим массивом. Так вот, файлы, отображаемые в память, - это нечто похожее, но здесь система берет на себя часть такой работы. Самое замечательное, однако, в этом механизме заключается в том, что файл, спроецированный в память, может быть разделенным между несколькими процессами, а это еще один способ обмена информацией между процессами.
У файлов, проецируемых в память, есть довольно существенный недостаток. После отображения их размер не может быть увеличен. В этом случае поступают следующим образом: заранее предполагая новый размер файла, проецируют его на большую область памяти.
Работу с файлами, отображаемыми в память, производят по следующему алгоритму:
- Открыть (или создать) файл с помощью обычной функции
CreateFile
. Функция, как известно, возвращает дескриптор открытого файла. - Отобразить файл45 с помощью функции
CreateFileMapping
. Именно эта функция определяет размер отображения. Эта функция, как и предыдущая, возвращает дескриптор, только не обычного, а отображенного файла. - Скопировать файл (или его часть) в созданную область при помощи функции
MapViewOfFile
. Эта функция возвращает указатель (вот оно!) на начало области, где будет расположен файл. После этого руки у Вас развязаны, и Вы можете делать с отображенным файлом все, что Вам заблагорассудится. - При желании можно записать область памяти в файл при помощи функции
FlushViewOfFile
. Разумеется, сбрасываться на диск будет та область памяти, которую мы заказали при помощи функцииCreateFileMapping
. Записывать на диск можно, разумеется, и с помощью обычной функцииWriteFile
. - Перед тем как закрывать отображенный файл, следует вначале сделать указатель
недействительным. Это делается с помощью функции
UnmapViewOfFile
. - Закрыть следует оба дескриптора. Вначале дескриптор, возвращенный функцией
CreateFileMapping
, а затем дескриптор, созданный функциейCreateFile
.
Как видите, алгоритм работы с отображаемыми файлами весьма прост. Рассмотрим теперь новые для Вас функции.
Функция CreateFileMapping
. Возвращает дескриптор отображаемого
файла.
- 1-й параметр. Дескриптор открытого файла.
- 2-й параметр. Атрибут доступа, обычно полагают равным нулю.
- 3-й параметр. Может принимать одно из следующих значений, и должен быть
совместим с режимом разделения файла:
PAGE_READONLY
,PAGE_WRITECOPY
,PAGE_READWRITE
. Как Вы понимаете, этот атрибут определяет защиту отображаемого файла и не должен противоречить атрибуту файла, открытого с помощьюCreateFile
. - 4-й параметр. Старшая часть (32 бита) размера отображаемого файла.
- 5-й параметр. Младшая часть размера отображаемого файла (как Вы понимаете, размер может и не совпадать с размером файла). Если оба параметра равны нулю, то размер полагается равным размеру открытого файла (1-й параметр).
- 6-й параметр. Имя отображаемого файла. Необходимо только в том случае, если
предполагается, что отображаемый файл будет использоваться несколькими
процессами. В этом случае повторный вызов функции
CreateFileMapping
другими процессами с тем же именем приведет не к созданию нового отображаемого файла, а к возвращению уже созданного дескриптора.
Функция MapViewOfFile
. Возвращает указатель на область памяти,
где помещается файл или его часть.
- 1-й параметр. Дескриптор, возвращенный функцией
CreateFileMapping
. - 2-й параметр. Определяет операцию, которую мы будем делать. Например,
FILE_MAP_READ
означает - только чтение, aFILE_MAP_WRITE
- чтение и запись. - 3-й параметр. Старшая часть (32-байта) смещения в файле, откуда начинается копирование в память.
- 4-й параметр. Младшая часть смещения в файле, откуда начинается копирование.
- 5-й параметр. Определяет количество копируемых байт. Если вы хотите
скопировать весь файл, то положите три последних параметра равными
0
.
Функция FlushViewOfFile
.
- 1-й параметр. Указывает на область, записываемую в файл.
- 2-й параметр. Определяет количество записываемых байт.
Функция UnmapViewOfFile
.
- 1-й параметр. Дескриптор отображаемого файла.
Вот, собственно, и вся теория отображаемых файлов. Материал весьма прост для программирования, и мы не приводим здесь программы. Я думаю, что читатель, добравшийся до данной главы сего повествования, без труда напишет свою программу, воспользовавшись изложенными выше материалами.
45 Возможно, несколько правильнее сказать, что функция создает объект, под названием отображаемый файл.
III
В.Можно ли контролировать ввод информации в окне редактирования?
Да, в главе 2.4 мы видели, как можно это корректно делать при помощи горячих клавиш. Однако с помощью горячих клавиш легко блокировать приход в окно редактирования некоторых символов. Речь же в некоторых случаях может идти о том, чтобы преобразовывать вводимую информацию, что называется, "налету". Таким механизмом может быть использование подклассов. Для демонстрации его мы возьмем программу на Рис. 1.3.2, видоизменим ее так, чтобы можно было контролировать ввод информации.
На самом деле в этом механизме нет ничего нового. Еще в операционной системе MS DOS можно было перехватывать прерывание и встраивать туда свою процедуру. В результате при вызове данного прерывания вызывалась вначале Ваша процедура, а потом уже та, которая была установлена ранее. Впрочем, можно было поступать и по-другому: вначале вызывается процедура, существовавшая ранее, а потом, в последнюю очередь, вызывается Ваша процедура (см. [1]). Поскольку перехватывать прерывание можно было многократно, в результате могла образоваться целая цепочка процедур, выполняющихся одна за другой. Мне уже приходилось сравнивать вызов процедур окна с вызовом прерываний. Это очень похоже, не правда ли? С точки зрения объектного программирования это является ни чем иным как созданием класса-родителя.
В основе рассматриваемого механизма лежит использование функции
SetWindowLong
, которая может менять атрибуты уже созданного окна.
Вот параметры этой функции.
- 1-й параметр. Дескриптор окна, которое было создано текущим процессом.
- 2-й параметр. Величина, определяющая, какой атрибут следует изменить. Вообще
говоря, это всего лишь смещение в некоторой области памяти. Нас будет
интересовать только величина, определяющая адрес процедуры окна. Эта величина
определяется константой
DWL_DLGPROC = 4
для диалогового окна иGWL_WNDPROC = -4
для обычного окна. Отсюда, кстати, следует, что данное действо можно производить не только над простыми, но и над диалоговыми окнами. - 3-й параметр. Новое значение атрибута окна.
Следует также отметить, что данная функция возвращает старое значение атрибута. Здесь я должен еще обратить внимание читателя на то, что все элементы, создаваемые на окне, являясь тоже окнами, имеют, разумеется, и свои функции окна. В повседневной практике мы довольствуемся только сообщениями, приходящими на процедуру основного окна.
Вопрос следующий: как вызвать старую процедуру окна. Обычная команда
CALL
не подходит. Нужно использовать специальную функцию
CallWindowProc
. Параметры этой функции следующие:
- 1-й параметр. Адрес вызываемой процедуры.
- 2-й параметр. Дескриптор окна.
- 3-й параметр. Код сообщения.
- 4-й параметр. Параметр
WPARAM
. - 5-й параметр. Параметр
LPARAM
.
; файл edit.inc ; константы WM_CHAR equ 102h WM_SETFOCUS equ 7h ; сообщение приходит при закрытии окна WM_DESTROY equ 2 ; сообщение приходит при создании окна WM_CREATE equ 1 ; сообщение, если что-то происходит с элементами на окне WM_COMMAND equ 111h ; сообщение, позволяющее получить строку WM_GETTEXT equ 0Dh ; константа для функции SetWindowLong GWL_WNDPROC equ -4 ; свойства окна CS_VREDRAW equ 1h CS_HREDRAW equ 2h CS_GLOBALCLASS equ 4000h WS_TABSTOP equ 10000h WS_SYSMENU equ 80000h WS_OVERLAPPEDWINDOW equ 0 + WS_TABSTOP + WS_SYSMENU STYLE equ CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS ;CS_HREDRAW equ 2h BS_DEFPUSHBUTTON equ 1h WS_VISIBLE equ 10000000h WS_CHILD equ 40000000h WS_BORDER equ 800000h STYLBTN equ WS_CHILD + BS_DEFPUSHBUTTON + WS_VISIBLE + WS_TABSTOP STYLEDT equ WS_CHILD + WS_VISIBLE + WS_BORDER + WS_TABSTOP ; идентификатор стандартной иконки IDI_APPLICATION equ 32512 ; идентификатор курсора IDC_ARROW equ 32512 ; режим показа окна - нормальный SW_SHOWNORMAL equ 1 ; прототипы внешних процедур IFDEF MASM EXTERN CallWindowProcA@20:NEAR EXTERN SetWindowLongA@12:NEAR EXTERN SetFocus@4:NEAR EXTERN SendMessageA@16:NEAR EXTERN MessageBoxA@16:NEAR EXTERN CreateWindowExA@48:NEAR EXTERN DefWindowProcA@16:NEAR EXTERN DispatchMessageA@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetMessageA@16:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadCursorA@8:NEAR EXTERN LoadIconA@8:NEAR EXTERN PostQuitMessage@4:NEAR EXTERN RegisterClassA@4:NEAR EXTERN ShowWindow@8:NEAR EXTERN TranslateMessage@4:NEAR EXTERN UpdateWindow@4:NEAR ELSE EXTERN CallWindowProcA:NEAR EXTERN SetWindowLongA:NEAR EXTERN SetFocus:NEAR EXTERN SendMessageA:NEAR EXTERN MessageBoxA:NEAR EXTERN CreateWindowExA:NEAR EXTERN DefWindowProcA:NEAR EXTERN DispatchMessageA:NEAR EXTERN ExitProcess:NEAR EXTERN GetMessageA:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadCursorA:NEAR EXTERN LoadIconA:NEAR EXTERN PostQuitMessage:NEAR EXTERN RegisterClassA:NEAR EXTERN ShowWindow:NEAR EXTERN TranslateMessage:NEAR EXTERN UpdateWindow:NEAR CallWindowProcA@20 = CallWindowProcA SetWindowLongA@12 = SetWindowLongA SetFocus@4 = SetFocus SendMessageA@16 = SendMessageA MessageBoxA@16 = MessageBoxA CreateWindowExA@48 = CreateWindowExA DefWindowProcA@16 = DefWindowProcA DispatchMessageA@4 = DispatchMessageA ExitProcess@4 = ExitProcess GetMessageA@16 = GetMessageA GetModuleHandleA@4 = GetModuleHandleA LoadCursorA@8 = LoadCursorA LoadIconA@8 = LoadIconA PostQuitMessage@4 = PostQuitMessage RegisterClassA@4 = RegisterClassA ShowWindow@8 = ShowWindow TranslateMessage@4 = TranslateMessage UpdateWindow@4 = UpdateWindow ENDIF ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS ;----структура класса окон WNDCLASS STRUC CLSSTYLE DD ? CLWNDPROC DD ? CLSCBCLSEX DD ? CLSCBWNDEX DD ? CLSHINST DD ? CLSHICON DD ? CLSHCURSOR DD ? CLBKGROUND DD ? CLMENNAME DD ? CLNAME DD ? WNDCLASS ENDS ; файл edit.asm .386P ; плоская модель .MODEL FLAT, stdcall include edit.inc ; директивы компоновщику для подключения библиотек IFDEF MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE includelib c:\tasm32\lib\import32.lib ENDIF ;------------------------------------------------- ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> WC WNDCLASS <?> HINST DD 0 ; дескриптор приложения TITLENAME DB 'Контроль окна редактирования',0 CLASSNAME DB 'CLASS32',0 CPBUT DB 'Выход',0 ; выход CPEDT DB ' ',0 CLSBUTN DB 'BUTTON',0 CLSEDIT DB 'EDIT',0 HWNDBTN DWORD 0 HWNDEDT DWORD 0 CAP BYTE 'Сообщение',0 MES BYTE 'Конец работы программы',0 TEXT DB 100 DUP (0) OLDWND DD 0 CHAR DD ? _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX REG_CLASS: ; заполнить структуру окна ; стиль MOV [WC.CLSSTYLE],STYLE ; процедура обработки сообщений MOV [WC.CLWNDPROC], OFFSET WNDPROC MOV [WC.CLSCBCLSEX],0 MOV [WC.CLSCBWNDEX],0 MOV EAX, [HINST] MOV [WC.CLSHINST], EAX ;----------иконка окна PUSH IDI_APPLICATION PUSH 0 CALL LoadIconA@8 MOV [WC.CLSHICON], EAX ;----------курсор окна PUSH IDC_ARROW PUSH 0 CALL LoadCursorA@8 MOV [WC.CLSHCURSOR], EAX ;—————————— MOV [WC.CLBKGROUND], 17 ; цвет окна MOV DWORD PTR [WC.CLMENNAME],0 MOV DWORD PTR [WC.CLNAME],OFFSET CLASSNAME PUSH OFFSET WC CALL RegisterClassA@4 ; создать окно зарегистрированного класса PUSH 0 PUSH [HINST] PUSH 0 PUSH 0 PUSH 150 ; DY - высота окна PUSH 400 ; DX - ширина окна PUSH 100 ; Y - координата левого верхнего угла PUSH 100 ; X - координата левого верхнего угла PUSH WS_OVERLAPPEDWINDOW PUSH OFFSET TITLENAME ; имя окна PUSH OFFSET CLASSNAME ; имя класса PUSH 0 CALL CreateWindowExA@48 ; проверка на ошибку CMP EAX,0 JZ _ERR MOV [NEWHWND], EAX ; дескриптор окна ;----------------------------------------- PUSH SW_SHOWNORMAL PUSH [NEWHWND] CALL ShowWindow@8 ; показать созданное окно ;----------------------------------------- PUSH [NEWHWND] CALL UpdateWindow@4 ; команда перерисовать видимую ; часть окна, сообщение WM_PAINT ; петля обработки сообщений MSG_LOOP: PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET MSG CALL GetMessageA@16 CMP AX, 0 JE END_LOOP PUSH OFFSET MSG CALL TranslateMessage@4 PUSH OFFSET MSG CALL DispatchMessageA@4 JMP MSG_LOOP END_LOOP: ; выход из программы (закрыть процесс) PUSH [MSG.MSWPARAM] CALL ExitProcess@4 _ERR: JMP END_LOOP ;----------------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI CMP DWORD PTR [EBP+0CH],WM_DESTROY JE WMDESTROY CMP DWORD PTR [EBP+0CH],WM_CREATE JE WMCREATE CMP DWORD PTR [EBP+0CH],WM_COMMAND JE WMCOMMND JMP DEFWNDPROC WMCOMMND: MOV EAX,HWNDBTN CMP DWORD PTR [EBP+14H],EAX JNE NODESTROY JMP WMDESTROY NODESTROY: MOV EAX, 0 JMP FINISH WMCREATE: ; создать окно-кнопку PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 60 ; DX PUSH 10 ; Y PUSH 10 ; X PUSH STYLBTN PUSH OFFSET CPBUT ; имя окна PUSH OFFSET CLSBUTN ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDBTN,EAX ; запомнить дескриптор кнопки ; создать окно редактирования PUSH 0 PUSH [HINST] PUSH 0 PUSH DWORD PTR [EBP+08H] PUSH 20 ; DY PUSH 350; DX PUSH 50 ; Y PUSH 10 ; X PUSH STYLEDT PUSH OFFSET CPEDT ; имя окна PUSH OFFSET CLSEDIT ; имя класса PUSH 0 CALL CreateWindowExA@48 MOV HWNDEDT,EAX ; установить фокус на окне редактирования PUSH HWNDEDT CALL SetFocus@4 ; установить свою собственную процедуру обработки PUSH OFFSET WNDEDIT PUSH GWL_WNDPROC PUSH [HWNDEDT] CALL SetWindowLongA@12 MOV OLDWND,EAX MOV EAX, 0 JMP FINISH DEFWNDPROC: PUSH DWORD PTR [EBP+14H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+08H] CALL DefWindowProcA@16 JMP FINISH WMDESTROY: ; получить отредактированную строку PUSH OFFSET TEXT PUSH 150 PUSH WM_GETTEXT PUSH HWNDEDT CALL SendMessageA@16 ; показать эту строку PUSH 0 PUSH OFFSET CAP PUSH OFFSET TEXT PUSH DWORD PTR [EBP+08H] ; дескриптор окна CALL MessageBoxA@16 ; на выход PUSH 0 CALL PostQuitMessage@4 ; сообщение WM_QUIT MOV EAX, 0 FINISH: POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP ;----------------------------------------------- ; новая процедура обработки сообщений окну редактирования WNDEDIT PROC PUSH EBP MOV EBP,ESP MOV EAX,DWORD PTR [EBP+10H] MOV CHAR,EAX CMP DWORD PTR [EBP+0CH],WM_CHAR JNE _OLD ; проверка вводимого символа CMP AL,13 JNE _OLD ; послать сообщение о закрытии основного окна PUSH 0 PUSH 0 PUSH WM_DESTROY PUSH [NEWHWND] CALL SendMessageA@16 _OLD: ; вызвать старую процедуру PUSH DWORD PTR [EBP+014H] PUSH DWORD PTR [EBP+10H] PUSH DWORD PTR [EBP+0CH] PUSH DWORD PTR [EBP+8H] PUSH [OLDWND] CALL CallWindowProcA@20 FIN: POP EBP RET 16 WNDEDIT ENDP _TEXT ENDS END START