Конфігураційні файли 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;

 

Питання для самоконтролю:

    1. Для чого призначений Системний реєстр?
    2. Які існують гілки в реєстрі, за що відповідає кожна з них?
    3. У яких файлах зберігається Системний реєстр?
    4.  Що таке ключ реєстру?
    5. У яких файлах містяться відомості про автозавантаження?
    6. Дії, що проводяться з реєстром?

 

 

Лабораторная работа №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

 

Принципы построения оконных приложений

  1. Свойства конкретного окна задаются при вызове функции CreateWindow определением параметра Style. Константы, определяющие свойства окна, содержатся в специальных файлах, которые подключаются при компиляции. Поскольку свойства фактически определяются значением того или иного бита в константе, комбинация свойств – это просто сумма битовых констант. В отличии от многих рекомендаций для разработчиков все константы здесь определяются непосредственно в программах.
  2. Окно создается на основе зарегистрированного класса. Оно может содержать элементы управления – кнопки, окна редактирования, списки, полосы прокрутки и т.д. Все эти элементы могут создаваться как окна с предопределенными классами (для кнопок BUTTON, для окна редактирования EDIT, для счетчика LISTBOX и т.д.).
  3. Система общается с окном, а следовательно, и с самим приложением посредством посылки сообщений. Эти сообщения должны обрабатываться процедурой окна. Программирование под Windows в значительной степени является программированием обработки таких сообщений. Сообщения генерируются системой также в случаях каких-либо визуальных событий, происходящих с окном или управляющими элементами в нем. К таким событиям относятся перемещение окна или изменение его размеров, нажатие кнопки, выбор элемента в списке, перемещение курсора мыши и т.п. Это понятно, программа должна как-то реагировать на подобные события.
  4. Сообщение имеет код (будем обозначать его в программе VES) и два параметра (WPARAM и LPARAM). Для каждого кода сообщения придумано свое макроимя, хотя это всего лишь целое число. Например, сообщение WM_ CREATE приходит один раз, когда создается окно, WM_ PAINT посылается окну при его перерисовке, сообщение WM_ RBUTTONDOWN генерируется, если щелкнуть правой кнопкой мыши при расположении курсора мыши в области окна и т.д. Параметры сообщения могут не иметь никакого смысла либо играть уточняющую роль. Например, сообщение WM_ COMMAND генерируется системой, когда что-то происходит с управляющими элементами окна. В этом случае по значению параметров можно определить, какой это элемент и что с ним произошло (LPARAM – дескриптор элемента, старшее слово WPARAM – событие, младшее слово WPARAM – обычно идентификатор ресурса). Можно сказать, что сообщение WM_ COMMAND несет сообщение от элемента окна.
  5. Сообщение может генерироваться не только системой, но и самой программой. Например, можно послать сообщение-команду какому-либо элементу управления (добавить элемент в список, передать строку в окно редактирования и т.п.). Иногда посылка сообщений используется как прием программирования. Например, можно придумать свои сообщений так, чтобы при их посылке программа выполнила те или иные действия. Естественно, это сообщение должно «отлавливаться» либо в процедуре какого-нибудь окна, либо в цикле обработки сообщений. Такой подход очень удобен, поскольку позволяет фактически осуществлять циклические алгоритмы так, чтобы возможные изменения с окном во время исполнения такого цикла сразу проявлялись на экране.

 

Структура окна

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-функции

  1. Функция 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        // режим отображения окна

 );

 

  1. Функция 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

  1. wm_ setfocus – установить фокус;
  2. wm_ destroy – сообщение приходит при закрытии окна;
  3. wm_ create – сообщение приходит при создании окна;
  4. wm_ command – выполняются какие-то действия с окном;
  5. wm_ settext – послать элементу строку;
  6. wm_ gettext – сообщение позволяющее, получить строку;
  7. wm_ paint – посылается, когда приложение делает запрос на рисование
  8. wm_ seticon – устанавливает окну диалог
  9. wm_ initdialog – посылается диалоговому окно до того, как оно будет показано.
  10. wm_ rbuttondown – приходит при нажатии на правую кнопку мыши
  11. wm_ lbuttondown – приходит при нажатии на левую кнопку мыши

 

Сообщения, посылаемые списку

  1. lb_addstring – добавление строки;
  2. lb_deletestring – удаление строки;
  3. lb_ gettext – получение текста выделенного элемента;
  4. lb_ getcursel – получение номера выделенного элемента;
  5. lbn_ dblclk – проверка на двойное нажатие кнопки мыши;
  6. lb_findstring – поиск строки;
  7. lb_err – ошибка функции SendMessage;
  8. lb_ getcount – получение количества элементов в списке.

 

Вопросы для подготовки к сдаче лабораторной работы.

 

  1. Что означает термин API?
  2. Понятие окна в операционной системе Windows.
  3. Визуальные элементы управления в операционной системе Windows.
  4. Каким образом вызываются API-функции?
  5. Какая последовательность создания окон в операционной системе Windows.
  6. Что понимается под термином «сообщение»?
  7. Что такое «очередь сообщений» в операционной системе Windows.
  8. Как необходимо проводить обработку сообщений в операционной системе Windows.
  9. Какие API-функции для работы с окнами вы знаете?
  10. Что означает сообщение 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; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!