Конфігураційні файли win.ini і system.ini
Набудувати автозапуск програм можна і в системних файлах Windows - system.ini і win.ini. Ці файли використовуються (переважно, використовувалися) в Windows 3.x, 9x, Me для зберігання системних настройок. У Windows NT, 2000, XP аналогічні настройки перенесені в системний реєстр, але старі конфігураційні файли збережені в цілях забезпечення сумісності із старими ж програмами.
Конфігураційні файли win.ini і system.ini розбиті на секції. Назва кожної секції укладена в квадратні дужки, наприклад, [boot] або [windows].
У файлі win.ini рядка запуску програм виглядають так:
- Load=<строка запуска>
- Run=>строка запуска>
Аналізуючи такі рядки можна зрозуміти, які файли запускаються при старті комп'ютера.
У файлі system.ini є рівно один рядок, через яку найчастіше запускаються віруси, розташована в секції [boot]:
- shell=<имя програмної оболонки Windows>
У всіх версіях Windows стандартною програмною оболонкою є explorer.exe. Якщо в рядку shell= вказане щось відмінне від explorer.exe, це з великою вірогідністю шкідлива програма. Справедливості ради, потрібно відзначити, що існують легальні програми, що є альтернативними програмними оболонками Windows. Такі програми можуть змінювати значення параметра shell у файлі system.ini.
Для створення програми можна використати класи TRegistry й TiniFile.
Частина коду, написаного на Delphi, яка вносить корективи до файлів Системного реєстру
procedure TForm1.Button2Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Edit1.Text:=OpenDialog1.FileName;
|
|
ini:=TIniFile.Create(OpenDialog1.FileName);
ComboBox1.Clear;
ini.ReadSections(ComboBox1.Items);
ComboBox1.ItemIndex:=0;
ComboBox1Change(Self);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var reg: TRegistry; key: TStrings; value: string;
i, j: integer;
begin
try
if Edit2.Text = '' then raise
Exception.Create('Вы забыли ввести название ключа реестра!');
try
reg:=TRegistry.Create;
reg.RootKey:=HKEY_CURRENT_USER;
for i:=0 to ComboBox1.Items.Count-1 do
begin
if reg.OpenKey('\'+Edit2.Text+'\'+ComboBox1.Items[i], true) then
begin
key:=TStringList.Create;
ini.ReadSection(ComboBox1.Items[i], key);
for j:=0 to key.Count-1 do
begin
value:=ini.ReadString(ComboBox1.Items[i], key[j], '');
reg.WriteString(key[j], value);
end;
key.Free;
end;
end;
ShowMessage('Данные были успешно внесены в реестр.');
finally reg.Free; end;
except raise; end;
end;
Питання для самоконтролю:
- Для чого призначений Системний реєстр?
- Які існують гілки в реєстрі, за що відповідає кожна з них?
- У яких файлах зберігається Системний реєстр?
- Що таке ключ реєстру?
- У яких файлах містяться відомості про автозавантаження?
- Дії, що проводяться з реєстром?
Лабораторная работа №3
Разработка стандартных оконных приложений с простейшими элементами управления под Windows
Основы программирования в операционной системе Windows
1. Программирование в Windows основывается на использовании функций API (Application Program Interface, интерфейс программного приложения). Их количество достигает двух тысяч. Ваша программ в значительной степени будет состоять из таких вызовов. Все взаимодействие с внешними устройствами и ресурсами операционной системы будет происходит посредством таких функций.
|
|
2. Список функций API и их описание лучше всего брать из файла WIN32.HLP, который поставляется, например с пакетом Borland C++.
3. Главным элементом программы в среде Windows является окно. Для каждого окна определяется своя процедура обработки сообщения.
4. Окно может содержать элементы управления: кнопки, списки, окна редактирования и др. Эти элементы, по сути, также являются окнами, но обладающими особыми свойствами. События, происходящие с этими элементами (и самим окном), приводят к приходу сообщений в процедуру окна.
5. Операционная система Windows использует линейную адресации памяти. Другими словами, всю память можно рассматривать как один сегмент. Для программиста на языке ассемблера это означает, что адрес любой ячейки памяти будет определяться содержимым одного 32-битного регистра, например ЕВХ.
6. Следствием пункта 5 является то, что мы фактически не ограничены в объеме данных, кода или стека (объеме локальных переменных). Сегменты в тексте программы играют теперь другую роль. Они позволяют задать отдельным фрагментам кода (секциям) определенные свойства: запрет на запись, общий доступ и т.д.
|
|
7. ОС Windows является многозадачной средой. Каждая задача имеет свое адресное пространство и свою очередь сообщений. Более того, даже в рамках одной программы может быть осуществлена многозадачность – любая процедура может быть запущена как самостоятельная задача.
Вызов функций API
Для наглядности рассмотрим подробно одну из API-функций – int MessageBox ( HWND hWnd , LPCTSTR lpText , LPCTSTR lpCaption , UINT uType );
Данная функция выводит на экран окно с сообщением и кнопкой выхода. Смысл параметров: hWnd – дескриптор окна, в котором будет появляться окно-сообщения, lpText – текст, который будет появляться в окне, lpCaption – текст в заголовке окна, uType – тип окна, в частности можно определить количество кнопок выхода. Все параметры являются 32-х битными целыми числами. По причине, которая будет объяснена позже, к имени функции нам придется добавлять суффикс «А», кроме того, при использовании MASM необходимо также в конце имени добавить @16 (CALL MessageBoxA@16).
Параметры функции следует предварительно, перед вызовом функции, поместить в стек командами PUSH, по правилу «слева направо – снизу вверх».
Пример:
|
|
Пусть дескриптор окна расположен по адресу HW, строки – по адресам STR1 и STR2, а тип окна сообщения – это константа. Самый простой тип имеет значение 0 и называется MB_OK. Имеем следующее:
MB_OK equ 0
.
.
STR1 DB “Неверный ввод! “,0
STR2 DB “Сообщение об ошибке.”,0
HW DWORD ?
.
.
PUSH MB_OK
PUSH OFFSET STR1
PUSH OFFSET STR2
PUSH HW
CALL MessageBoxA@16
Результат выполнения любой функции – это, как правило, целое число, которое возвращается в регистре EAX.
Структура окна
Мы будем рассматривать классическую структуру программы под Windows. В такой программе имеется главное окно, а следовательно, и процедура главного окна. В целом, в коде программы можно выделить следующие разделы:
· Регистрация класса окон;
· Создание главного окна;
· Цикл обработки очереди сообщений;
· Процедура главного окна.
Конечно в программе могут быть и другие разделы, но данные разделы образуют основной скелет программы. Разберем эти разделы по порядку.
Регистрация класса окон
Регистрация класса окон осуществляется с помощью функции RegisterClassA, единственным параметром которой является указатель на структуру WNDCLASS, содержащую информацию об окне.
Создание окна
На основе зарегистрированного класса с помощью функции CreateWindowExA (или CreateWindowA) можно создать экземпляр окна. Как можно заметить, это весьма напоминает объектную модель программирования.
Цикл обработки очереди сообщений
Вот как выглядит этот цикл на языке Си:
While (GetMessage (&msg, NULL,0,0))
{
// разрешить использование клавиатуры путем трансляции сообщений
// о виртуальных клавишах в сообщении об алфавитно-цифровых
// клавишах
TranslateMessage(&msg);
// вернуть управление Windows и передать сообщение дальне
// Процедура окна
DispatchMessage(&msg);
}
Функция GetMessage() «отлавливает» очередное сообщение из ряда сообщений данного приложения и помещает его в структуру MSG.
Что касается функции TranslateMessage, то ее компетенция касается сообщений WM _ KEYDOWN и WM _ KEYUP, которые транслируются в WM _ CHAR и WM _ DEDCHAR, а также WM _ SYSCHAR и WM _ SYSDEADCHAR.
Выход из цикла ожидается только в том случае, если функция GetMessage возвращает 0. Это происходит только при получении сообщения о выходе (WM _ QUIT).
Процедура главного окна
Вот прототип функции окна на языке С:
LRESULT CALLBACK WindowFunc (HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
hwnd – идентификатор окна;
message – идентификатор сообщения;
wParam и lParam – параметры, уточняющие смысл сообщения (для каждого сообщения могут играть разные роли или не играть никаких). Все четыре параметра имеют тип DWORD.
Скелет на языке ассемблера имеет следующий вид:
WNDPROC PROC
PUSH EBP
MOV EBP, ESP ;
PUSH EBX
PUSH ESI
PUSH EDI
PUSH DWORD PTR [EBP+14H] ;
PUSH DWORD PTR [EBP+10H] ;
PUSH DWORD PTR [EBP+0CH] ;
PUSH DWORD PTR [EBP+08H] ;
CALL DefWindowProcA@16
POP EDI
POP ESI
POP EBX
POP EBP
RET16
WNDPROC ENDP
Принципы построения оконных приложений
- Свойства конкретного окна задаются при вызове функции CreateWindow определением параметра Style. Константы, определяющие свойства окна, содержатся в специальных файлах, которые подключаются при компиляции. Поскольку свойства фактически определяются значением того или иного бита в константе, комбинация свойств – это просто сумма битовых констант. В отличии от многих рекомендаций для разработчиков все константы здесь определяются непосредственно в программах.
- Окно создается на основе зарегистрированного класса. Оно может содержать элементы управления – кнопки, окна редактирования, списки, полосы прокрутки и т.д. Все эти элементы могут создаваться как окна с предопределенными классами (для кнопок BUTTON, для окна редактирования EDIT, для счетчика LISTBOX и т.д.).
- Система общается с окном, а следовательно, и с самим приложением посредством посылки сообщений. Эти сообщения должны обрабатываться процедурой окна. Программирование под Windows в значительной степени является программированием обработки таких сообщений. Сообщения генерируются системой также в случаях каких-либо визуальных событий, происходящих с окном или управляющими элементами в нем. К таким событиям относятся перемещение окна или изменение его размеров, нажатие кнопки, выбор элемента в списке, перемещение курсора мыши и т.п. Это понятно, программа должна как-то реагировать на подобные события.
- Сообщение имеет код (будем обозначать его в программе VES) и два параметра (WPARAM и LPARAM). Для каждого кода сообщения придумано свое макроимя, хотя это всего лишь целое число. Например, сообщение WM_ CREATE приходит один раз, когда создается окно, WM_ PAINT посылается окну при его перерисовке, сообщение WM_ RBUTTONDOWN генерируется, если щелкнуть правой кнопкой мыши при расположении курсора мыши в области окна и т.д. Параметры сообщения могут не иметь никакого смысла либо играть уточняющую роль. Например, сообщение WM_ COMMAND генерируется системой, когда что-то происходит с управляющими элементами окна. В этом случае по значению параметров можно определить, какой это элемент и что с ним произошло (LPARAM – дескриптор элемента, старшее слово WPARAM – событие, младшее слово WPARAM – обычно идентификатор ресурса). Можно сказать, что сообщение WM_ COMMAND несет сообщение от элемента окна.
- Сообщение может генерироваться не только системой, но и самой программой. Например, можно послать сообщение-команду какому-либо элементу управления (добавить элемент в список, передать строку в окно редактирования и т.п.). Иногда посылка сообщений используется как прием программирования. Например, можно придумать свои сообщений так, чтобы при их посылке программа выполнила те или иные действия. Естественно, это сообщение должно «отлавливаться» либо в процедуре какого-нибудь окна, либо в цикле обработки сообщений. Такой подход очень удобен, поскольку позволяет фактически осуществлять циклические алгоритмы так, чтобы возможные изменения с окном во время исполнения такого цикла сразу проявлялись на экране.
Структура окна
WNDCLASS STRUC
CLSSTYPE DD ? ; стиль окна
CWNDPROC DD ? ; указатель на процедуру окна
CLSCEXTRA DD ? ; инф-я о доп. байтах для структуры
CLWNDEXTRA DD ? ; информация о доп. байтах для окна
CLSHINSTANCE DD ? ; дескриптор приложения
CLSICON DD ? ; идентификатор пиктограммы окна
CLSHCURSOR DD ? ; идентификатор курсора окна
CLBKGROUND DD ? ; идентификатор кисти окна
CLMENUNAME DD ? ; имя-идентификатор меню
CLNAM DD ? ; специфицирует имя класса окон
WNDCLASS ENDS
Структура сообщения
MSGSTRUCT STRUC
MSHWND DD ? ;идент. Окна, получающего сообщение
MSMESSAGE DD ? ; идентификатор сообщения
MSWPARAM DD ? ; доп. информация о сообщении
MSLPARAM DD ? ; доп. информация о сообщении
MSTIME DD ? ; время посылки сообщения
MSPT DD ? ; курсор, во время посылки сообщения
MSGSTRUCT ENDS
Константы
Стили окна
- CS_VREDRAW equ 1
- CS_HREDRAW equ 2
- CS_GLOBALCLASS equ 4000H
- WS_TABSTOP equ 10000H
- WS_SYSMENU equ 80000H
- WS_VISIBLE equ 10000000H
- WS_CHILD equ 40000000H
- WS_BORDER equ 800000H
- WS_THICKFRAME equ 40000H
- WS_VSCROLL equ 200000H
- WS_OVERLAPPEDWINDOW equ WS_TABSTOP+WS_SYSMENU
В программе стиль главного окна программы задается следующим образом:
STYLE EQU CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
Стиль кнопки
· BS_PUSHBUTTON equ 0
· BS_DEFPUSHBUTTON equ 1
· BS_CHECKBOX equ 2
· BS_AUTOCHECKBOX equ 3
· BS_RADIOBUTTON equ 4
· BS_GROUPBPX equ 7
· BS_LEFTTEXT equ 20H
В программе стиль кнопки определяется следующим образом:
STYLBTN equ WS_CHILD+BS_DEFPUSHBUTTON+WS_VISIBLE+S_TABSTOP
Некоторые API-функции
- Функция MessageBoxA :
Создает и отображает диалог, содержащий указанное сообщение и заголовок, а также предопределенные пиктограммы и текстовые кнопки.
int MessageBox(
HWND hWnd, // дескриптор окна
LPCTSTR lpText, // адрес строки текста диалога
LPCTSTR lpCaption, // адрес заголовка диалога
UINT uType // стиль окна диалога
);
2. Функция GetModuleHandleA :
Возвращает дескриптор приложения.
HMODULE GetModuleHandle(
LPCTSTR lpModuleName // адрес приложения
);
3. Функция LoadCursorA :
Загружает указанный курсор из исполняемого файла, связанного с экземпляром приложения или файла ресурсов.
HCURSOR LoadCursor(
HINSTANCE hInstance, // дескриптор экземпляра приложения
LPCTSTR lpCursorName //строка с именем курсора или идентификатор ресурса
);
4. Функция LoadIconA :
Загружает указанную пиктограмму из исполняемого файла, связанного с экземпляром приложения, или файла ресурсов.
HICON LoadIcon(
HINSTANCE hInstance, // дескриптор экземпляра приложения
LPCTSTR lpIconName // строка с именем курсора или идентификатор ресурса
);
5. Функция RegisterClassA :
Регистрирует класс окна, атрибуты которого определены в структуре WndClass, для последующего использования. Класс окна может регистрироваться только один раз.
ATOM RegisterClass(
CONST WNDCLASS *lpWndClass // адрес структуры окна
);
6. Функция CreateWindowExA :
Создает перекрытое, всплывающее или дочернее окно с расширенным стилем.
HWND CreateWindowEx(
DWORD dwExStyle, // расширенный стиль окна
LPCTSTR lpClassName, // спецификация имени класса окна
LPCTSTR lpWindowName, // заголовок окна
DWORD dwStyle, // стиль окна
int x, // горизонтальная позиция окна
int y, // вертикальная позиция окна
int nWidth, // ширина окна
int nHeight, // высота окна
HWND hWndParent, // дескриптор родительского, дочернего окна
HMENU hMenu, // дескриптор меню
HINSTANCE hInstance, // дескриптор приложения
LPVOID lpParam // pointer to window-creation data
);
Параметры dwExStyle :
Стиль | Описание |
WS_EX_ACCEPTFILES | Определяет, что окно этого стиля поддерживает drag-drop для файлов. |
WS_EX_APPWINDOW | Forces a top-level window onto the taskbar when the window is visible. |
WS_EX_CLIENTEDGE | Определяет, что окно имеет границу с притопленным краем. |
WS_EX_CONTEXTHELP | Отображает кнопку с вопросительным знаком в окне заголовка, по нажатию на которую открывается раздел контекстно-зависимой справки. |
WS_EX_CONTROLPARENT | Позволяет пользователю переключаться между дочерними окнами по клавише Tab. |
WS_EX_DLGMODALFRAME | Создает окно, которое имеет двойную границу. |
WS_EX_LEFT | Окно имеет свойство по умолчанию : "выровненные по левой границе". |
WS_EX_LEFTSCROLLBAR | Если язык оболочки - Иврит, Арабский, или другой язык, читающийся справа налево, вертикальная полоса прокрутки рассположена слева. |
WS_EX_LTRREADING | Текст в окне рассположен слева направо. |
WS_EX_MDICHILD | Создаёт дочернее MDI окно. |
WS_EX_NOPARENTNOTIFY | Определяет, что дочернее окно не посылает сообщение WM_PARENTNOTIFY. destroyed. |
WS_EX_OVERLAPPEDWINDOW | Совмещает стили WS_EX_CLIENTEDGE и WS_EX_WINDOWEDGE. |
WS_EX_PALETTEWINDOW | Совмещает стили WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW и WS_EX_TOPMOST. |
WS_EX_RIGHT | Окно имеет свойство "выравнивание по правому краю". Этот стиль имеет эффект только, если язык оболочки - Иврит, Арабский, или другой язык, который читается справа налево. |
WS_EX_RIGHTSCROLLBAR | Полоса прокрутки находится в правой части окна. |
WS_EX_RTLREADING | Если язык оболочки - Иврит, Арабский, или другой язык, читающийся справа налево, текст в окне рассположен справа налево. |
WS_EX_STATICEDGE | Создает окно с трехмерным стилем границы. |
WS_EX_TOOLWINDOW | Создает окно, используемое как перемещаемая панель инструментов. |
WS_EX_TOPMOST | Создаёт окно, отображаемое поверх всех остальных окон. |
WS_EX_TRANSPARENT | Specifies that a window created with this style should not be painted until siblings beneath the window (that were created by the same thread) have been painted. The window appears transparent because the bits of underlying sibling windows have already been painted. |
WS_EX_WINDOWEDGE | Определяет, что окно имеет границу с приподнятым краем. |
Параметры dwStyle :
Стиль | Описание |
WS_BORDER | Создаёт окно с «тонкой» границей. |
WS_CAPTION | Создаёт окно, имеющее заголовок. |
WS_CHILD | Создаёт дочернее окно. |
WS_CHILDWINDOW | Аналогично WS_CHILD. |
WS_CLIPCHILDREN | Исключает область, занятую дочерними окнами, когда прорисовка происходит в пределах родительского окна |
WS_CLIPSIBLINGS | Отсекает дочерние окна относительно друг друга. |
WS_DISABLED | Создаёт неактивное, по умолчанию, окно. |
WS_DLGFRAME | Создаёт окно с границей диалогового окна (не имеет заголовка). |
WS_GROUP | Определяет первый элемент в группе средств управления. |
WS_HSCROLL | Создаёт окно с горизонтальной полосой прокрутки. |
WS_ICONIC | Создаёт «минимизированное» окно. |
WS_MAXIMIZE | Создаёт окно, развёрнутое на весь экран. |
WS_MAXIMIZEBOX | Создаёт окно, имеющее кнопку «развернуть». |
WS_MINIMIZE | Аналогично WS_ICONIC. |
WS_MINIMIZEBOX | Создаёт окно, имеющее кнопку «свернуть». |
WS_OVERLAPPED | Создает перекрытое окно. |
WS_OVERLAPPEDWINDOW | Создает перекрытое окно со стилями : WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX. |
WS_POPUP | Создает всплывающее окно. |
WS_POPUPWINDOW | Создает всплывающее окно со стилями : WS_BORDER, WS_POPUP, WS_SYSMENU. |
WS_SIZEBOX | Создаёт окно с "расстягивающейся" границей. |
WS_SYSMENU | Создаёт окно имеющее системное меню в строке заголовка. |
WS_TABSTOP | Определяет элемент, получающий фокус при нажатии Tab. |
WS_THICKFRAME | Аналогично WS_SIZEBOX. |
WS_TILED | Аналогично WS_OVERLAPPED. |
WS_TILEDWINDOW | Аналогично WS_OVERLAPPEDWINDOW. |
WS_VISIBLE | Создаёт окно, видимое по умолчанию. |
WS_VSCROLL | Создаёт окно с вертикальной полосой прокрутки. |
7. Функция ShowWindow :
Устанавливает режим отображения заданного окна.
BOOL ShowWindow(
HWND hWnd, // дескриптор окна
int nCmdShow // режим отображения окна
);
- Функция UpdateWindow :
Обновляет клиентскую область заданного окна, отправляя ему сообщение WM_PAINT, если область обновления не пуста. Функция отправляет сообщение WM_PAIN напрямую оконной процедуре указанного окна, обходя очередь сообщений приложения. Если область обновления пуста, то сообщение не отправляется.
BOOL UpdateWindow(
HWND hWnd // дескриптор окна
);
9. Функция GetMessageA :
Извлекает сообщение из очереди сообщений вызывающего потока и помещает его в указанную структуру
BOOL GetMessage(
LPMSG lpMsg, // адрес структуры с сообщением
HWND hWnd, // дескриптор окна
UINT wMsgFilterMin, // первое сообщение
UINT wMsgFilterMax // последнее сообщение
);
10. Функция TranslateMessageA :
Преобразует сообщения виртуальных клавиш в сообщения о символах.
BOOL TranslateMessage(
CONST MSG *lpMsg // адрес структуры сообщения
);
11. Функция DispatchMessageA :
Передает сообщение оконной функции.
LONG DispatchMessage(
CONST MSG *lpmsg // указатель на структуру с сообщением
);
12. Функция ExitProcess :
Завершает процессы и все потоки.
VOID ExitProcess(
UINT uExitCode // код выхода для всех потоков
);
13. Функция PostQuitMessage :
Указывает Windows, что поток послал запрос на завершение.
VOID PostQuitMessage(
int nExitCode // код возврата
);
14. Функция DefWindowProc :
Вызывает оконную процедуру по умолчанию для обработки сообщений, которые не обрабатываются приложением.
LRESULT DefWindowProc(
HWND hWnd, // дескриптор окна
UINT Msg, // сообщение
WPARAM wParam, // первый параметр сообщения
LPARAM lParam // второй параметр сообщения
);
15. Функция SetFocus:
Устанавливает фокус на определенное окно. Окно должно быть связано с вызовом нити очереди сообщений.
HWND SetFocus (
HWND hWnd // дескриптор окна
);
16. Функция SendMessage:
Функция посылает сообщение окну или окна.м. Функция вызывает процедуру окна и не возвращает значения, пока процедура окна не обработает сообщение.
LRESULT SendMessage(
HWND hWnd, // дескриптор окна
UINT Msg, // сообщение для передачи
WPARAM wParam, // первый параметр сообщения
LPARAM lParam // второй параметр сообщения
);
Функция BeginPaint:
Функция готовит окно для рисования и заполнения структуры PAINTSTRUCT информацией о рисовании.
HDC BeginPaint( HWNDhwnd, // дескриптор окна LPPAINTSTRUCTlpPaint// информация о рисовании);18. Функция EndPaint:
Функция отмечает окончание рисования в окне.
BOOL EndPaint( HWND hWnd, // дескриптор окна CONST PAINTSTRUCT *lpPaint // данные рисования);19. Функция TextOut:
Функция записывает строку символов в определенную позиция, использую текущий шрифт, цвет фона и цвет текста.
BOOL TextOut( HDC hdc, // дескриптор int nXStart, // x-координата начала int nYStart, // y-координта начала LPCTSTR lpString, // строка символов int cbString);
20. Функция CreateSolidBrush:
Функция создает логическую кисть, у которой есть определенный цвет.
HBRUSH CreateSolidBrush( COLORREF crColor // значение цвета кисти);Функция SetBkColor:
Функция устанавливает текущий цвет фона значением цвета.
COLORREF SetBkColor( HDC hdc, // дескриптор COLORREF crColor // значение цвета фона);22. Функция SetTextColor:
The SetTextColor function sets the text color for the specified device context to the specified color.
COLORREF SetTextColor( HDC hdc, // дескриптор COLORREF crColor // цвет текста);Сообщения ОС Windows
- wm_ setfocus – установить фокус;
- wm_ destroy – сообщение приходит при закрытии окна;
- wm_ create – сообщение приходит при создании окна;
- wm_ command – выполняются какие-то действия с окном;
- wm_ settext – послать элементу строку;
- wm_ gettext – сообщение позволяющее, получить строку;
- wm_ paint – посылается, когда приложение делает запрос на рисование
- wm_ seticon – устанавливает окну диалог
- wm_ initdialog – посылается диалоговому окно до того, как оно будет показано.
- wm_ rbuttondown – приходит при нажатии на правую кнопку мыши
- wm_ lbuttondown – приходит при нажатии на левую кнопку мыши
Сообщения, посылаемые списку
- lb_addstring – добавление строки;
- lb_deletestring – удаление строки;
- lb_ gettext – получение текста выделенного элемента;
- lb_ getcursel – получение номера выделенного элемента;
- lbn_ dblclk – проверка на двойное нажатие кнопки мыши;
- lb_findstring – поиск строки;
- lb_err – ошибка функции SendMessage;
- lb_ getcount – получение количества элементов в списке.
Вопросы для подготовки к сдаче лабораторной работы.
- Что означает термин API?
- Понятие окна в операционной системе Windows.
- Визуальные элементы управления в операционной системе Windows.
- Каким образом вызываются API-функции?
- Какая последовательность создания окон в операционной системе Windows.
- Что понимается под термином «сообщение»?
- Что такое «очередь сообщений» в операционной системе Windows.
- Как необходимо проводить обработку сообщений в операционной системе Windows.
- Какие API-функции для работы с окнами вы знаете?
- Что означает сообщение WM_PAINT?
Лабораторная работа №4
Разработка оконных приложений с использованием ресурсов
В операционной системе Windows введено понятие ресурса. Ресурс представляет собой некий визуальный элемент с заданными свойствами, хранящийся в исполняемом файле отдельно от кода и данных, который может отображаться специальными функциями.
Использование ресурсов дает две вполне определенные выгоды:
1. Ресурсы загружаются в память лишь при обращении к ним, тем самым достигается экономия памяти.
2. Свойства ресурсов поддерживаются системой автоматически, не требуя от программиста написания дополнительного кода.
Описание ресурсов хранится отдельно от программы в текстовом файле ( rc) и компилируется (res) специальным транслятором ресурсов. В исполняемый файл ресурсы включаются компоновщиком. Транслятором ресурсов в пакете MASM32 является RC.EXE.
Язык описания ресурсов
В настоящее время существует большое количество редакторов ресурсов.
Перечислим наиболее употребляемые ресурсы:
· пиктограммы;
· курсоры;
· битовая картинка;
· строка;
· диалоговое окно;
· меню;
· акселераторы.
Пиктограммы
Могут быть описаны в самом файле ресурсов, либо хранится в отдельном ico-файле. Рассмотрим последний случай. Вот файл ресурсов resu.rc:
#define IDI_ICON1 1
IDI_ICON1 ICON “Cdrom01.ico”
Как видите, файл содержит всего две значимы строки. Одна строка определяет идентификатор пиктограммы, вторая – ассоциирует идентификатор с файлом Cdrom01.ico. Язык ресурсов очень напоминает язык Си. Откомпилируем текстовый файл resu.rc: RC resu. rc. На диске появится объектный файл resu. res. При компоновке укажем этот файл в командной строке:
LINK / subsysem:windows resu.obj resu.res
Для использования данного ресурса в программе, приведем пример. Предположим, что мы хотим установить новую пиктограмму для окна. Вот фрагмент программы, который устанавливает стандартную пиктограмму для главного окна:
PUSH IDI_APPLICATION
PUSH 0
CALL LoadIconA8
MOV [WC.CLSHICON], EAX
А вот фрагмент программы для установки пиктограммы указанной в файле ресурсов:
PUSH 1 ; идентификатор пиктограммы-см. файл resu.rc
PUSH [HINST] ; идентификатор процесса
CALL LoadIconA@8
MOV [WC.CLSHICON], EAX
Структура значка
NotifyIconDataA STRUCT
cbSize DWORD ?;размер структуры
hwnd DWORD ?;дескриптор окна
uID DWORD ?;идентификатор пиктограммы
uFlags DWORD ?;флаги
uCallbackMessage DWORD ?;идентификатор сообщения
hIcon DWORD ?;дескриптор пиктограммы
szTip DWORD ?;сообщение,которое будет
;отображаться
NotifyIconDataA ENDS
Курсоры
Подход здесь полностью идентичен. Привожу ниже файл ресурсов, где определен и курсор, и пиктограмма.
#define IDI_ICON 1
#define IDI_CUR1 2
IDI_ICON1 ICON “Cdrom01.ico”
IDI_CUR1 CURSOR “4way01.cur”
А вот фрагмент программы, вызывающей пиктограмму и курсор.
;-------------- пиктограмма окна
PUSH 1 ;
PUSH [HINST]
CALL LoadIconA@8
MOV [WC.CLSHICON], EAX
;-------------- курсор окна
PUSH 2
PUSH [HINST]
CALL LoadCursorA@8
MOV [WC.CLSHCURSOR], EAX
Битовые картинки
Здесь ситуация аналогична двум предыдущим. Вот пример файла ресурсов с битовой картинкой:
#define BIT1 1
BIT1 BITMAP “PIR2.BMP”
Строки
Чтобы задать строку или несколько строк, используется ключевое слово STRINGTABLE. Ниже представлен текст ресурса, задающий две строки. Для загрузки строки в пиктограмму используется функция LoadString. Строки, задаваемые в файле ресурсов, могут играть роль констант.
#define STR1 1
#define STR2 2
STRINGTABLE
{
STR1, “Сообщение”
STR2, “Версия 1.01”
}
Диалоговые окна
Диалоговые окна являются наиболее сложными элементами ресурсов. Поэтому работа с диалоговыми окнами рассматривается в лабораторной работе №3.
Меню и ресурсы
Меню также может быть задано в файле ресурсов. Как и диалоговое окно, в программе оно определяется по имени (строке). Меню можно задать и в обычном окне в диалоговом окне. Для обычного окна при регистрации класса следует просто заменить строку
MOV DWORD PTR [WC.CLMENNAME],0
На
MOV DWORD PTR [WC.CLMENNAME],OFFSET MENS
где MENS – имя, под которым меню располагается в файле ресурсов.
Меню на диалоговое окно устанавливается другим способом, который, разумеется, подходит и для обычного окна. В начале меню загружается при помощи функции LoadMenu, а затем устанавливается функцией SetMenu.
Рассмотрим структуру файла ресурсов, содержащего определение меню. Ниже представлен текст файла, содержащего определение меню.
Далее представлена программа, демонстрирующая меню на диалоговом окне.
MENUP MENU
{
POPUP “&Первый пункт”
{
MENUITEM “&Первый”, 1
MENUITEM “&Второй”, 2
POPUP “Подмен&ю”
{
MENUITEM “Десятый пунк&т”,6
}
}
POPUP “&Второй пункт”
{
MENUITEM “Трети&й”,3
MENUITEM “Четверт&ый”,4
}
MENITEM “Вы&ход”,5
}
Как видите пункты меню имеют идентификаторы, по которым в программе можно определить, какой пункт меню выбран. Можно заметить, что выпадающее меню может содержать ещё и подменю.
Пункты меню могут содержат дополнительные параметры, которые определяют дополнительные свойства этих пунктов. Вот эти свойства, понимаемые компилятором ресурсов:
· CHECKED – пункт отмечен галочкой;
· GRAYED – элемент недоступен (имеет серый цвет);
· HELP – элемент может быть связан с помощью. Редакторы ресурсов дополнительно создают ресурс – строку. При этом идентификатор строки совпадает с идентификатором пункта меню.
· MENUBARBREAK – для горизонтального пункт это означает, что начиная с него горизонтальные пункт располагаются в новой строке. Для вертикального пункт – то, что начиная с него пункты расположены в новом столбце. При этом проводится разделительная линия.
· MENUBREAK – аналогично предыдущему, но разделительная линия не проводится;
· INACTIVE – пункт не срабатывает;
· SEPARATOR – создает в меню разделитель. При этом идентификатор не ставится.
В Windows есть обширный набор функций, с помощью которых можно менять свойства меню (удалять и добавлять пункты меню, менять их свойства).
Акселераторы
Акселератор позволяет выбирать пункт меню просто сочетанием клавиш. Это очень удобно и быстро. Таблица акселераторов является ресурсом, имя которого совпадает с именем того меню (ресурса), пункты которого она определяет.
Вот пример такой таблицы. Определяется один акселератор на пункт меню MENUP, имеющий идентификатор 4.
MENUP ACCELERATORS
{
VK_F5, 4,VIRTKEY
}
А вот общий вид таблицы акселераторов:
Имя ACCLERATORS
{
Клавиша 1, Идентификатор пункта меню (1) [,тип][,параметр]
Клавиша 2, Идентификатор пункта меню (2) [,тип][,параметр]
Клавиша 3, Идентификатор пункта меню (3) [,тип][,параметр]
…
Клавиша N, Идентификатор пункта меню (N) [,тип][,параметр]
}
Клавиша – это любой символ в кавычках, либо ASCII-код символа, либо виртуальная клавиша. Если вначале стоит код символа, то тип задается как ASCII. Если используется виртуальная клавиша, то тип определяется как VIRTUAL. Все названия (макроимена) виртуальных клавиш можно найти в include – файлах (windows.h).
Параметр может принимать одно из следующих значений: NOINVERT, ALT, CONTROL, SHIFT. Значение NOINVERT означает, что не подсвечивается выбранный при помощи акселератора пункт меню. Значение ALT, SHIFT, CONTROL означают, что, кроме клавиши, определенной в акселераторе, должна быть нажата одна из управляющих клавиш. Кроме этого, если клавиша определяется в кавычках, то нажатие при этом клавиши CONTROL определяется знаком ^: ^A.
Для того чтобы акселераторы работали, необходимо выполнить два условия:
1. должна быть загружена таблица акселераторов. Для этого используется функция LoadAccelerators;
2. сообщения, пришедшие от акселератора, следует преобразовать в сообщение WM_COMMAND. Здесь нам пригодится функция TranslateAccelerator.
Функция TranslateAccelerator преобразует сообщения WM_KEYDOWN и WM_SYSKEYDOWN в сообщения WM_COMMAND и WM_SYSCOMMAND соответственно. При этом в старшем слове параметра WPARAM помещается 1 как отличие для акселератора. В младшем слове, как вы помните, содержится идентификатор пункт меню. Сообщение WM_SYSCOMMAND генерируется для пунктов системного меню или меню окна.
Функция TranslateAccelerator возвращает ненулевое значение, если было произведено преобразование сообщения акселератора, в противном случае возвращает 0.
API – функции
1. Функция LoadIconA
Загружает указанный ресурс пиктограммы из исполняемого файла, связанного с экземпляром приложения.
HICON LoadIcon (
HINSTANCE hInstance, //дескриптор экземпляра приложения
LPCTSTR lpIconName //строка с именем пиктограммы или
); //идентификатор ресурса
2. Функция Shell_NotifyIconA
Посылает сообщение системе добавить, изменить или удалить иконку в системной области (System Tray).
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
DWORD dwMessage, //идентификатор сообщения
PNOTIFYICONDATA pnid //точка входа в структуру
);
3. Функция LoadMenu
Загружает меню из ресурса в контекст приложения
HMENU LoadMenu(
HINSTANCE hInstance, //дескриптор приложения
LPCTSTR lpMenuName); //имя ресурса
4. Функция SetMenu
Назначает новое меню указанному окну.
BOOL SetMenu(
HWND hWnd, //дескриптор окна
HMENU hMenu //дескриптор меню
);
5. Функция DrawMenuBar
Перерисовывает меню указанного окна
BOOL DrawMenuBar(
HWND hWnd //дескриптор окна
);
6. Функция LoadCursorA
Загружает системный курсор или курсор определенный в файле ресурсов
HMENU LoadCursor(
HINSTANCE hInstance, //дескриптор приложения
LPCTSTR lpCursorName //строка с именем курсора или
); //идентификатор ресурса
7. Функция LoadBitmapA
Загружает битовую картинку определенную в файле ресурсов
HMENU LoadBitmap(
HINSTANCE hInstance, //дескриптор приложения
LPCTSTR lpBitmapName //строка с именем битовой картинки
); //или идентификатор ресурса
8. Функция LoadStringA
Загружает строку, определенную в файле ресурсов
HMENU LoadString (
HINSTANCE hInstance, //дескриптор приложения
LPCTSTR lpStringName //строка с именем строки или
); //идентификатор ресурса
9. Функция SetFocus
Устанавливает фокус на заданное окно
10. Функция DestroyMenu
Удаляет меню из памяти
11. Функция GetMenuItemInfo
Получает информацию о выбранном пункте меню
Вопросы для подготовки к сдаче лабораторной работы.
1. Что понимается под термином ресурс в операционной системе Windows.?
2. Каким образом в программе хранятся ресурсы?
3. Какие виды ресурсов вы знаете?
4. Язык описания ресурсов, приведите пример описания.
5. Особенности хранения меню в ресурсах программы.
6. Особенности хранения строк в ресурсах.
7. Особенности хранения изображений в ресурсах.
8. Каким образом извлекаются данные из ресурсов.
9. В каких случаях ресурсы загружаются в память?
10. Какие API функции используются для извлечения ресурсов?
Лабораторная работа №5
«Создание многопоточных приложений в ОС Windows»
Концепция потоков
Поток (thread) — это объект операционной системы, который представляет собой последовательность (поток выполнения) команд программы внутри определенного процесса. Понятию поток соответствует последовательный переход процессора от одной команды к другой. Каждое приложение Win32 имеет, по крайней мере, один поток, обычно называемый первичным, или главным, но приложения имеют право создавать дополнительные потоки, предназначенные для выполнения других задач.
С помощью потоков реализуются средства одновременного выполнения нескольких различных подпрограмм. Конечно, если компьютер оснащен только одним процессором, то о настоящей одновременности работы двух потоков говорить не приходится. Но, когда для обработки каждого потока операционная система поочередно выделяет определенное время (измеряемое в мельчайших долях секунды), создается впечатление одновременной работы нескольких приложений.
Потоки никогда не поддерживались в среде 16-разрядной Windows. Это означает, что ни одна 32-разрядная программа Delphi, написанная с использованием потоков, никогда не будет совместимой с Delphi 1. Данный момент обязательно нужно учитывать при разработке приложений, предназначенных для работы на обеих платформах.
Типы многозадачности
Понятие потока во многом отличается от многозадачности, поддерживаемой в среде 16-разрядной Windows. Возможно, Вам приходилось слышать, что Win32 называют операционной системой с приоритетной (preemptive), или вытесняющей, многозадачностью, а Windows 3.1 — средой с кооперативной многозадачностью (cooperative multitasking).
В чем же разница? В среде приоритетной многозадачности управление возложено на операционную систему — именно она отвечает за то, какой поток должен выполняться в данный момент времени (и обладать более высоким приоритетом). Когда первый поток приостанавливается системой ради передачи второму потоку нескольких циклов работы процессора, то о первом потоке говорят, что он выгружен. И если к тому же окажется, что первый поток выполняет бесконечный цикл, то, как правило, это не приводит к трагической ситуации, поскольку операционная система будет продолжать выделение процессорного времени для всех других потоков.
В среде Windows 3.1 ответственность за возвращение управления операционной системе по завершении выполнения приложения лежит целиком на разработчике. Поэтому, когда приложению не удается корректно завершить работу и вернуть управление операционной системе, кажется, что операционная система "зависает" — все хорошо знакомы с такой печальной ситуацией. Если вдуматься, то это может показаться даже забавным — устойчивость работы всей системы 16-разрядной Windows полностью зависит от поведения всех ее приложений, которые должны самостоятельно защитить себя от попадания в бесконечные циклы, замкнутую цепь рекурсии и другие неприятные ситуации. А поскольку все приложения для достижения корректной работы системы должны работать согласованно, этот тип многозадачности называется кооперативным.
Использование многопоточности в приложениях Delphi
Итак, давайте определимся, что под словом "поток" мы подразумеваем именно Thread, который еще имеет название "нить". Нередко встречаются на форумах мнения, что потоки не нужны вообще, любую программу можно написать так, что она будет замечательно работать и без них. Конечно, если не делать ничего серьёзней "Hello World" это так и есть, но если постепенно набирать опыт, рано или поздно любой начинающий программист упрётся в возможности "плоского" кода, возникнет необходимость распараллелить задачи. А некоторые задачи вообще нельзя реализовать без использования потоков, например работа с сокетами, COM-портом, длительное ожидание каких-либо событий, и т.д.
Всем известно, что Windows система многозадачная. Попросту говоря, это означает, что несколько программ могут работать одновременно под управлением ОС. Все мы открывали диспетчер задач и видели список процессов. Процесс - это экземпляр выполняемого приложения. На самом деле сам по себе он ничего не выполняет, он создаётся при запуске приложения, содержит в себе служебную информацию, через которую система с ним работает, так же ему выделяется необходимая память под код и данные. Для того, чтобы программа заработала, в нём создаётся поток. Любой процесс содержит в себе хотя бы один поток, и именно он отвечает за выполнение кода и получает на это процессорное время. Этим и достигается мнимая параллельность работы программ, или, как её еще называют, псевдопараллельность. Почему мнимая? Да потому, что реально процессор в каждый момент времени может выполнять только один участок кода. Windows раздаёт процессорное время всем потокам в системе по очереди, тем самым создаётся впечатление, что они работают одновременно. Реально работающие параллельно потоки могут быть только на машинах с двумя и более процессорами.
Для создания дополнительных потоков в Delphi существует базовый класс TThread, от него мы и будем наследоваться при реализации своих потоков. Для того, чтобы создать "скелет" нового класса, можно выбрать в меню File - New - Thread Object, Delphi создаст новый модуль с заготовкой этого класса. Для наглядности, опишем его в модуле формы. Как Вы видите, в этой заготовке добавлен один метод - Execute. Именно его нам и нужно переопределить, код внутри него и будет работать в отдельном потоке. И так, попробуем написать пример - запустим в потоке бесконечный цикл:
TNewThread = class(TThread)private
{ Private declarations }
protected
procedure Execute; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TNewThread }
procedure TNewThread.Execute;
begin
while true do {ничего не делаем};
end;
procedure TForm1.Button1Click(Sender: TObject);
var
NewThread: TNewThread;
begin
NewThread:=TNewThread.Create(true);
NewThread.FreeOnTerminate:=true;
NewThread.Priority:=tpLower;
NewThread.Resume;
end;
Запустите пример на выполнение и нажмите кнопку. Вроде ничего не происходит - форма не зависла, реагирует на перемещения. На самом деле это не так - откройте диспетчер задач и вы увидите, что процессор загружен полностью. Сейчас в процессе вашего приложения работает два потока - один был создан изначально, при запуске приложения. Второй, который так грузит процессор - мы создали по нажатию кнопки. Итак, давайте разберём, что же означает код в Button1Click:
NewThread:=TNewThread.Create(true);
тут мы создали экземпляр класса TNewThread. Конструктор Create имеет всего один параметр - CreateSuspended типа boolean, который указывает, запустить новый поток сразу после создания (если false), или дождаться команды (если true).
New.FreeOnTerminate := true;
свойство FreeOnTerminate определяет, что поток после выполнения автоматически завершится, объект будет уничтожен, и нам не придётся его уничтожать вручную. В нашем примере это не имеет значения, так как сам по себе он никогда не завершится, но понадобится в следующих примерах.
NewThread.Priority:=tpLower;
Свойство Priority, если вы еще не догадались из названия, устанавливает приоритет потока. Каждый поток в системе имеет свой приоритет. Если процессорного времени не хватает, система начинает распределять его согласно приоритетам потоков. Свойство Priority может принимать следующие значения:
- tpTimeCritical - критический
- tpHighest - очень высокий
- tpHigher - высокий
- tpNormal - средний
- tpLower - низкий
- tpLowest - очень низкий
- tpIdle - поток работает во время простоя системы
Ставить высокие приоритеты потокам не стоит, если этого не требует задача, так как это сильно нагружает систему.
NewThread.Resume;
Ну и собственно, запуск потока.
Это был пример создания потоков. Но не всё так просто. Казалось бы - пишем любой код внутри метода Execute и всё, а нет, потоки имеют одно неприятное свойство - они ничего не знают друг о друге. Это значит, допустим, Вы пытаетесь из другого потока изменить свойство какого-нибудь компонента на форме. Как известно, VCL однопоточная, весь код внутри приложения выполняется последовательно. Допустим, в процессе работы изменились какие-то данные внутри классов VCL, система отбирает время у основного потока, передаёт по кругу остальным потокам и возвращает обратно, при этом выполнение кода продолжается с того места, где приостановилось. Если мы из своего потока что-то меняем, к примеру, на форме, задействуется много механизмов внутри VCL (напомним, выполнение основного потока пока "приостановлено"), соответственно за это время успеют измениться какие-либо данные. И тут вдруг время снова отдаётся основному потоку, он спокойно продолжает своё выполнение, но данные уже изменены! К чему это может привести - предугадать нельзя. Вы можете проверить это тысячу раз, и ничего не произойдёт, а на тысяча первый программа рухнет. И это относится не только к взаимодействию дополнительных потоков с главным, но и к взаимодействию потоков между собой. Писать такие ненадёжные программы конечно нельзя.
Синхронизации потоков
Если вы создали шаблон класса автоматически, то, наверное, заметили комментарий, который дружелюбная Delphi поместила в новый модуль. Он гласит: "Methods and properties of objects in visual components can only be used in a method called using Synchronize". Это значит, что обращение к визуальным компонентам возможно только путём вызова процедуры Synchronize. Давайте рассмотрим пример, но теперь наш поток не будет разогревать процессор впустую, а будет делать что-нибудь полезное, к примеру, прокручивать ProgressBar на форме. В качестве параметра в процедуру Synchronize передаётся метод нашего потока, но сам он передаётся без параметров. Параметры можно передать, добавив поля нужного типа в описание нашего класса. У нас будет одно поле - тот самый прогресс:
TNewThread = class(TThread)
private
Progress: integer;
procedure SetProgress;
protected
procedure Execute; override;
end;
...
procedure TNewThread.Execute;
var
i: integer;
begin
for i:=0 to 100 do
begin
sleep(50);
Progress:=i;
Synchronize(SetProgress);
end;
end;
procedure TNewThread.SetProgress;
begin
Form1.ProgressBar1.Position:=Progress;
end;
Вот теперь ProgressBar двигается, и это вполне безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передаёт управление главному потоку, т.е. SetProgress выполняется в главном потоке. Это нужно запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для вывода информации - тот самый двигатель прогресса, обновления заголовков компонентов и т.д.
Вы наверное заметили, что внутри цикла мы используем процедуру Sleep. В однопоточном приложении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример - бесконечный цикл, пока не выполнится какое-нибудь условие. Если не вставить туда Sleep мы будем просто нагружать систему бесполезной работой.
Так работает Synchronize. Но есть еще один довольно удобный способ передать информацию форме - посылка сообщения. Давайте рассмотрим и его. Для этого объявим константу:
const
PROGRESS_POS = WM_USER+1;
В объявление класса формы добавим новый метод, а затем и его реализацию:
TForm1 = class(TForm)
Button1: TButton;
ProgressBar1: TProgressBar;
procedure Button1Click(Sender: TObject);
private
procedure SetProgressPos(var Msg: TMessage); message PROGRESS_POS;
public
{ Public declarations }
end;
...
procedure TForm1.SetProgressPos(var Msg: TMessage);
begin
ProgressBar1.Position:=Msg.LParam;
end;
Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока:
procedure TNewThread.Execute;
var
i: integer;
begin
for i:=0 to 100 do
begin
sleep(50);
SendMessage(Form1.Handle,PROGRESS_POS,0,i);
end;
end;
Используя функцию SendMessage, мы посылаем окну приложения сообщение, один из параметров которого содержит нужный нам прогресс. Сообщение становится в очередь, и согласно этой очереди будет обработано главным потоком, где и выполнится метод SetProgressPos. Но тут есть один нюанс: SendMessage, как и в случае с Synchronize, приостановит выполнение нашего потока, пока основной поток не обработает сообщение. Если использовать PostMessage этого не произойдёт, наш поток отправит сообщение и продолжит свою работу, а уж когда оно там обработается - неважно. Какую из этих функций использовать - решать вам, всё зависит от задачи.
Вот, в принципе, мы и рассмотрели основные способы работы с компонентами VCL из потоков. А как быть, если в нашей программе не один новый поток, а несколько? И нужно организовать работу с одними и теми же данными? Тут нам на помощь приходят другие способы синхронизации. Один из них мы и рассмотрим. Для его реализации нужно добавить в проект модуль SyncObjs.
Критические секции
Работают они следующим образом: внутри критической секции может работать только один поток, другие ждут его завершения. Чтобы лучше понять, везде приводят сравнение с узкой трубой: представьте, с одной стороны "толпятся" потоки, но в трубу может "пролезть" только один, а когда он "пролезет" - начнёт движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar'ом. Итак, запустите один из примеров, приведённых ранее. Нажмите на кнопку, подождите несколько секунд, а затем нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передаёт разные значения прогресса. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:
var
Form1: TForm1;
CriticalSection: TCriticalSection;
...
procedure TForm1.FormCreate(Sender: TObject);
begin
CriticalSection:=TCriticalSection.Create;
end;
У TCriticalSection есть два нужных нам метода, Enter и Leave, соответственно вход и выход из неё. Поместим наш код в критическую секцию:
procedure TNewThread.Execute;var
i: integer;
begin
CriticalSection.Enter;
for i:=0 to 100 do
begin
sleep(50);
SendMessage(Form1.Handle,PROGRESS_POS,0,i);
end;
CriticalSection.Leave;
end;
Попробуйте запустить приложение и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдёт прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаём поток, он занимает критическую секцию и начинает работу. Нажимаем второй - создаётся второй поток, но критическая секция занята, и он ждёт, пока её не освободит первый. Третий, четвёртый - все пройдут только по очереди.
Критические секции удобно использовать при обработке одних и тех же данных (списков, массивов) разными потоками. Поняв, как они работают, Вы всегда найдёте им применение.
Пример создания многопоточного приложения в Delphi:
Этот раздел содержит описание шагов, необходимых для создания простого, но показательного примера многопоточного приложения. Мы будем пытаться вычислить число "пи" с максимальной точностью после запятой. Конечно, встроенная в Delphi константа Pi имеет достаточную точность, правильнее сказать — максимальную, допускаемую самым точным 10-байтным форматом для вещественных чисел Extended. Так что превзойти ее нам не удастся. Но этот пример использования потоков может послужить прологом для решения реальных задач.
Первый пример будет содержать два потока: главный (обрабатывающий ввод пользователя) и вычислительный; мы сможем изменять их свойства и наблюдать за реакцией. Итак, выполните следующую последовательность действий:
1. В среде Delphi откройте меню File и выберите пункт New Application.
2. Расположите на форме пять меток и один переключатель, как показано на рис. 1. Переименуйте главную форму в fmMain.
3. Откройте меню File и выберите пункт Save Project As. Сохраните модуль как uMain, а проект — как Threads 1.
Рис. 1. Внешний вид формы для приложения Threads'1
4. Откройте меню File и выберите пункт New. Затем дважды щелкните на объекте типа поток (значок Thread Object). Откроется диалоговое окно New Items, показанное на рис. 2.
Рис. 2. Диалоговое окно New Items с выбранным объектом типа "поток"
Рис . 3. Диалоговое окно New Thread Object
5. Когда появится диалоговое окно для именования объекта поток, введите TPiThread и нажмите клавишу Enter (рис. 3). Помимо этого, при желании, вы можете присвоить создаваемому потоку имя, установив флажок Named Thread и задав имя в поле Thread Name. Так как имя потока используется только для удобства обозначения, эту возможность мы использовать не будем.
Delphi создаст новый модуль и поместит в него шаблон для нового потока.
6. Код, вносимый в метод Execute, вычисляет число Pi, используя сходимость бесконечного ряда Лейбница:
Pi = 4 - 4/3 + 4/5 - 4/7 + 4/9 -...Разумеется, отображать новое значение после каждой итерации — это то же самое, что стрелять из пушки по воробьям. На отображение информации система потратит в десятки раз больше времени, чем на собственно вычисления. Поэтому мы ввели константу updatePeriod, которая регулирует периодичность отображения текущего значения.
Код метода Execute показан ниже:
7. Откройте меню File и выберите пункт Save As. Сохраните модуль с потоком как uPiThread.pas.
8. Отредактируйте главный файл модуля uMain.pas и добавьте модуль uPiThread к списку используемых модулей в секции интерфейса. Он должен выглядеть так:
9. В секции public формы TfmMain добавьте ссылку на создаваемую нить: PiThread : TPiThread;
10. Добавьте в модуль uMain две глобальные переменные
и метод UpdatePi:
Этот метод, если Вы обратили внимание, вызывается из потока посредством процедуры Synchronize. Он отображает текущее значение приближения к числу "пи" и количество итераций.
В случае, если главное окно приложения свернуто, отображение не производится; так что после его развертывания вам, возможно, придется подождать некоторое время для обновления.
11. Выполните двойной щелчок на свободном месте рабочей области формы, при этом создастся шаблон метода FormCreate. Здесь мы отобразим значение системной константы р±:
12. Выберите на форме переключатель (его название cbcalculate) и назначьте событию Onclick код, создающий и уничтожающий вычислительный поток в зависимости от состояния переключателя:
Таким образом, многопоточное приложение готово к запуску. Если все пройдет нормально, вы увидите картинку, подобную той, которая приведена на рис. 4.
Рис. 4. Выполняющееся приложение Threads1
Вопросы для подготовки к сдаче лабораторной работы:
1. Что такое поток?
2. Какие Вы знаете типы многозадачности в ОС Windows?
3. Что такое процесс?
4. В чем отличие потока от процесса?
5. Для чего необходима процедура Synchronize?
6. Для чего необходимы критические секции?
7. Какие Вы знаете приоритеты потоков?
8. Для чего необходим метод Execute?
9. Как можно определить количество потоков в запущенном процессе?
Лабораторная работа №6
«Специальные объекты синхронизации»
Краткие теоретические сведенья:
Иногда при работе с несколькими потоками или процессами появляется необходимость синхронизировать выполнение двух или более из них. Причина этого чаще всего заключается в том, что два или более потоков могут требовать доступ к разделяемому ресурсу, который реально не может быть предоставлен сразу нескольким потокам.
Разделяемым называется ресурс, доступ к которому могут одновременно получать несколько выполняющихся задач.
Потоки находятся в состоянии ожидания, пока ожидаемые ими объекты заняты. Как только объект освобождается, ОС будит поток и позволяет продолжить выполнение. Для приостановки потока и перевода его в состояние ожидания освобождения объекта используется функция
Дата добавления: 2020-12-12; просмотров: 176; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!