Доброго времени суток, уважаемые подписчики. Сегодня мы немного отступим от нашего проекта и вернёмся к главной теме рассылки, к программированию под Windows на ассемблере. К тому же это наверняка пригодиться нам в нашем проекте.
Думаю, многие из вас уже сталкивались с тем, что иногда контролы ведут не так, как вам бы этого хотелось. Причём проблема не в контроле (он работает без ошибок), а в том, что контрол всегда работает одинаково, а вам нужно что-то особенное. Именно для таких случаев существует сабклассинг (subclassing).
Создавая окно Windows создаёт структуру окна на основе заданного класса и, в последующих операциях с окном, использует эту структуру. Информацию из этой структуры можно получить, используя функцию GetWindowLong. Функции передаётся два параметра, handle окна и индекс получаемого значения в структуре окна. Причём нулевой индекс указывает на первый байт дополнительной информации окна (она указывается при создании класса, параметр cbWndExtra структуры WNDCLASSEX), а основная информация окна имеет негативные индексы, вот они:
имя индекса
индекс
Возвращаемое значение
GWL_WNDPROC
-4
указатель на процедуру окна
GWL_HINSTANCE
-6
handle программы
GWL_HWNDPARENT
-8
handle окна-родителя
GWL_ID
-12
индекс окна
GWL_STYLE
-16
стиль окна
GWL_EXSTYLE
-20
расширенный стиль окна
GWL_USERDATA
-21
информация пользователя
Помимо того, что эта информация может быть прочитана, она также может быть изменена так, как это нужно программисту. Для этого служит функция SetWindowLong. Это даёт нам возможность перехватывать сообщения, посылаемые окну. Теперь нам просто нужно создать процедуру, которая служила бы фильтром к сообщениям окна, и выставить её как процедуру окна при помощи функции SetWindowLong. Главное, не забыть, после фильтрации сообщений, передавать их старой процедуре обработки. Сделать это можно функцией CallWindowProc. Рассмотрим это на небольшом примере:
subclass.rc:
include macros.inc
include user32.inc
include kernel32.inc
include def32.inc
.386
.model flat
.data?
OldWndProc dd ?
.code
_start:
null ebx
run GetModuleHandle, ebx
run DialogBoxParam, eax, 1, ebx, offset WndProc, ebx
run ExitProcess, ebx
WndProc proc
push ebp
mov ebp,esp
pusha
mov edi,[ebp+08h]
null ebx
mov eax,[ebp+0Ch]
cmp eax,WM_INITDIALOG
je init_proc
cmp eax,WM_CLOSE
je close_proc
popa
pop ebp
null eax
ret 16
init_proc:
run GetDlgItem, edi, 100
run SetWindowLong, eax, GWL_WNDPROC, offset NewWndProc
mov OldWndProc, eax
EndWndProc:
popa
pop ebp
null eax
ret 16
close_proc:
run EndDialog, edi, ebx
jmp short EndWndProc
WndProc endp
NewWndProc proc
push ebp
mov ebp,esp
mov eax,[ebp+0Ch]
cmp eax,WM_CHAR
jne Pass_message
cmp dword ptr [ebp+10h],'s'
jne Pass_message
mov dword ptr [ebp+10h],'$'
Pass_message:
run CallWindowProc, OldWndProc, dword ptr [ebp+08h], eax, dword ptr [ebp+10h], dword ptr [ebp+14h]
pop ebp
ret 16
NewWndProc endp
end _start
Здесь мы создаём обычный диалог, с одним edit box-ом. При инициализации мы получаем handle edit box-а и изменяем его процедуру обработки сообщений на нашу. SetWindowLong возвращает предидущее значение устанавливаемой переменной, поэтому возвращённое значение ни что иноё, как адрес старой процедуры edit box-а. Особое внимание хочу обратить на процедуру NewWndProc. Она проверяет, не WM_CHAR ли сообщение, пришедшее edit box-у, и если это оно и нажатый символ равен "s", то она заменяет символ на "$". Преимущество сабклассинга в том, что настоящая процедура даже не знает, что была нажата "s", для неё такая клавиша никогда и не была нажата.
Этот способ достаточно хорош, но если нужно создать десятки или даже сотни контролов, то сабклассинг может стать достаточно не эффективным решением. Проблема в том, что сабклассинг окна можно произвести лишь после его создания мы вынуждены будем вызывать одни и те же функции десятки или сотни раз, создавая нужные нам контролы. То есть для избежания проблем нам надо заранее изменить процедуру окна контрола. Можно конечно изменить процедуру окна класса функциями SetClassLong и GetClassLong, но тогда мы теряем возможность использовать обычный контрол и решаем этой возможности все работающие приложения разом. Выходом из положения является так называемый суперклассинг (superclassing). Суперклассинг подразумевает собой создание класса, отличающегося от уже существующего лишь заранее установленной процедурой фильтра сообщений (как NewWndProc). Для этого мы должны получить информацию об уже существующем классе, используем функцию GetClassInfo, передав ей три параметра, handle приложения, создавшего класс, имя класса и адрес структуры WNDCLASSEX для заполнения. Далее мы изменяем процедуру обработки сообщений класса, не забыв предварительно сохранить адрес старой, и имя класса. Регистрируем его и используем.
Для получения результата нам нужно лишь немного изменить программу subclass:
superclass.asm:
NewWndProc proc
push ebp
mov ebp,esp
mov eax,[ebp+0Ch]
cmp eax,WM_CHAR
jne Pass_message
cmp dword ptr [ebp+10h],'s'
jne Pass_message
mov dword ptr [ebp+10h],'$'
Pass_message:
run CallWindowProc, OldWndProc, dword ptr [ebp+08h], eax, dword ptr [ebp+10h], dword ptr [ebp+14h]
pop ebp
ret 16
NewWndProc endp
end _start
Как вы видите изменений достаточно мало. Просто в ресурсах мы изменяем класс edit на наш editX, а потом в программе создаём его используя суперклассинг edit.
Ещё один вопрос, который я хотел бы разобрать, это работа с памятью в среде Windows. Эта тема уже была поднята в уроке 10, но способ выделения большого глобального куска памяти не всегда оправдан. Очень часто нужно выделять больше кусков памяти с меньшим размером, а так как GlobalAlloc всегда выделяет достаточно большой кусок памяти (обычно 64 Кб), даже если реально требуется меньше, память становится очень фрагментированной. В случае, если работа происходит с огромным количеством мизерных строк, то оправдано создание менеджера памяти, но если достаточно 10-16 достаточно больших кусков, то почти идеальным выбором могут стать функции работы с так называемой кучей (heap)! У каждого процесса при создании уже имеется одна "куча", но она часто используется для нужд системы и её лучше не трогать, но мы можем создать новую кучу функцией HeapCreate, которой необходимо указать флаги создания, начальный размер и максимальный размер. Большим плюсом является создание "куч", которые могут рости (growable heap). Плюс заключается в том, что размер выделеных кусков ограничен лишь памятью вашего компьютера, а создать такую "кучу" можно указав максимальный размер равным нулю.
null ebx
run HeapCreate, ebx, 1024, ebx
mov hHeap,eax
Например, этот фрагмент создаёт "кучу", размер которой равен 1Кб, но попытка выделить 30 Кб из "кучи" увенчается успехом, так как третий параметр, максимальный размер, равен нулю. Следующем шагом будет резервирование памяти.
run HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 4096
mov oBuffer,eax
Функция HeapAlloc возвращает указатель на зарезервированную память в случае успеха. Получает следующие параметры: handle "кучи", флаги резервирования и размер участка памяти. Освобождение памяти происходит следующим путём:
run HeapFree, hHeap, ebx, oBuffer
Параметры следующие: handle "кучи", флаги и указатель на блок памяти. Функция HeapSize определяет точный размер куска памяти выделеного из "кучи" и получает те же параметры, что и HeapFree. После использования "куча" удаляется функцией HeapDestroy.
run HeapDestroy, hHeap
Вот, вроде и все основные функции работы с "кучами...
Вы можете скачать следующие дополнения к выпуску: 0027asm - набранный вариант примеров, приведённых в выпуске 0027inc - файлы дополнения kernel32.inc, user32.inc и def32.inc 0027exe - скомпилированные примеры выпуска