Г.С.Алтыбаев, А.Н.Иманбекова
Учебно-методическое пособие
Рецензенты:
Аязбаев Т.Л. – к.ф.-м.н., доцент кафедры «Математика и
вычислительная техника» Таразского инновационно-
гуманитарного университета;
Мамаев Ш.М. – д.ф.-м.н., заведующий кафедрой «Математика и
вычислительная техника» Таразского инновационно-
гуманитарного университета;
Сембина Г.К. – к.т.н., доцент, заведующая кафедрой
«Информационные системы» Таразского
государственного университета им. М.Х. Дулати.
ВВЕДЕНИЕ 4
Лабораторная работа №1. Создание окон с помощью Win32 6
API функций
Лабораторная работа №2. Процессы и их создание в Win32 API 19
Лабораторная работа №3. Создание потоков в Win32 API 26
Лабораторная работа №4. Синхронизация потоков при помощи 32
семафоров и критических секций
Лабораторная работа №5. Синхронизация процессов при 52
помощи событий и мьютексов
Лабораторная работа №6. Обмен данными по анонимному 73
каналу с сервером
Лабораторная работа №7. Обмен данными по именованному 94
каналу с сервером
Лабораторная работа №8. Работа с файлами с помощью Win32 112
API функций
Лабораторная работа №9. Работа с атрибутами файлов с 120
помощью Win32 API функций
Лабораторная работа №10. Работа с каталогами с помощью 127
Win32 API функций
Лабораторная работа №11. Использование динамических 133
библиотек для создания приложений
Список рекомендованной литературы 143
3
ВВЕДЕНИЕ
5
Лабораторная работа №1
Создание окон с помощью Win32 API функций
Цель работы:
1. Изучение принципов организации и функционирования стандартных
приложений Windows.
2. Изучение структуры приложений, использующих функции Win32API.
3. Создание каркаса оконного приложения Win32 и исследование его
структуры.
Головная функция
WinMain()
{
...
RegisterClass(&wc);
...
CreateWindow();
...
ShowWindow();
OS
... Windows Оконная
Цикл обработки сообщений функция
while();
{ WndProc()
{
} ...
return 0; }
}
6
Разработчиками операционной системы Windows была создана библиотека
функций, при помощи которых и происходит взаимодействие приложения с
операционной системой, так называемые функции Программного интерфейса
приложений (Application Program Interface, API).
Подмножество этих функций, предназначенных для графического вывода
на дисплей, графопостроитель и принтер, представляет собой Интерфейс
графических устройств (Graphics Device Interface, GDI).
Библиотека API-функций разрабатывалась в расчете на то, что ее можно
использовать для любого языка программирования, а поскольку разные языки
имеют различные типы данных, то были созданы собственные Windows-типы,
которые приводятся к типам данных языков программирования. Отметим
только, что в Windows нет логического типа bool, но есть Windows-тип BOOL,
который эквивалентен целому типу int. Будем рассматривать типы данных
Windows по мере необходимости.
Еще одной особенностью API-функций является использование обратного,
по отношению к принятому в языке С, порядка передачи параметров, как это
реализовано в языке Pascal. В С для идентификации таких функций
использовалось служебное слово pascal, в Windows введены его синонимы
CALLBACK, APIENTRY или WINAPI. По умолчанию С-функции передают
параметры, начиная с конца списка так, что первый параметр всегда находится
на вершине стека. Именно это позволяет использовать в языке С функции с
переменным числом параметров, что в API-функциях невозможно.
Каркас Windows-приложения
В отличие от программы, выполняемой в операционной системе MS-DOS,
даже для создания простейшего приложения под Windows придется проделать
намного больше работы. Чтобы иметь возможность работать с оконным
интерфейсом, заготовка или каркас Windows-приложения должна выполнить
некоторые стандартные действия:
1. Определить класс окна.
2. Зарегистрировать окно.
3. Создать окно данного класса.
4. Отобразить окно.
5. Запустить цикл обработки сообщений.
Термин интерфейс здесь следует понимать как способ взаимодействия
пользователя и приложения. Класс окна – структура, определяющая его
свойства.
С помощью листинга 1 рассмотрим "каркас" Windows-приложения.
8
}
return 0;
}
9
данный макрос потому, что многие определения Windows описаны подобным
образом.
Далее следует прототип оконной функции:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
Оконная функция также является функцией обратного вызова, что связано
с некоторыми особенностями организации вызовов операционной системы. Эта
функция регистрируется в системе, а ее вызов осуществляет операционная
система, когда требуется обработать сообщение. Тип возвращаемого значения
функции LRESULT эквивалентен long для Win32-проекта.
На глобальном уровне описывается имя класса окна приложения в виде
текстовой строки:
TCHAR WinName [] = _T("MainFrame");
Тип TCHAR также преобразуется в wchar_t, если определена константа
_UNICODE, и в char, если константа не определена.
Тип wchar_t эквивалентен типу short и служит для хранения строк в
кодировке Unicode, где для одного символа выделяется 16 бит.
Имя класса окна используется операционной системой для его
идентификации. Имя может быть произвольным, в частности содержать
кириллический текст.
Рассмотрим заголовок головной функции:
int APIENTRY _tWinMain(HINSTANCE This, // Дескриптор текущего
приложения
HINSTANCE Prev, // В современных системах всегда 0
LPTSTR cmd, // Командная строка
int mode) // Режим отображения окна
Для Windows-приложений с Unicode она носит имя wWinMain(), а в 8-
битной кодировке -WinMain(), выбор варианта определяется префиксом _t, что
также является стандартным приемом в библиотеке API-функций. Функция
имеет четыре параметра, устанавливаемых при загрузке приложения:
− This – дескриптор, присваиваемый операционной системой при загрузке
приложения;
− Prev – параметр предназначен для хранения дескриптора предыдущего
экземпляра приложения, уже загруженного системой. Сейчас он
потерял свою актуальность и сохранен лишь для совместимости со
старыми приложениями (начиная с Windows 95, параметр
устанавливается в нулевое значение);
− cmd – указатель командной строки, но без имени запускаемой
программы. Тип LPTSTR эквивалентен TCHAR*;
− mode – режим отображения окна.
Дескриптор (описатель) – тип данных Windows, который используется для
описания объектов операционной системы. Дескриптор напоминает индекс
хеш-таблицы и позволяет отслеживать состояние объекта в памяти при его
перемещении по инициативе операционной системы. Предусмотрено много
типов дескрипторов: HINSTANCE, HWND и др., но все они являются 32-
разрядными целыми числами.
Внутри головной функции описаны три переменные:
10
− hWnd — предназначена для хранения дескриптора главного окна
программы;
− msg — это структура, в которой хранится информация о сообщении,
передаваемом операционной системой окну приложения:
struct MSG
{
HWND hWnd; // Дескриптор окна
UINT message; // Номер сообщения
WPARAM wParam; // 32-разрядные целые содержат
LPARAM lParam; // дополнительные параметры сообщения
DWORD time; // Время посылки сообщения в миллисекундах
POINT pt; // Координаты курсора (x,y)
};
struct POINT
{
LONG x,y;
};
Тип WPARAM – "короткий параметр" был предназначен для передачи 16-
разрядного значения в 16-разрядной операционной системе, в Win32 это такое
же 32-разрядное значение, что и LPARAM.
– wc – структура, содержащая информацию по настройке окна.
Требуется заполнить следующие поля:
wc.hlnstance = This; Дескриптор текущего приложения.
wc.lpszClassName = WinName;
Имя класса окна.
• wc.lpfnWndProc = WndProc;
Имя оконной функции для обработки сообщений.
• wc.style = CS_HREDRAW | CS_VREDRAW;
Такой стиль определяет автоматическую перерисовку окна при изменении
его ширины или высоты.
• wc.hIcon = LoadIcon(NULL,IDI_APPLICATION);
Дескриптор пиктограммы (иконки) приложения. Функция LoadIcon()
обеспечивает ее загрузку. Если первый параметр NULL, используется
системная пиктограмма, которая выбирается по второму параметру из
следующего набора:
– IDI_APPLICATION — стандартная иконка;
– IDI_ASTERISK — звездочка;
– IDI_EXCLAMATION — восклицательный знак;
– IDI_HAND — ладонь;
– IDI_QUESTION — вопросительный знак;
– IDI_WINLOGO — логотип Windows;
• wc.hCursor = LoadCursor(NULL,IDC_ARROW);
Аналогичная функция LoadCursor () обеспечивает загрузку графического
образа курсора, где нулевой первый параметр также означает использование
системного курсора, вид которого можно выбрать из списка:
– IDC_ARROW — стандартный курсор;
– IDC_APPSTARTING — стандартный курсор и маленькие песочные часы;
– IDC_CROSS — перекрестие;
– IDC_IBEAM — текстовый курсор;
11
– IDC_NO — перечеркнутый круг;
– IDC_SIZEALL — четырехлепестковая стрелка;
– IDC_SIZENESW — двухлепестковая стрелка, северо-восток и юго-запад;
– IDC_SIZENWSE — двухлепестковая стрелка, северо-запад и юго-восток;
– IDC_SIZENS — двухлепестковая стрелка, север и юг;
– IDC_SIZEWE — двухлепестковая стрелка, запад и восток;
– IDC_UPARROW — стрелка вверх;
– IDC_WAIT — песочные часы;
• wc.lpszMenuName = NULL;
Ссылка на строку главного меню, при его отсутствии NULL.
• wc.cbClsExtra = 0;
Дополнительные параметры класса окна.
• wc.cbWndExtra = 0; Дополнительные параметры окна.
• wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
Дескриптор кисти, которая используется для заполнения окна.
Стандартная конструкция, создает системную кисть белого цвета
WHITE_BRUSH. Требуется явное преобразование типа — HBRUSH.
После того как определены основные характеристики окна, можно это
окно создать при помощи API-функции CreateWindow(), где также нужно задать
параметры:
1. WinName — имя, которое присвоено классу окна.
2. _T("Каркас Windows-приложения") — заголовок окна в виде строки
Unicode либо С-строки.
3. WS_OVERLAPPEDWINDOW — макрос, определяющий стиль
отображения стандартно-
го окна, имеющего системное меню, заголовок, рамку для изменения размеров,
а также кнопки минимизации, развертывания и закрытия. Это наиболее общий
стиль окна, он определен так:
#define WS_OVERLAPPEDWINDOW
(WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|
WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX)
Можно создать другой стиль, используя комбинацию стилевых макросов
при помощи операции логического сложения, вот некоторые из них:
WS_OVERLAPPED — стандартное окно с рамкой;
WS_CAPTION — окно с заголовком;
WS_THICKFRAME — окно с рамкой;
WS_MAXIMIZEBOX — кнопка распахивания окна;
WS_MINIMIZEBOX — кнопка минимизации;
WS_SYSMENU — системное меню;
WS_HSCROLL — горизонтальная панель прокрутки;
WS_VSCROLL — вертикальная панель прокрутки;
WS_VISIBLE — окно отображается;
WS_CHILD — дочернее окно;
WS_POPUP — всплывающее окно;
4. Следующие два параметра определяют координаты левого верхнего угла
окна (x,y) , еще два параметра: Width — ширину и Height — высоту окна в
12
пикселах. Задание параметра CW_USEDEFAULT означает, что система
сама выберет для отображения окна наиболее (с ее точки зрения) удобное
место и размер.
5. Следующий параметр — указатель на структуру меню, или NULL, при
его отсутствии.
6. Далее требуется указать дескриптор приложения, владельца окна — This.
7. И, наконец, указатель на дополнительную информацию, в нашем случае
– NULL.
Окно создано, и с ним можно работать, но пока оно не отображается. Для
того чтобы окно увидеть, необходимо его отобразить с помощью функции
ShowWindow(hWnd, mode), которая принимает два параметра: hWnd –
дескриптор окна и mode – режим отображения. В нашем случае используется
значение, полученное при открытии приложения через параметр головной
функции.
Далее, заключительная часть головной функции – цикл обработки
сообщений. Он задается оператором while, аргументом которого является
функция GetMessage(&msg, NULL, 0, 0). Такой цикл является обязательным для
всех Windows-приложений, его цель — получение и обработка сообщений,
передаваемых операционной системой. Операционная система ставит
сообщения в очередь, откуда они извлекаются функцией GetMessage() по мере
готовности приложения:
□ первым параметром функции является &msg — указатель на структуру
MSG, где и хранятся сообщения;
□ второй параметр hWnd — определяет окно, для которого предназначено
сообщение, если же необходимо перехватить сообщения всех окон данного
приложения, он должен быть NULL;
□ остальные два параметра определяют [min, max] диапазон получаемых
сообщений. Чаще всего необходимо обработать все сообщения, тогда эти
параметры должны быть равны 0.
Сообщения определяются их номерами, символические имена для них
определены в файле включений winuser.h. Префикс всех системных сообщений
WM_.
Внутри цикла расположены две функции:
TranslateMessage(&msg);
DispatchMessage(&msg);
Первая из них транслирует код нажатой клавиши в клавиатурные
сообщения WM_CHAR. При этом в переменную wParam структуры msg
помещается код нажатой клавиши в Windows-кодировке CP-1251, в младшее
слово lParam — количество повторений этого сообщения в результате
удержания клавиши в нажатом состоянии, а в старшее слово — битовая карта
со значениями, приведенными в таблица 1.
Использование этой функции не обязательно и нужно только для
обработки сообщений от клавиатуры.
Вторая функция, DispatchMessage(&msg), обеспечивает возврат
преобразованного сообщения обратно операционной системе и инициирует
вызов оконной функции данного приложения для его обработки.
13
Данным циклом и заканчивается головная функция.
Далее следует описать оконную функцию WndProc(), и построение каркаса
Windows-приложения будет закончено.
15
Порядок выполнения работы
Рассмотрим сначала, как можно "вручную" создать минимальное
приложение Win32. Загрузив Visual Studio 2010, выполним команду File | New |
Project... и выберем тип проекта — Win32 Project. В раскрывающемся списке
Location выберем путь к рабочей папке, а в поле Name имя проекта (рисунок
3). В следующем диалоговом окне, приведенном на рисунке 4, нажимаем
кнопку Next, а в окне опций проекта (рисунок 5) выберем флажок Empty
project (Пустой проект) и нажмем кнопку Finish — получим пустой проект, в
котором нет ни одного файла.
16
Рисунок 5. Окно опций проекта
17
Программа не делает ничего полезного, поэтому, запустив ее на
выполнение кнопкой ► (Start Debugging), мы получим изображенное на
рисунке 8 пустое окно, имеющее заголовок и набор стандартных кнопок.
Контрольные вопросы
1. Что такое Win32 API?
2. Какие операционные системы обслуживает API Win32?
3.Какие особенности имеет Win32 API?
4. Какие преимущества программирования дает Win32 API?
5. Какой основной тип переменных используется в Win32?
6. Для управления каких систем могут быть написаны программы с
использованием Win32?
18
Лабораторная работа №2
Тема: Процессы и их создание в Win32 API
Цель работы:
1. Изучение основных функций Win32API, используемых для управления
процессами
2. Разработка простейшей программы, демонстрирующей создание и
завершение процесса.
3. Разработка приложения Win32 API, реализующего функции указанные в
варианте.
22
return 0;
}
23
процесса.
Приведем программу, которая демонстрируют работу функции
TerminateProcess. Для этого сначала создадим бесконечный процесс-счетчик,
который назовем ConsoleProcess.exe и расположим на диске C.
24
}
}
// закрываем дескрипторы нового процесса в текущем процессе
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
Заданиe на выполнение
Написать программы двух консольных процессов Parent и Child, которые
выполняют следующие действия.
Процесс Parent:
1. Создает бинарный файл, записи которого имеют следующую структуру:
struct emp
{
int num; // номер зачетки
char name[10]; // имя студента
double grade; // средний бал
};
Имя файла и данные о студентах вводятся с консоли.
2. Выводит созданный файл на консоль.
3. Запрашивает с консоли номер зачетки, имя студента и новый средний
бал этого студента.
4. Формирует командную строку, которая содержит имя созданного файла
и информацию, полученную в пункте 3.
5. Запускает дочерний процесс Child, которому как параметр передается
командная строка, сформированная в пункте 4.
6. Ждет завершения работы процесса Child.
7. Выводит откорректированный файл на консоль.
8. Завершает свою работу.
Процесс Child:
1. Выводит на консоль информацию, полученную через командную строку.
2. Корректирует в файле, созданном процессом Parent, нужную запись, т.е.
устанавливает новый средний бал студента.
3. Завершает свою работу.
Для ожидания завершения работы процесса Child использовать
функцию:
DWORD WaitForSingleObject(
HANDLE hHandle, // дескриптор объекта
DWORD dwMilliseconds // интервал ожидания в миллисекундах
);
где второй параметр установить равным INFINITE, например
WaitForSingleObject(hProcess, INFINITE); // ждать завершения
процесса
Здесь hProcess – дескриптор процесса Child.
Контрольные вопросы
1. С помощью каких функций можно создать процесс?
2. С помощью каких функций можно удалит процесс?
3. Какую фунцию выполняет CreateProcess?
25
4. Какую фунцию выполняет OpenProcess?
5. Какую фунцию выполняет ExitProcess ?
6. Какую фунцию выполняет WaitForSingleObject?
Лабораторная работа №3
Тема: Создание потоков в Win32 API
Цель работы:
1. Изучение основных функций Win32 API, используемых для управления
потоками.
2. Разработка простейшей программы, демонстрирующей создание и
завершение процесса.
3. Разработка приложения Win32 API, реализующую указанные в варианте
функции.
2. Потоки и процессы
Когда мы говорим "программа" (application), то обычно имеем в виду
понятие, в терминологии операционной системы обозначаемое как "процесс".
Процесс состоит из виртуальной памяти, исполняемого кода, потоков и данных.
Процесс может содержать много потоков, но обязательно содержит, по крайней
мере, один. Поток, как правило, имеет "в собственности" минимум ресурсов; он
зависит от процесса, который и распоряжается виртуальной памятью, кодом,
данными, файлами и другими ресурсами ОС.
Почему мы используем потоки вместо процессов, хотя, при
необходимости, приложение может состоять и из нескольких процессов?
Дело в том, что переключение между процессами — значительно более
трудоемкая операция, чем переключение между потоками. Другой довод в
пользу использования потоков — то, что они специально задуманы для
разделения ресурсов; разделить ресурсы между процессами (имеющими
раздельное адресное пространство) не так-то просто.
3. Приоритеты потоков
Интерфейс Win32 API позволяет программисту управлять распределением
времени между потоками; это распространяется и на приложения, написанные
на Delphi. Операционная система планирует время процессора в соответствии с
27
приоритетами потоков.
Приоритет потока – величина, складывающаяся из двух составных частей:
приоритета породившего поток процесса и собственно приоритета потока.
Когда поток создается, ему назначается приоритет, соответствующий
приоритету породившего его процесса.
В свою очередь, процессы могут иметь следующие классы приоритетов.
− Real time;
− Normal;
− High;
− Below normal;
− Above normal;
− Idle.
Класс реального времени задает приоритет даже больший, чем у многих
процессов операционной системы. Такой приоритет нужен для процессов,
обрабатывающих высокоскоростные потоки данных. Если такой процесс не
завершится за короткое время, пользователь почувствует, что система
перестала откликаться, т. к. даже обработка событий мыши не получит времени
процессора.
Использование класса High ограничено процессами, которые должны
завершаться за короткое время, чтобы не вызвать сбойной ситуации. Пример –
процесс, который посылает сигналы внешнему устройству; причем устройство
отключается, если не получит своевременный сигнал. Если у вас возникли
проблемы с производительностью вашего приложения, было бы неправильно
решать их просто за счет повышения его приоритета до high – такой процесс
также влияет на всю ОС. Возможно, в этом случае следует модернизировать
компьютер.
Большинство процессов запускается в рамках класса с нормальным
приоритетом. Нормальный приоритет означает, что процесс не требует какого-
либо специального внимания со стороны операционной системы.
И, наконец, процессы с фоновым приоритетом запускаются лишь в том
случае, если в очереди Диспетчера задач нет других процессов. Обычные виды
приложений, использующие такой приоритет, – это программы сохранения
экрана и системные агенты (system agents).
Программисты могут использовать фоновые процессы для организации
завершающих операций и реорганизации данных. Примерами могут служить
сохранение документа или резервное копирование базы данных.
Приоритеты имеют значения от 0 до 31. Процесс, породивший поток,
может впоследствии изменить его приоритет; в этой ситуации программист
имеет возможность управлять скоростью отклика каждого потока.
Нормальная практика для асимметричных потоков – это назначение
потоку, обрабатывающему ввод, более высокого приоритета, а всем остальным
– более низкого или даже приоритета idle, если этот поток должен выполняться
только во время простоя системы.
28
4. Функции работы с потоками
Создается поток функцией CreateThread, которая имеет следующий прототип:
HANDLE CreateThread (
LPSECURITY ATTRIBUTES lpThreadAttributes,// атрибуты защиты
DWORD dwStackSize,// размер стека потока в байтах
LPTHREAD_START_ROUTINE lpStartAddress,// адрес исполняемой
функции
LPVOID lpParameter,// адрес параметра
DWORD dwCreationFlags,// флаги создания потока
LPDWORD lpThreadId// идентификатор потока
) ;
При успешном завершении функция CreateThread возвращает дескриптор
созданного потока и его идентификатор, который является уникальным для
всей системы. В противном случае эта функция возвращает значение NULL.
Кратко опишем назначение параметров функции CreateThread. Параметр
lpThreadAttributes устанавливает атрибуты защиты создаваемого потока. До тех
пор пока мы не изучим структуру системы безопасности в Windows, то есть
раздел Windows NT Access Control из интерфейса программирования
приложений Win32 API, мы будем устанавливать значения этого параметра в
NULL при вызове почти всех функций ядра Windows. Это означает, что
атрибуты защиты потока совпадают с атрибутами защиты создавшего его
процесса. О процессах будет подробно рассказано в следующем разделе.
Параметр dwStackSize определяет размер стека, который выделяется
потоку при запуске. Если этот параметр равен нулю, то потоку выделяется стек,
размер которого равен по умолчанию 1 Мб. Это наименьший размер стека,
который может быть выделен потоку. Если величина параметра dwStackSize
меньше, значения, заданного по умолчанию, то все равно потоку выделяется
стек размеров в 1Мб. Операционная система Windows округляет размер стека
до одной страницы памяти, который обычно равен 4 Кб.
Параметр lpStartAddress указывает на исполняемую потоком функцию. Эта
функция должна иметь следующий прототип:
DWORD WINAPI ThreadProc (LPVOID lpParameters);
Параметр lpParameter является единственным параметром, который будет
передан функции потока.
Параметр dwCreationFlags определяет, в каком состоянии будет создан
поток. Если значение этого параметра равно 0, то функция потока начинает
выполняться сразу после создания потока. Если же значение этого параметра
равно CREATE_SUSPENDED, то поток создается в подвешенном состоянии. В
дальнейшем этот поток можно запустить вызовом функции ResumeThread.
Параметр lpThreadId является выходным, то есть его значение устанавливает
Windows. Этот параметр должен указывать на переменную, в которую Windows
поместит идентификатор потока, который уникален для всей системы и может
в дальнейшем использоваться для ссылок на поток.
Приведем пример программы, которая использует функцию CreateThread
для создания потока, и продемонстрируем способ передачи параметров
исполняемой потоком функции.
29
Листинг 1. Пример создания потока функцией CreateThread
#include <windows.h>
#include <iostream.h>
volatile int n;
DWORD WINAPI Add(LPVOID iNum)
{
cout << "Thread is started." << endl;
n += (int)iNum;
cout << "Thread is finished." << endl; return 0;
}
int main()
{
int inc = 10;
HANDLE hThread;
DWORD IDThread;
cout << "n = " << n << endl;
hThread = CreateThread(NULL, 0, Add, (void*)inc, 0, &IDThread);
if (hThread == NULL)
return GetLastError();
// ждем пока поток Add закончит работу
WaitForSingleObject(hThread, INFINITE);
// закрываем дескриптор потока Add
CloseHandle(hThread);
cout << "n = " << n << endl;
return 0;
}
Отметим, что в этой программе используется функция WaitForSingleObject,
которая ждет завершения потока Add.
Задание на выполнение
А. Изучить программу для консольного процесса, который состоит из двух
потоков: main и worker.
Поток main должен выполнить следующие действия:
1. Создать массив целых чисел, размерность и элементы которого вводятся с
консоли.
2. Создать поток worker.
3. Найти минимальный и максимальный элементы массива и вывести их на
консоль. После каждого сравнения элементов «спать» 7 миллисекунд.
4. Дождаться завершения потока worker.
5. Подсчитать количество элементов в массиве, значение которых больше
среднего значения элементов массива, и вывести его на консоль.
6. Завершить работу.
Поток worker должен выполнить следующие действия:
1. Найти среднее значение элементов массива. После каждого суммирования
элементов «спать» 12 миллисекунд.
2. Завершить свою работу.
Для ожидания завершения работы потока worker использовать функцию:
DWORD WaitForSingleObject(
HANDLE hHandle,// дескриптор объекта
DWORD dwMilliseconds // интервал ожидания в миллисекундах
30
);
где второй параметр установить равным INFINITE. Например
WaitForSingleObject(hThread, INFINITE); // ждать завершения потока
Здесь hThread – дескриптор потока worker.
Для засыпания использовать функцию:
VOID Sleep(
DWORD dwMilliseconds // миллисекунды
);
Например,
Sleep (12); // спать 12 миллисекунд
3. Модифицировать и отладить программу в соответствии со своим
вариантом.
Варианты заданий
1) Поток worker должен найти значение факториала элементов массива.
2) Поток worker должен найти значение суммы четных элементов
массива.
3) Поток worker должен найти значение количество четных элементов
массива.
4) Поток worker должен найти значение количество нечетных элементов
массива.
5) Поток worker должен найти значение суммы нечетных элементов
массива.
6) Поток worker должен найти значение среднее значение четных
элементов массива.
7) Поток worker должен найти значение среднее значение нечетных
элементов массива.
8) Поток worker должен найти значение факториала четных элементов
массива.
9) Поток worker должен найти значение факториала нечетных элементов
массива.
10) Поток worker должен найти значение среднее значение элементов
массива, исключая максимальный элемент.
11) Поток worker должен найти значение среднее значение элементов
массива, исключая минимальный элемент.
12) Поток worker должен найти значение факториала элементов массива,
исключая максимальный элемент.
13) Поток worker должен найти значение факториала элементов массива,
исключая минимальный элемент.
14) Поток worker должен найти значение суммы нечетных элементов
массива и минимального элемента.
15) Поток worker должен найти значение суммы четных элементов
массива и минимального элемента.
16) Поток worker должен найти значение факториала элементов массива.
17) Поток worker должен найти значение суммы четных элементов
массива.
18) Поток worker должен найти значение количество четных элементов
31
массива.
19) Поток worker должен найти значение количество нечетных элементов
массива.
20) Поток worker должен найти значение суммы нечетных элементов
массива.
21) Поток worker должен найти значение среднее значение четных
элементов массива.
22) Поток worker должен найти значение среднее значение нечетных
элементов массива.
23) Поток worker должен найти значение факториала четных элементов
массива.
24) Поток worker должен найти значение факториала нечетных элементов
массива.
Контрольные вопросы
1. Дайте определение понятию поток.
2. Что такое «симметричные» и «асимметричные» потоки. В каких
ситуациях возникает необходимость в асимметричных потоках?
2. Каково различие процессов от потоков?
3. Что такое приоритет потока?
4. Перечислите классы приоритетов для процессов.
5. Каким образом можно добавить новый поток в текущий процесс?
Лабораторная работа №4
Тема: Синхронизация потоков при помощи семафоров и критических секций
Цель работы:
1. Изучить объекты синхронизации потоков семафор и критические секции
2. В соответствии с заданным вариантом разработать приложение,
реализующее синхронизацию потоков с помощью семафоров.
3. В соответствии с заданным вариантом разработать приложение,
реализующее синхронизацию потоков с помощью критических секций.
33
}
return 0;
}
int main()
{
int i,j;
HANDLE hThread;
DWORD IDThread;
hThread=CreateThread(NULL, 0, thread, NULL, 0, &IDThread);
if (hThread == NULL)
return GetLastError();
// так как потоки не синхронизированы,
// то выводимые строки непредсказуемы for (j = 10; j < 20; j++)
{
for (i = 0; i < 10; i++)
{
cout << j << ' '; cout << flush;
Sleep(22);
}
cout << endl;
}
// ждем, пока поток thread закончит свою работу
WaitForSingleObject(hThread, INFINITE);
return 0;
}
int main()
{
int i, j;
HANDLE hThread;
DWORD IDThread;
// инициализируем критическую секцию
InitializeCriticalSection(&cs);
hThread=CreateThread(NULL, 0, thread, NULL, 0, &IDThread);
if (hThread == NULL)
return GetLastError();
// потоки синхронизированы, поэтому каждая
// строка содержит только одинаковые числа
for (j = 10; j < 20; j++)
{
// попытка войти в критическую секцию
TryEnterCriticalSection(&cs);
for (i = 0; i < 10; i++)
{
cout << j << " "; cout.flush();
}
cout << endl;
// выход из критической секции
LeaveCriticalSection(&cs);
}
// удаляем критическую секцию
DeleteCriticalSection(&cs);
// ждем завершения работы потока thread
WaitForSingleObject(hThread, INFINITE);
return 0;
}
2. Семафоры Дийкстры
Семафор – это неотрицательная целая переменная, значение которой
может изменяться только при помощи неделимых операций. Под понятием
неделимая операция мы понимаем такую операцию, выполнение которой не
может быть прервано. Семафор считается свободным, если его значение
больше нуля, в противном случае семафор считается занятым. Пусть s -
семафор, тогда над ним можно определить следующие неделимые операции:
P(s) {
36
если s >0 то s = s - 1; // поток продолжает работу
иначе ждать освобождения s; // поток переходит в состояние
ожидания
}
V(s) {
если потоки ждут освобождения s, то освободить один поток;
иначе s = s + 1;
}
Семафор с операциями P и V называется семафором Дийкстры, который
первым использовал семафоры для решения задач синхронизации. Из
определения операций над семафором видно, что если поток выдает операцию
P и значение семафора больше нуля, то значение семафора уменьшается на 1 и
этот поток продолжает свою работу, в противном случае поток переходит в
состояние ожидания до освобождения семафора другим потоком. Вывести из
состояния ожидания поток, который ждет освобождения семафора, может
только другой поток, который выдает операцию V над этим же семафором.
Потоки, ждущие освобождения семафора, выстраиваются в очередь к этому
семафору. Дисциплина обслуживания очереди зависит от конкретной
реализации. Очередь может обслуживаться как по правилу FIFO, так и при
помощи более сложных алгоритмов, учитывая приоритеты потоков.
Семафор, который может принимать только значения 0 или 1, называется
двоичным или бинарным семафором. Чтобы подчеркнуть отличие бинарного
семафора от не бинарного семафора, то есть такого семафора, значение
которого может быть больше 1, последний обычно называют считающими
семафором. Покажем, как бинарный семафор может использоваться для
моделирования критических секций и событий. Для этого сначала рассмотрим
следующие потоки.
semaphor s = 1; // семафор свободен
void thread_1( ) void thread_2( )
{ {
P(s); P(s);
if (n%2 == 0) n++;
n = a; V(s);
else .
n = b; .
V(s); .
. }
}
Как следует из определения операций над семафором, данный подход
решает проблему взаимного исключения одновременного доступа к
переменной n для потоков thread_1 и thread_2. Таким образом, бинарный
семафор позволяет решить проблему взаимного исключения.
Теперь предположим, что поток thread_1 должен производить проверку
значения переменной n только после того, как поток thread_2 увеличит
значение этой переменной. Для решения этой задачи модифицируем наши
программы следующим образом:
semaphor s = 0; // семафор занят
void thread_1( ) void thread_2( )
{ {
37
P(s); n++;
if (n%2 == 0) V(s);
n = a; .
else .
n = b; .
. }
}
Как видно из этих программ, бинарный семафор позволяет также решить
задачу условной синхронизации.
3. Семафоры в Windows
Семафоры в операционных системах Windows описываются объектами
ядра Semaphores, Семафор находится в сигнальном состоянии, если его
значение больше нуля. В противном случае семафор находится в не сигнальном
состоянии. Создаются семафоры посредством вызова функции
CreateSemaphore, которая имеет следующий прототип:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttribute, // атрибуты защиты
LONG lInitialCount, // начальное значение семафора
LONG lMaximumCount, // максимальное значение семафора
LPCTSTR lpName // имя семафора
);
Как и обычно, пока значение параметра lpSemaphoreAttributes будем
устанавливать в NULL. Основную смысловую нагрузку в этой функции несут
второй и третий параметры. Значение параметра lInitialCount устанавливает
начальное значение семафора, которое должно быть не меньше 0 и не больше
его максимального значения, которое устанавливается параметром
lMaximumCount.
В случае успешного завершения функция CreateSemaphore возвращает
дескриптор семафора, в случае неудачи - значение NULL. Если семафор с
заданным именем уже существует, то функция CreateSemaphore возвращает
дескриптор этого семафора, а функция GetLastError, вызванная после функции
CreateSemaphore вернет значение ERROR_ALREADY_EXISTS.
Значение семафора уменьшается на 1 при его использовании в функции
ожидания. Увеличить значение семафора можно посредством вызова функции
ReleaseSemaphore, которая имеет следующий прототип:
BOOL Release Semaphore(
HANDLE hSemaphore, // дескриптор семафора
LONG lRelease Count, // положительное число,
// на которое увеличивается значение семафора LPLONG
lpPreviousCount // предыдущее значение семафора
);
В случае успешного завершения функция ReleaseSemaphore возвращает
значение TRUE, в случае неудачи - FALSE. Если значение семафора плюс
значение параметра lReleaseCount
больше максимального значения семафора, то функция ReleaseSemaphore
возвращает значение FALSE и значение семафора не изменяется.
Значение параметра lpPreviousCount этой функции может быть равно
NULL. В этом случае предыдущее значение семафора не возвращается.
38
Приведем пример программы, в которой считающий семафор используется
для синхронизации работы потоков. Для этого сначала рассмотрим не
синхронизированный вариант этой программы.
int main()
{
int i;
HANDLE hThread;
DWORD IDThread;
cout << "An initial state of the array: ";
for (i = 0; i < 10; i++)
cout << a[i] <<' '; cout << endl;
// создаем семафор
hSemaphore=CreateSemaphore(NULL, 0, 10, NULL); if (hSemaphore
== NULL)
return GetLastErrorO;
// создаем поток, который готовит элементы массива hThread =
CreateThread(NULL, 0, thread, NULL, 0, &IDThread); if (hThread ==
NULL)
return GetLastError();
// поток main выводит элементы массива // только после их
подготовки потоком thread cout << "A final state of the array: ";
for (i = 0; i < 10; i++)
{
WaitForSingleObject(hSemaphore, INFINITE);
cout << a[i] << ' ';
cout.flush();
}
cout << endl;
CloseHandle(hSemaphore);
CloseHandle(hThread);
return 0;
}
Может возникнуть следующий вопрос: почему для решения этой задачи
используется именно считающий семафор и почему его максимальное значение
равно 10. Конечно, поставленную задачу можно было бы решить и другими
способами. Но дело в том, что считающие семафоры предназначены именно
40
для решения подобных задач. Подробнее, считающие семафоры используются
для синхронизации доступа к однотипным ресурсам, которые производятся
некоторым потоком или несколькими потоками, а потребляются другим
потоком или несколькими потоками. В этом случае значение считающего
семафора равно количеству произведенных ресурсов, а его максимальное
значение устанавливается равным максимально возможному количеству таких
ресурсов. При производстве единицы ресурса значение семафора увеличивается
на единицу, а при потреблении единицы ресурса значение семафора
уменьшается на единицу. В нашем примере ресурсами являются элементы
массива, заполненные потоком thread, который является производителем этих
ресурсов. В свою очередь поток main является потребителем этих ресурсов,
которые он выводит на консоль. Так как в общем случае мы не можем сделать
предположений о скоростях работы параллельных потоков, то максимальное
значение считающего семафора должно быть установлено в максимальное
количество производимых ресурсов. Если поток потребитель ресурсов работает
быстрее чем поток производитель ресурсов, то, вызвав функцию ожидания
считающего семафора, он вынужден будет ждать, пока поток- производитель не
произведет очередной ресурс. Если же наоборот, поток-производитель работает
быстрее чем поток-потребитель, то первый поток произведет все ресурсы и
закончит свою работу, не ожидая, пока второй поток потребит их. Такая
синхронизация потоков производителей и потребителей обеспечивает их
максимально быструю работу.
Доступ к существующему семафору можно открыть с помощью одной из
функций CreateSemaphore или OpenSemaphore. Если для этой цели
используется функция CreateSemaphore, то значения параметров lInitialCount и
lMaximalCount этой функции игнорируются, так как они уже установлены
другим потоком, а поток, вызвавший эту функцию, получает полный доступ к
семафору с именем, заданным параметром lpName. Теперь рассмотрим
функцию OpenSemaphore, которая используется в случае, если известно, что
семафор с заданным именем уже существует. Эта функция имеет следующий
прототип:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // флаги доступа
BOOL bInheritHandle, // режим наследования
LPCTSTR lpName // имя события
);
Параметр dwDesiredAccess определяет доступ к семафору, и может
быть равен любой логической комбинации следующих флагов:
SEMAPHORE_ALL_ACCESS
SEMAPHORE_MODIFY_STATE
SYNCHRONIZE
Флаг SEMAPHORE_ALL_ACCESS устанавливает для потока полный
доступ к семафору. Это означает, что поток может выполнять над семафором
любые действия. Флаг SEMAPHORE_MODIFY_STATE означает, что поток
может использовать только функцию ReleaseSemaphore для изменения значения
семафора. Флаг SYNCHRONIZE означает, что поток может использовать
семафор только в функциях ожидания. Отметим, что последний режим
41
поддерживается только на платформе Windows NT/2000.
Задание на выполнение
Написать программу для консольного процесса, который состоит из трёх
потоков: main , work, и третьего (см. варианты).
Глобальные переменные не использовать!
Индивидуальные варианты:
1. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль
каждого нового элемента массива.
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут выведены на консоль k элементов
массива).
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− найти в массиве неповторяющиеся элементы (разместить их в массиве
слева, остальные соответственно справа). Элементы - символы.
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком main, использовать бинарный семафор!):
− ждёт от потока main сигнал о начале суммирования;
− выполнить суммирование элементов итогового массива до заданной
позиции k;
− вывести итоговую сумму.
2. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток SumElement;
− освободить выходной поток stdout после вывода на консоль каждого
42
нового элемента массива.
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу используя бинарный
семафор для синхронизации с потоком main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− найти в массиве повторяющиеся элементы (разместить их группы в
массиве слева, остальные соответственно справа). Элементы –
вещественные числа.
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будет сформирован итоговый массив.
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать
− ждёт от потока work сигнал о начале суммирования;
− выполнить суммирование элементов итогового массива;
− вывести итоговую сумму.
3. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива.
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− -известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут выведены на консоль k элементов).
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− cортировка методом “пузырька”. Элементы - вещественные числа
двойной точности;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени.
Поток SumElement должен выполнить следующие действия (Для
43
синхронизации с потоком main, использовать бинарный семафор!):
− ждёт от потока main сигнал о начале суммирования;
− выполнить суммирование элементов итогового массива до заданной
позиции k;
− вывести итоговую сумму.
4. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток MultElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work.
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main – использовать семафор. Проверить работу используя бинарный
семафор для синхронизации с потоком main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве элементов из диапазона [A,B] (разместить их в массиве
слева, остальные элементы массива - заполнить нулями). Элементы -
целые числа без знака. Числа A,B ввести в потоке main.
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток MultElement о начале работы (момент запуска
произойдёт после того, будет сформирована часть итогового массива
(когда будут найдены все элементы из диапазона [A, B]).
Поток MultElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать критическую секцию!):
− ждёт от потока work сигнал о начале работы;
− выполнить произведение элементов итогового массива (когда будут
найдены все элементы из диапазона [A, B]);
− вывести произведение.
5. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
44
параллельно с работой потока work;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут выведены на консоль k элементов
массива).
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!)'.
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− сортировка выбором. Элементы - символы;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени.
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком main, использовать бинарный семафор!)'
− ждёт от потока main сигнал о начале суммирования;
− выполнить суммирование элементов (кодов символов) итогового
массива до заданной позиции k;
− вывести итоговую сумму.
6. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− запросить число А;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work.
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу используя бинарный
семафор для синхронизации с потоком main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве элементов >А (разместить их в массиве слева,
остальные элементы массива -заполнить нулями). Элементы - целые
числа без знака. Число А ввести в потоке main;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будет сформирован итоговый массив.
Поток SumElement должен выполнить следующие действия (Для
45
синхронизации с потоком work, использовать критическую секцию!):
− от потока work сигнал о начале суммирования;
− выполнить суммирование элементов итогового массива;
− вывести итоговую сумму.
7. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут готовы к печати k - элементов массива).
Поток work должен выполнить следующие действия Для синхронизации с
потоком main – использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!.
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− Поиск в массиве простых чисел (разместить их в массиве слева,
остальные элементы массива - справа). Элементы - целые числа без
знака.
− извещать поток main о новом элементе, после каждого готового
элемента отдыхать в течение заданного интервала времени;
− поток SumElement должен выполнить следующие действия Для
синхронизации с потоком main, использовать бинарный семафор!
− ждёт от потока main сигнал о начале суммирования;
− выполнить суммирование элементов итогового массива до заданной
позиции k, вывести итоговую сумму.
8. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток CountElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− запросить символ X;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− поток work должен выполнить следующие действия (Для
синхронизации с потоком main - использовать семафор. Проверить
46
работу используя бинарный семафор для синхронизации с потоком
main, объяснить отличия, если есть!)
− запросить у пользователя временной интервал, требуемый для
отдыха после подготовки одного элемента в массиве;
− поиск в массиве элементов =Х (разместить их в массиве слева,
остальные элементы массива -справа). Элементы - символы. X ввести в
потоке main;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток CountElement о начале работы (момент запуска
произойдёт после того, будет сформирован итоговый массив.
Поток CountElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать критическую секцию!)
− ждёт от потока work сигнал о начале суммирования;
− подсчитать количество элементов равных X;
− вывести итоговую сумму.
9. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток MultElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− известить поток MultElement о начале работы (момент запуска
произойдёт после того, будут выведены на консоль k элементов
массива).
− поток work должен выполнить следующие действия (Для
синхронизации с потоком main - использовать семафор. Проверить
работу программы используя критическую секцию для синхронизации
с потоком main, объяснить отличия, если есть!)
− запросить у пользователя временной интервал, требуемый для
отдыха после подготовки одного элемента в массиве;
− поиск в массиве элементов <А (разместить их в массиве слева,
остальные элементы массива - справа). Элементы - вещественные числа.
Число А ввести в потоке main;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− поток MultElement должен выполнить следующие действия (Для
синхронизации с потоком main, использовать бинарный семафор!)
− ждёт от потока main сигнал о начале суммирования;
47
− выполнить произведение элементов итогового массива до заданной
позиции k;
− вывести итоговое произведений.
10. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива.
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− поток work должен выполнить следующие действия (Для
синхронизации с потоком main - использовать семафор. Проверить
работу используя бинарный семафор для синхронизации с потоком
main, объяснить отличия, если есть!)
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве лексем, (разделители - цифры). Полученные лексемы
поместить в массиве слева, разделитель - пробел, остальные элементы -
заполнить символом ‘0’. Элементы массива - символы;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будет сформирован итоговый массив;
− поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать критическую секцию!)
− ждёт от потока work сигнал о начале суммирования;
− выполнить суммирование элементов (кодов) итогового массива;
− вывести итоговую сумму.
11 . Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут выведены на консоль k элементов
массива);
48
− поток work должен выполнить следующие действия (Для
синхронизации с потоком main - использовать семафор. Проверить
работу используя бинарный семафор для синхронизации с потоком
main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− приведение массива к палиндрому (получившейся палиндром
поместить в массиве слева, а лишние элементы соответственно - справа )
Элементы – символы извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком main, использовать критическую секцию!):
− ждёт от потока main сигнал о начале суммирования;
− выполнить суммирование элементов (кодов) итогового массива до
заданной позиции k;
− вывести итоговую сумму.
12. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток MultElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве; сортировка выбором.
Элементы - целые числа, извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток MultElement о начале работы (момент запуска
произойдёт после того, будет сформирован итоговый массив.
Поток MultElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать бинарный семафор!):
− ждёт от потока work сообщения о начале суммирования;
− выполнить произведение элементов итогового массива;
− вывести произведение.
13. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
49
− вывести размерность и элементы исходного массива на консоль;
− ввести число k;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будут выведены на консоль k элементов
массива).
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу программы используя
критическую секцию для синхронизации с потоком main, объяснить отличия,
если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве элементов, соответствующих цифрам (слева поместить
в массив цифры, а остальные элементы массива - заполнить
пробелами). Элементы - символы.
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком main, использовать бинарный семафор!):
− ждёт от потока main сообщения о начале суммирования;
− выполнить суммирование элементов (кодов) итогового массива до
заданной позиции k;
− вывести итоговую сумму.
14. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток SumElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main – использовать семафор. Проверить работу используя бинарный
семафор для синхронизации с потоком main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве лексем, начинающихся с цифры (разделители - пробел и
тире). Полученные лексемы поместить в массиве слева, а лишние
50
элементы заполнить символом подчеркивания: «_» ). Элементы –
символы;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток SumElement о начале суммирования (момент запуска
произойдёт после того, будет сформирован итоговый массив.
Поток SumElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать критическую секцию!):
− ждёт от потока work сообщения о начале суммирования;
− выполнить суммирование элементов (кодов) итогового массива;
− вывести итоговую сумму.
15. Поток main должен выполнить следующие действия:
− создать массив, размерность и элементы которого вводятся
пользователем с консоли;
− вывести размерность и элементы исходного массива на консоль;
− запустить поток work;
− запустить поток Sum/CountElement;
− освобождение выходной поток stdout после вывода на консоль каждого
нового элемента массива;
− выводить на экран поэлементно элементы массива (итогового)
параллельно с работой потока work;
Поток work должен выполнить следующие действия (Для синхронизации с
потоком main - использовать семафор. Проверить работу используя бинарный
семафор для синхронизации с потоком main, объяснить отличия, если есть!):
− запросить у пользователя временной интервал, требуемый для отдыха
после подготовки одного элемента в массиве;
− поиск в массиве лексем, начинающихся с цифры (разделители – пробел
и тире).
− полученные лексемы поместить в массиве слева, а лишние элементы
заполнить символом подчеркивания: «_» ). Элементы – символы;
− извещать поток main о новом элементе;
− после каждого готового элемента отдыхать в течение заданного
интервала времени;
− известить поток Sum/CountElement о начале суммирования (момент
запуска произойдёт после того, будет сформирован итоговый массив;
− поток Sum/CountElement должен выполнить следующие действия (Для
синхронизации с потоком work, использовать критическую секцию!):
− ждёт от потока work сообщения о начале суммирования;
− выполнить суммирование и подсчёт элементов (до символов
подчеркивания: «_») итогового массива;
− вывести результаты.
51
Лабораторная работа №5
Тема: Синхронизация процессов при помощи событий и мьютексов
Цель работы:
1. Изучить объекты синхронизации потоков мьютексы и события.
2. В соответствии с заданным вариантом разработать приложение,
реализующее синхронизацию потоков с помощью мьютексов и критических
секций.
3. В соответствии с заданным вариантом разработать приложение,
реализующее синхронизацию потоков с помощью
52
Функция WaitForSingleObject в течение интервала времени, равного значению
параметра dwMilliseconds, ждет пока объект синхронизации с дескриптором
hHandle перейдет в сигнальное состояние. Если значение параметра
dwMilliseconds равно нулю, то функция только проверяет состояние объекта.
Если же значение параметра dwMilliseconds равно INFINITE, то функция ждет
перехода объекта синхронизации в сигнальное состояние бесконечно долго.
В случае удачного завершения функция WaitForSingleObject возвращает
одно из следующих значений:
WAIT_OBJECT_0
WAIT_ABANDONED
WAIT_TIMEOUT
Значение WAIT_OBJECT_0 означает, что объект синхронизации находился или
перешел в сигнальное состояние. Значение WAIT_ABANDONED означает, что
объектом синхронизации является мьютекс, который не был освобожден
потоком, завершившим свое исполнение. После завершения потока этот
мьютекс освободился системой и перешел в сигнальное состояние. Такой
мьютекс иногда называется забытым мьютексом (abandoned mutex). Значение
WAIT_TIMEOUT означает, что время ожидания истекло, а объект
синхронизации не перешел в сигнальное состояние. В случае неудачи функция
WaitForSingleObject возвращает значение WAIT_FAILED.
Приведем пример простой программы, которая использует функцию
WaitForSingleObject для ожидания завершения потока. Отметим также, что эта
функция уже использовалась нами в Программе 2.1 для ожидания завершения
работы потока Add.
int main()
{
HANDLE hThread;
DWORD dwThread;
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread,
NULL, 0, &dwThread); if (hThread == NULL)
return GetLastError();
// ждем завершения потока thread
if(WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
{
cout << "Wait for single object failed." << endl; cout <<
53
"Press any key to exit." << endl;
}
// закрываем дескриптор потока thread CloseHandle(hThread);
return 0;
}
void thread_1()
{
int i;
for (i = 5; i < 10 ; i++)
{
cout << i << ' ';
cout << flush << '\a';
Sleep(500);
}
cout << endl;
}
int main()
{
55
HANDLE hThread[2];
DWORD dwThread[2];
// запускаем первый поток
hThread[0] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)thread_0,
NULL, 0, &dwThread[0]);
if (hThread[0] == NULL)
return GetLastError();
// запускаем второй поток
hThread[1] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)thread_1,
NULL, 0, &dwThread[1]);
if (hThread[1] == NULL)
return GetLastError();
// ждем завершения потоков thread_1 и thread_2
if (WaitForMultipleObjects(2, hThread, TRUE, INFINITE) ==
WAIT_F AILED)
{
cout << "Wait for multiple objects failed." << endl; cout <<
"Press any key to exit." << endl;
}
// закрываем дескрипторы потоков thread_0 и thread_1
CloseHandle(hThread[0]);
CloseHandle(hThread[ 1 ]);
return 0;
}
2. Мьютексы в Windows
Для решения проблемы взаимного исключения между параллельными
потоками, выполняющимися в контексте разных процессов, в операционных
системах Windows используется объект ядра мьютекс. Слово мьютекс является
переводом английского слова mutex, которое в свою очередь является
сокращением от выражения mutual exclusion, что на русском языке значит
взаимное исключение. Мьютекс находится в сигнальном состоянии, если он не
принадлежит ни одному потоку. В противном случае мьютекс находится в
несигнальном состоянии. Одновременно мьютекс может принадлежать только
одному потоку.
Создается мьютекс вызовом функции CreateMutex, которая имеет
следующий прототип:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // атрибуты
защиты
BOOL bInitialOwner, // начальный владелец мьютекса
LPCTSTR lpName // имя мьютекса
);
Пока значение параметра LPSECURITY_ATTRIBUTES будем устанавливать в
NULL. Это означает, что атрибуты защиты заданы по умолчанию, то есть
дескриптор мьютекса не наследуется и доступ к мьютексу имеют все
пользователи. Теперь перейдем к другим параметрам.
Если значение параметра bInitialOwner равно TRUE, то мьютекс сразу
переходит во владение потоку, которым он был создан. В противном случае
вновь созданный мьютекс свободен. Поток, создавший мьютекс, имеет все
права доступа к этому мьютексу.
Значение параметра lpName определяет уникальное имя мьютекса для всех
процессов, выполняющихся под управлением операционной системы. Это имя
позволяет обращаться к мьютексу из других процессов, запущенных под
управлением этой же операционной системы. Длина имени не должна
превышать значение MAX_PATH. Значением параметра lpName может быть
пустой указатель NULL. В этом случае система создает безымянный мьютекс.
Отметим также, что имена мьютексов являются чувствительными к нижнему и
верхнему регистрам.
В случае удачного завершения функция CreateMutex возвращает
дескриптор созданного мьютекса. В случае неудачи эта функция возвращает
значение NULL. Если мьютекс с заданным именем уже существует, то функция
CreateMutex возвращает дескриптор этого мьютекса, а функция GetLastError,
57
вызванная после функции CreateMutex вернет значение
ERROR_ALREADY_EXISTS.
Мьютекс захватывается потоком посредством любой функции ожидания, а
освобождается функцией ReleaseMutex, которая имеет следующий прототип:
BOOL ReleaseMutex( HANDLE hMutex // дескриптор мьютекса
);
В случае успешного завершения функция ReleaseMutex возвращает значение
TRUE, в случае неудачи - FALSE. Если поток освобождает мьютекс, которым
он не владеет, то функция ReleaseMutex возвращает значение FALSE.
Для доступа к существующему мьютексу поток может использовать одну из
функций CreateMutex или OpenMutex. Функция CreateMutex используется в тех
случаях, когда поток не знает, создан или нет мьютекс с указанным именем
другим потоком. В этом случае значение параметра bInitialOwner нужно
установить в FALSE, так как невозможно определить какой из потоков создает
мьютекс. Если поток использует для доступа к уже созданному мьютексу
функцию CreateMutex, то он получает полный доступ к этому мьютексу. Для
того чтобы получить доступ к уже созданному мьютексу, поток может также
использовать функцию OpenMutex, которая имеет следующий прототип:
HANDLE OpenMutex(
DWORD dwDesiredAccess, // доступ к мьютексу
BOOL bInheritHandle // свойство наследования
LPCTSTR lpName // имя мьютекса
Параметр dwDesiredAccess этой функции может принимать одно из
двух значений:
MUTEX_ALL_ACCES S SYNCHRONIZE
В первом случае поток получает полный доступ к мьютексу. Во втором случае
поток может использовать мьютекс только в функциях ожидания, чтобы
захватить мьютекс, или в функции ReleaseMutex, для его освобождения.
Параметр bInheritHandle определяет свойство наследования мьютекса. Если
значение этого параметра равно TRUE, то дескриптор открываемого мьютекса
является наследуемым. В противном случае - дескриптор не наследуется.
В случае успешного завершения функция OpenMutex возвращает дескриптор
открытого мьютекса, в случае неудачи эта функция возвращает значение
NULL.
Покажем пример использования мьютекса для синхронизации потоков из
разных процессов. Для этого сначала рассмотрим пример не
синхронизированных потоков.
59
Листинг 5. Синхронизация потоков, выполняющихся в // разных
процессах, с использованинм мьютекса
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HANDLE hMutex;
int ij;
// открываем мьютекс
hMutex = OpenMutex(SYNCHRONIZE, FALSE, "DemoMutex"); if (hMutex
== NULL)
{
cout << "Open mutex failed." << endl; cout << "Press any key to
exit." << endl; cin.get();
return GetLastError();
}
for (j = 10; j < 20; j++)
{
// захватываем мьютекс WaitForSingleObject(hMutex, INFINITE);
for (i = 0; i < 10; i++)
{
cout << j << ' ';
cout.flush();
Sleep(5);
}
cout << endl;
// освобождаем мьютекс ReleaseMutex(hMutex);
}
// закрываем дескриптор объекта CloseHandle(hMutex);
return 0;
}
3. События в Windows
Событием называется оповещение о некотором выполненном действии. В
программировании события используются для оповещения одного потока о
том, что другой поток выполнил некоторое действие. Сама же задача
оповещения одного потока о некотором действии, которое совершил другой
поток называется задачей условной синхронизации или иногда задачей
оповещения.
В операционных системах Windows события описываются объектами ядра
Events. При этом различают два типа событий:
- события с ручным сбросом;
- события с автоматическим сбросом.
Различие между этими типами событий заключается в том, что событие с
ручным сбросом можно перевести в несигнальное состояние только
посредством вызова функции ResetEvent, а событие с автоматическим сбросом
переходит в несигнальное состояние как при помощи функции ResetEvent, так и
при помощи функции ожидания. При этом отметим, что если события с
автоматическим сбросом ждут несколько потоков, используя функцию
WaitForSingleObject, то из состояния ожидания освобождается только один из
этих потоков. Создаются события вызовом функции CreateEvent, которая имеет
следующий прототип:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpSecurity Attributes, // атрибуты
защиты
BOOL bManualReset, // тип события
BOOL bInitialState, // начальное состояние события
61
LPCTSTR lpName // имя события
);
Как и обычно, пока значение параметра lpSecurity Attributes будем
устанавливать в NULL. Основную смысловую нагрузку в этой функции несут
второй и третий параметры. Если значение параметра bManualReset равно
TRUE, то создается событие с ручным сбросом, в противном случае - с
автоматическим сбросом. Если значение параметра bInitialState равно TRUE, то
начальное состояние события является сигнальным, в противном случае -
несигнальным. Параметр lpName задает имя события, которое позволяет
обращаться к нему из потоков, выполняющихся в разных процессах. Этот
параметр может быть равен NULL, тогда создается безымянное событие.
В случае удачного завершения функция CreateEvent возвращает дескриптор
события, а в случае неудачи - значение NULL. Если событие с заданным
именем уже существует, то функция CreateEvent возвращает дескриптор этого
события, а функция GetLastError, вызванная после функции CreateEvent вернет
значение ERROR_ALREADY_EXISTS.
Ниже приведена программа, в которой безымянные события с
автоматическим сбросом используются для синхронизации работы потоков,
выполняющихся в одном процессе.
int main()
{
HANDLE hThread;
DWORD IDThread;
cout << "An initial value of n = " << n << endl;
// создаем события с автоматическим сбросом hOutEvent =
CreateEvent(NULL, FALSE, FALSE, NULL); if (hOutEvent == NULL)
return GetLastError();
hAddEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if
(hAddEvent == NULL)
return GetLastError();
62
// создаем поток счетчик thread
hThread = CreateThread(NULL, 0, thread, NULL, 0, &IDThread); if
(hThread == NULL)
return GetLastError();
// ждем пока поток thread выполнит половину работы
WaitForSingleObject(hOutEvent, INFINITE);
// выводим значение переменной
cout << "An intermediate value of n = " << n << endl;
// разрешаем дальше работать потоку thread SetEvent(hAddEvent);
WaitForSingleObject(hThread, INFINITE);
cout << "A final value of n = " << n << endl;
CloseHandle(hThread);
CloseHandle(hOutEvent);
CloseHandle(hAddEvent);
return 0;
}
Для перевода любого события в сигнальное состояние используется
функция SetEvent, которая имеет следующий прототип:
BOOL SetEvent(
HANDLE hEvent // дескриптор события
);
При успешном завершении эта функция возвращает значение TRUE, в
случае неудачи - FALSE.
Для перевода любого события в несигнальное состояние
используется функция ResetEvent, которая имеет следующий прототип:
BOOL ResetEvent(
HANDLE hEvent // дескриптор события
);
При успешном завершении эта функция возвращает значение TRUE, в
случае неудачи - FALSE.
Для освобождения нескольких потоков, ждущих сигнального
состояния события с ручным сбросом, используется функция PulseEvent,
которая имеет следующий прототип:
BOOL PulseEvent(
HANDLE hEvent // дескриптор события
);
При вызове этой функции все потоки, ждущие события с дескриптором hEvent,
выводятся из состояния ожидания, а само событие сразу переходит в
несигнальное состояние. Если функция PulseEvent вызывается для события с
автоматическим сбросом, то из состояния ожидания выводится только один из
ожидающих потоков. Если нет потоков, ожидающих сигнального состояния
события из функции PulseEvent, то состояние этого события остается
несигнальным. Однако заметим, что на платформе Windows NT/2000 для
выполнения этой функции требуется, чтобы в дескрипторе события был
установлен режим доступа EVENT_MODIFY_STATE.
Ниже приведен пример программы, использующей для синхронизации
события как с ручным, так и автоматическим сбросом.
63
#include <iostream>
using namespace std;
volatile int n,m;
HANDLE hOutEvent[2], hAddEvent;
DWORD WINAPI thread_1 (LPVOID)
{
int i;
for (i = 0; i < 10; i++)
{
++n;
if (i == 4)
{
SetEvent(hOutEvent[0]);
WaitForSingleObject(hAddEvent, INFINITE);
}
}
return 0;
}
int i;
for (i = 0; i < 10; i++)
{
++m;
if (i == 4)
{
S etEvent(hOutEvent [ 1 ]);
WaitForSingleObject(hAddEvent, INFINITE);
}
}
return 0;
}
int main()
{
64
return GetLastError();
// ждем пока потоки счетчики выполнят половину работы
WaitForMultipleObjects(2, hOutEvent, TRUE, INFINITE); cout << "An
intermediate values of n = " << n
<< ", m = " << m << endl;
// разрешаем потокам счетчикам продолжать работу
SetEvent(hAddEvent);
// ждем завершения потоков W aitForSingleObj ect(hThread_ 1,
INFINITE);
W aitForSingleObj ect(hThread_2, INFINITE);
cout << "A final values of n = " << n << ", m = " << m << endl;
CloseHandle(hThread_ 1);
CloseHandle(hThread_2);
CloseHandle(hOutEvent[0]);
CloseHandle(hOutEvent[ 1 ]);
CloseHandle(hAddEvent);
return 0;
}
Доступ к существующему событию можно открыть с помощью одной из
функций CreateEvent или OpenEvent. Если для этой цели используется функция
CreateEvent, то значения параметров bManualReset и bInitialState этой функции
игнорируются, так как они уже установлены другим потоком, а поток,
вызвавший эту функцию, получает полный доступ к событию с именем,
заданным параметром lpName. Теперь рассмотрим
функцию OpenEvent, которая используется в случае, если известно, что событие
с заданным именем уже существует. Эта функция имеет следующий прототип:
HANDLE OpenEvent(
DWORD dwDesiredAccess, // флаги доступа
BOOL bInheritHandle, // режим наследования
LPCTSTR lpName // имя события
);
Параметр dwDesiredAccess определяет доступ к событию, и может
быть равен любой логической комбинации следующих флагов:
E VENT_ALL_ACCES S
EVENT_MODIFY_STATE
SYNCHRONIZE
Флаг EVENT_ALL_ACCESS означает, что поток может выполнять над
событием любые действия. Флаг EVENT_MODIFY_STATE означает, что поток
может использовать функции SetEvent и ResetEvent для изменения состояния
события. Флаг SYNCHRONIZE означает, что поток может использовать
событие в функциях ожидания.
В завершение параграфа приведем пример синхронизации потоков,
выполняющихся в разных процессах, при помощи события с автоматическим
сбросом. В этом примере также используется функция OpenEvent для доступа к
уже существующему событию.
Задание на выполнение
1. Используя представленные в работе примеры программ, реализовать
приложения, демонстрирующие использование объектов синхронизации
мьютексы и события.
2. Разработать приложение для консольных процессов, характеристики
которых указаны в соответствующем варианте.
3. Для синхронизации консольных процессов использовать мьютексы и
события.
4. Результаты работы представить преподавателю в виде отчета и
продемонстрировать функционирующее приложение.
Варианты заданий
1. Написать программы для консольного процесса Boss (Резидент) и
консольных процессов Scout (Шпион). При реализации синхронизации
процессов использовать функции ожидания сигнального состояния объекта
только с равным нулю или бесконечности интервалом ожидания. Каждый
отдельный процесс открывать в отдельном консольном окне.
Для моделирования передачи сообщений ввести специальные события,
которые обозначают «точку» и «тире», конец сеанса.
Процесс Boss:
− запрашивает у пользователя количество процессов Scout, которые он
должен запустить;
− запускает заданное количество процессов Scout;
− принимает от каждого процесса Scout сообщение и выводит его на
консоль в одной строке. Принимать сообщение может только от одного
процесса, передача остальных сообщений от других процессов должна
блокироваться с помощью мьютекса;
− завершает свою работу.
Процесс Scout:
− запрашивает с консоли символы: «-», «.» (событие «тире», событие
«точка» ) и передает соответствующие события процессу Boss;
− завершает свою работу, когда будет введён символ, обозначающий
конец ввода сообщений.
2. Написать программы для консольного процесса Boss (Резидент) и
консольных процессов Scout (Шпион).
Для моделирования передачи сообщений ввести специальные события,
которые обозначают «1» , «2» и конец сеанса для процессов Scout
Процесс Boss:
− запрашивает у пользователя количество процессов Scout, которые он
должен запустить;
− запускает заданное количество процессов Scout;
67
− принимает от каждого процесса Scout сообщение и выводит его на
консоль в одной строке. Принимать сообщение может только от двух
процессов, передача остальных сообщений от других процессов должна
блокироваться с помощью мьютексов;
− завершает свою работу.
Процесс Scout:
− запрашивает с консоли сообщения, состоящее из «1» , «2», и передает
их (по одному) процессу Boss;
− завершает свою работу.
3. Написать программы для консольного процесса Boss и консольных
процессов Parent, Child. Для моделирования передачи сообщений ввести
специальные события, которые обозначают любые 4-е цифры и конец сеанса
для процессов Parent и Child
Процесс Boss:
− запрашивает у пользователя количество процессов Parent и количество
процессов Child, которые он должен запустить;
− запрашивает кол-во сообщений, отправленных Parent и Child
− запускает заданное количество процессов Parent, Child;
− отправляет сообщения для процессов Parent, Child Отправить сообщение
может только трём процессам из всех процессов Child и Parent, передача
остальных сообщений от других процессов должна блокироваться с
помощью мьютексов;
− завершает свою работу.
Процесс Parent:
− получает сообщение, от процесса Boss и выводит его на консоль;
− завершает свою работу.
Процесс Child:
− получает сообщение, от процесса Boss и выводит его на консоль;
завершает свою работу.
4. Написать программы для консольного процесса Boss (Резидент) и
консольных процессов Scout (Шпион).
Для моделирования передачи сообщений ввести специальные события,
которые обозначают любые 4-е цифры.
Процесс Boss:
− запрашивает у пользователя количество процессов Scout, которые он
должен запустить;
− запрашивает у пользователя пароль (3 цифры);
− запускает заданное количество процессов Scout;
− принимает от каждого процесса Scout сообщение и выводит его на
консоль в одной строке. Принимать сообщение может только от трёх
процессов, передача остальных сообщений от других процессов должна
блокироваться;
− если приходит сообщение, с цифрой не из пароля, то выводит на консоль
текст "ошибка";
− завершает свою работу.
Процесс Scout:
68
− запрашивает с консоли сообщение, состоящее из цифр, и передает их (по
одному) процессу Boss;
− завершает свою работу.
5. Написать программы для консольного процесса Boss и консольных
процессов Parent, Child. Для моделирования передачи сообщений ввести
специальные события, которые обозначают «А» , «В» и конец сеанса для
процессов Parent и Child.
Процесс Boss:
− запрашивает у пользователя количество процессов Parent и количество
процессов Child, которые он должен запустить;
− запускает заданное количество процессов Parent, Child;
− запрашивает кол-во сообщений, полученных от Parent или Child
− принимает от каждого процесса Parent, Child сообщение и выводит
сообщение и кто его отправил на консоль в одной строке. Принимать
сообщение может только от одного процесса Child и одного процесса
Parent, передача остальных сообщений от других процессов должна
блокироваться с помощью мьютексов;
− завершает свою работу.
Процесс Parent:
− запрашивает с консоли сообщения, состоящее из «А» и передает их (по
одному) процессу Boss;
− завершает свою работу.
Процесс Child:
− запрашивает с консоли сообщения, состоящее из «В» » и передает их (по
одному) процессу Boss;
− завершает свою работу.
6. Написать программы для консольного процесса Administrator и
консольных процессов Reader и Writer.
Для моделирования передачи сообщений ввести специальные события,
которые обозначают сообщение “A”, сообщение “В”, и конец сеанса для
процессов Reader и Writer.
Одновременно принимать и отправлять сообщения могут только два
процесса Writer и два процесса Reader, передача остальных сообщений от
других процессов должна блокироваться с помощью мьютексов;
Процесс Administrator:
− запрашивает у пользователя количество процессов Writer( Reader);
− запрашивает у пользователя количество отправленных (полученных)
сообщений для процессов Writer (Reader);
− запускает заданное количество процессов Reader и Writer;
− принимает от каждого процесса Writer сообщение и выводит на консоль,
затем отправляет его процессу Reader;
− принимает от каждого процесса Reader и Writer сообщение о
завершении сеанса и выводит его на консоль в одной строке;
− завершает свою работу.
Процесс Writer:
− запрашивает с консоли сообщения, состоящее из “A" , “В", и передает их
69
(по одному) процессу Administrator;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
Процесс Reader:
− принимает сообщение от процесса Administrator;
− выводит на консоль сообщение;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
7. Написать программы для консольного процесса Boss и консольных
процессов Parent, Child. Для моделирования передачи сообщений ввести
специальные события, которые обозначают «А» , «В», «С» , «D» и конец сеанса
для процессов Parent и Child.
Процесс Boss:
− запрашивает у пользователя количество процессов Parent и количество
процессов Child, которые он должен запустить;
− запускает заданное количество процессов Parent, Child;
− запрашивает количество сообщений, принятых от Parent или Child
− принимает от каждого процесса Parent, Child сообщение и выводит
сообщение и кто его отправил на консоль в одной строке. Принимать
сообщение может только от двух процессов Child и одного процесса
Parent, передача остальных сообщений от других процессов должна
блокироваться с помощью мьютексов;
− завершает свою работу.
Процесс Parent:
− запрашивает с консоли сообщения, состоящее из «А» , «В» и передает их
(по одному) процессу Boss;
− завершает свою работу.
Процесс Child:
− запрашивает с консоли сообщения, состоящее из «С», «D» и передает
их (по одному) процессу Boss;
− завершает свою работу.
8. Написать программы для консольного процесса Administrator и
консольных процессов Reader и Writer.
Для моделирования передачи сообщений ввести специальные события, которые
обозначают сообщение “A", сообщение “В", и конец сеанса для процессов
Reader и Writer.
Одновременно принимать и отправлять сообщения могут только один процесс
Writer и один процесс Reader, передача остальных сообщений от других
процессов должна блокироваться с помощью мьютексов.
Процесс Administrator:
− запрашивает у пользователя количество процессов Reader и Writer,
которые он должен запустить;
− запрашивает у пользователя количество отправленных сообщений для
процесса Writer и количество принятых сообщений для процесса
Reader(соответствие сообщений проверить и подкорректировать по
формуле);
70
− запускает заданное количество процессов Reader и Writer;
− принимает от каждого процесса Reader и Writer сообщение о
завершении сеанса и выводит его на консоль в одной строке.
− завершает свою работу.
Процесс Writer:
− запрашивает с консоли сообщения, и передает их (по одному) процессу
Reader;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
Процесс Reader:
− принимает сообщение от процесса Writer;
− выводит на консоль сообщение;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
9. Написать программы для консольного процесса Boss и консольных
процессов Employee. Для моделирования передачи сообщений ввести
специальные события, которые «0» , «1», «2», «3» и конец сеанса для процессов
Employee .
Процесс Boss:
− запрашивает у пользователя количество процессов Employee, которые
он должен запустить;
− запускает заданное количество процессов Employee;
− принимает от каждого процесса Employee сообщение и выводит его на
консоль в одной строке. Принимать сообщение может только от трёх
процессов, передача остальных сообщений от других процессов
должна блокироваться с помощью мьютексов;
− завершает свою работу.
Процесс Employee:
− запрашивает с консоли сообщения, состоящее из «0» , «1», «2», «3»,
конец сеанса работы и передает (по одному) его процессу Boss;
− завершает свою работу.
10. Написать программы для консольного процесса Administrator и
консольных процессов Reader и Writer.
Для моделирования передачи сообщений ввести специальные события,
которые обозначают сообщение “Л”, сообщение “Б”, и конец сеанса для
процессов Reader и Writer.
Одновременно принимать и отправлять сообщения могут только два
процесса Writer и два процесса Reader, передача остальных сообщений от
других процессов должна блокироваться с помощью мьютексов;
Процесс Administrator:
− запрашивает у пользователя количество процессов Reader и Writer,
которые он должен запустить;
− запрашивает у пользователя кол-во отправленных сообщений для
процесса Writer. Кол-во принятых сообщений для процесса Reader
вычислить. (соответствие сообщений проверить и подкорректировать по
формуле);
71
− запускает заданное количество процессов Reader и Writer;
− принимает от каждого процесса Reader и Writer сообщение о завершении
сеанса и выводит его на консоль в одной строке.
− завершает свою работу.
Процесс Writer:
− запрашивает с консоли сообщения, и передает их (по одному) процессу
Reader;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
Процесс Reader:
− принимает сообщение от процесса Writer;
− выводит на консоль сообщение;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
11. Написать программы для консольного процесса Administrator и
консольных процессов Reader и Writer.
Для моделирования передачи сообщений ввести специальные события, которые
обозначают сообщение “Л” , сообщение “Б”, и конец сеанса для процессов
Reader и Writer.
Одновременно принимать и отправлять сообщения могут только один процесс
Writer и два процесса Reader, передача остальных сообщений от других
процессов должна блокироваться с помощью мьютексов;
Процесс Administrator:
− запрашивает у пользователя количество процессов Reader и Writer,
которые он должен запустить;
− запрашивает у пользователя кол-во отправленных сообщений для
процесса Writer. Кол-во принятых сообщений для процесса Reader
вычислить. (соответствие сообщений проверить и подкорректировать
по формуле);
− запускает заданное количество процессов Reader и Writer;
− принимает от каждого процесса Reader и Writer сообщение о
завершении сеанса и выводит его на консоль в одной строке.
− завершает свою работу.
Процесс Writer:
− запрашивает с консоли сообщения, и передает их (по одному) процессу
Reader;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
Процесс Reader:
− принимает сообщение от процесса Writer;
− выводит на консоль сообщение;
− передает сообщение о завершении сеанса процессу Administrator;
− завершает свою работу.
Контрольные вопросы
1. Поясните цели синхронизации процессов и потоков многозадачных ОС.
72
2. Что такое мьютексы (mutex) и чем они отличаются от критических
областей?
3. Какие функции и типы Windows API используются для получения
доступа к мьютексу, его захвата и освобождения?
4.Что такое события (event) в Windows API и для чего они могут
использоваться?
5. Какие существуют типы событий и чем они отличаются друг от друга?
6. Какими функциями Windows API осуществляется работа с событиями?
7. В каком случае событие, сбрасываемое вручную, всё-таки сбрасывается
автоматически?
8. Поясните общие свойства и различия при использовании событий и
мьютексов.
Лабораторная работа №6
Тема: Обмен данными по анонимному каналу с сервером
Цель работы:
1. Изучение механизмов межпроцессного обмена в ОС семейства Windows.
2. Изучить функции для работы с анонимными каналами.
3. В соответствии с заданным вариантом разработать приложение,
реализующее обмен данными между процессами с помощью анонимных
каналов.
73
анонимного канала. При этом передаваемый дескриптор должен быть
наследуемым. Наследование дескрипторов анонимного канала определяется
значением поля bInheritHandle в структуре типа SECURITY_ATTRIBUTES, на
которую указывает параметр lpPipeAttributes функции CreatePipe. В ранних
версиях Windows эта задача решалась путем создания соответственно
ненаследуемого или наследуемого дубликата исходного дескриптора,
используя функцию DuplicateHandle. После этого исходный дескриптор
закрывается. В операционной системе Windows 2000 эта задача может быть
также решена, используя функцию SetHandleInformation, которая изменяет
свойство наследования дескриптора.
Передача наследуемого дескриптора клиенту может выполняться одним из
следующих способов:
− через командную строку;
− через поля hStdInput, hStdOutput и hStdError структуры
STARTUPINFO;
− посредством сообщения WM_COPYDATA;
− через файл.
В данной работе будут использованы только первых два способа передачи
дескрипторов процессу-клиенту. Третьим способом можно пользоваться только
процессам с графическим интерфейсом (GUI).
74
LPDWORD lpNumberOfBytesRead, // число записанных байт
LPOVERLAPPED lpOverlapped // асинхронный ввод
);
Функция ReadFile читает из анонимного канала количество байт, заданных
параметром dwNumberOfBytesToRead, в буфер данных, на который указывает
параметр lpBuffer. Дескриптор ввода этого ананимного канала должен быть
задан первым параметром функции ReadFile. Также как и в случае записи в
анонимный канал параметр lpOverlapped должен быть равен NULL.
Следует помнить, что обмен данными по анонимному каналу
осуществляется только в соответствии с назначением дескриптора этого канала.
Дескриптор для записи в анонимный канал должен быть параметром функции
WriteFile, а дескриптор для чтения из анонимного канала должен быть
параметром функции ReadFile. В этом и состоит смысл передачи данных по
анонимному каналу только в одном направлении. Один и тот же процесс
может, как писать данные в анонимный канал, так и читать данные из него,
должным образом используя дескрипторы этого анонимного канала.
После завершения обмена данными по анонимному каналу, потоки должны
закрыть дескрипторы записи и чтения анонимного канала, используя функцию
CloseHandle.
76
GetCurrentProcess(), // дескриптор текущего процесса
&hInheritWritePipe, // новый дескриптор канала
0, // этот параметр игнорируется
TRUE, // новый декскриптор наследуемый
DUPLICATE_SAME_ACCESS ))// доступ не изменяем
{
_cputs("Duplicate handle failed.\n");
_cputs("Press any key to finish.\n");
_getch();
return GetLastError();
}
// закрываем ненужный дескриптор CloseHandle(hWritePipe);
// устанавливаем атрибуты нового процесса ZeroMemory(&si,
sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO);
// формируем командную строку wsprintf(lpszComLine,
"C:\\Client.exe %d", (int)hInheritWritePipe);
// запускаем новый консольный процесс if (!CreateProcess(
NULL, // имя процесса
lpszComLine, // командная строка
NULL, // атрибуты защиты процесса по умолчанию
NULL, // атрибуты защиты первичного потока по умолчанию
TRUE, // наследуемые дескрипторы текущего процесса
// наследуются новым процессом
CREATE_NEW_CONSOLE, // новая консоль
NULL, // используем среду окружения процесса предка
NULL, // текущий диск и каталог, как и в процессе предке
&si, // вид главного окна - по умолчанию
&pi // здесь будут дескрипторы и идентификаторы
// нового процесса и его первичного потока )) {
_cputs("Create process failed.\n"); _cputs("Press any key to
finish.\n"); _getch();
return GetLastError(); }
// закрываем дескрипторы нового процесса
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
// закрываем ненужный дескриптор канала
CloseHandle(hInheritWritePipe);
// читаем из анонимного канала for (int i = 0; i < 10; i++) {
int nData;
DWORD dwBytesRead;
if (!ReadFile(
hReadPipe, &nData, sizeof(nData), &dwBytesRead, NULL)) {
_cputs("Read from the pipe failed.\n"); _cputs("Press any key
to finish.\n"); _getch();
return GetLastError(); } _cprintf("The number %d is read from
the pipe.\n", nData); }
// закрываем дескриптор канала CloseHandle(hReadPipe);
_cputs("The process finished reading from the pipe.\n");
_cputs("Press any key to exit.\n");
_getch();
return 0; }
В следующих программах показывается, как организовать двусторонний
обмен данными по анонимному каналу между клиентом и сервером. Для этого
дескрипторы чтения и записи в анонимный канал используются как сервером,
так и клиентом этого анонимного канала. В этом примере также сначала
77
приведена программа процесса-клиента анонимного канала.
79
to finish.\n"); _getch();
return GetLastError(); } _cprintf("The number %d is read from
the pipe.\n", nData); } _cputs("The process finished reading from
the pipe.\n");
// даем сигнал на разрешение чтения клиентом
SetEvent(hEnableRead);
// пишем ответ в анонимный канал for (int j = 10; j < 20; j++)
{
DWORD dwBytesWritten; if (!WriteFile(
hWritePipe, &j,
sizeof(j),
&dwBytesWritten, NULL)) {
_cputs("Write to file failed.\n"); _cputs("Press any key to
finish.\n"); _getch();
return GetLastError(); } _cprintf("The number %d is written to
the pipe.\n", j); }
// закрываем дескрипторы канала CloseHandle(hReadPipe);
CloseHandle(hWritePipe); CloseHandle(hEnableRead);
_cputs("The process finished writing to the pipe.\n");
_cputs("Press any key to exit.\n");
_getch();
return 0; }
80
вывода, которые описаны в заголовке <iostream.h>, обеспечивают ввод-вывод в
стандартные потоки ввода-вывода cin, cout, cerr. Эти функции составляют
стандартную библиотеку ввода-вывода языка С++. При создании консольного
процесса или при распределении консоли приложением с графическим
интерфейсом, стандартные потоки ввода-вывода связываются с дескрипторами,
которые заданы в полях hStdInput, hStdOutput и hStdError структуры типа
STARTUPINFO. Поэтому, если в эти поля будут записаны соответствующие
дескрипторы анонимного канала, то для передачи данных по анонимному
каналу можно использовать функции стандартного ввода-вывода. Такая
процедура называется перенаправлением стандартного ввода-вывода.
Функции ввода-вывода, которые поддерживаются заголовком <conio.h>,
отличаются от функций стандартной библиотеки ввода-вывода языка С только
тем, что они всегда связываются с консолью. Поэтому эти функции можно
использовать для ввода-вывода на консоль даже в случае перенаправления
стандартного ввода-вывода.
Ниже приведены программы, в которых стандартный ввод-вывод
перенаправляется в анонимный канал, а для обмена данными по анонимному
каналу используются перегруженные операторы ввода-вывода. Пример
включает программы следующих процессов: два процесса клиента, которые
обмениваются данными по анонимному каналу, и процесс сервер, который
создает клиентов и передает им дескрипторы анонимного канала через поля
структуры STARTUPINFO. Сначала приведем программы, которые описывают
процессы клиенты.
// Пример обмена данными по анонимному каналу,
// используя перенаправленные стандартные потоки ввода-вывода.
// Дескрипторы анонимного канала передаются через поля
структуры STARTUPINFO.
#include <windows.h> #include <conio.h> #include <iostream.h>
int main() {
// события для синхронизации обмена данными
HANDLE hReadFloat, hReadText;
char lpszReadFloat[] = "ReadFloat";
char lpszReadText[] = "ReadText";
// открываем события hReadFloat = CreateEvent(NULL, FALSE,
FALSE, lpszReadFloat); hReadText = CreateEvent(NULL, FALSE, FALSE,
lpszReadText);
// ждем команды о начале записи в анонимный канал _cputs("Press
any key to start communication.\n"); _getch();
// пишем целые числа в анонимный канал for (int i = 0; i < 5;
++i) {
Sleep(500); cout << i << endl;
}
// ждем разрешение на чтение дробных чисел из канала
WaitForSingleObject(hReadFloat, INFINITE);
// читаем дробные числа из анонимного канала for (int j = 0; j
< 5; ++j) {
float nData;
cin >> nData;
_cprintf("The number %2.1f is read from the pipe.\n", nData);
}
81
}
// отмечаем, что можно читать текст из анонимного канала
SetEvent(hReadText);
// теперь передаем текст cout << "This is a demo sentence." <<
endl;
// отмечаем конец передачи cout << '\0' << endl;
_cputs("The process finished transmission of data.\n");
_cputs("Press any key to exit.\n");
_getch();
CloseHandle(hReadFloat); CloseHandle(hReadText);
return 0;
Задание на выполнение
Варинты заданий
1. Написать программы трёх консольных процессов Server и Client,
которые обмениваются сообщениями по анонимному каналу.
Сообщения бывают двух типов:
1. сообщения первого типа содержат размер буфера экрана и размер
курсора для консольного приложения;
2. сообщения второго типа содержат массив символов.
84
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Запрашивает размер буфера экрана и размер курсора.
− Размер массива вводится с консоли.
− Запускает процесс Client.
− Получает и передает от процесса-Client по анонимным каналам массив
символов. Выводит полученные результаты и переданные массив на
консоль.
− Передача сообщения первого типа инициируется нажатием правой
кнопки мыши. Передача сообщения второго типа инициируется
посредством нажатия клавиши “G” на клавиатуре.
− Закончить работу , после нажатия левой клавиши мыши.
Процесс- Client , который выполняет следующие действия.
− Генерирует символы и передает их по анонимному каналу процессу-
серверу.
− Получает по анонимному каналу размер буфера экрана и размер курсора
от Server и устанавливает их;
− Передача сообщения второго типа инициируется нажатием правой
кнопки.
− Выводит сгенерированный массив на консоль.
− Заканчивает работу.
2. Написать программы трёх консольных процессов Server и Client, Part
которые обмениваются сообщениями по анонимному каналу.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Создает массив, для хранения вещественных чисел.
− Размер массива вводится с консоли.
− Запускает процесс Client.
− Запускает процесс Part.
− Передёт размер массива процессам Client, Part. Получает и передает по
анонимным каналам массив чисел. Выводит полученные результаты и
переданные массив на консоль. Элементы массива передаются
посимвольно.
− Повторяет запрос массива от процесса-Client посредством нажатия
любой клавиши на клавиатуре.
− Закончить работу после нажатия клавиши - “Enter”.
Процесс- Client , который выполняет следующие действия.
− Запрашивает у пользователя размер буфера экрана и размер курсора
консоли и устанавливает их.
− Генерирует вещественные числа и передает их по анонимному каналу
процессу-серверу.
− Выводит сгенерированный массив на консоль.
Процесс-Part, который выполняет следующие действия.
− Получает массив чисел по анонимному каналу от процесса-сервера
− Запрашивает число вещественные числа N и M (N < M ).
− Определяет какие из чисел попали в отрезок [N,M], передаёт их по
85
анонимному каналу процессу-серверу.
− Передача массива инициируется нажатием левой кнопки мыши.
− Выводит полученные числа на консоль.
3. Написать программы трёх консольных процессов Server и Client, Sum
которые обмениваются сообщениями по анонимному каналу.
Одновременно сообщение может передаваться только одним из процессов.
Процесс-Server, который выполняет следующие действия.
− Размер массива вводится с консоли.
− Запускает процесс Client.
− Запускает процесс Sum.
− Передача размера массива инициируется нажатием правой кнопки
мыши.
− Получает и передаёт по анонимным каналам массив чисел. Выводит
полученные результаты и переданные массив на консоль.
− Передача массив процессу Sum инициируется нажатием левой кнопки
мыши. Элементы массива передаются посимвольно.
− Повторяет запрос массива от процесса-Client посредством нажатия
любой клавиши на клавиатуре.
− Закончить работу после нажатия клавиши - “Q”.
Процесс-Client , который выполняет следующие действия.
− Запрашивает у пользователя размер буфера экрана и размер курсора и
устанавливает их.
− Получает от сервера размер массива.
− Генерирует целые числа и передает их по анонимному каналу процессу-
серверу.
− Выводит сгенерированный массив на консоль.
Процесс-Sum, который выполняет следующие действия.
− Получает массив символов по анонимному каналу от процесса-сервера
− Запрашивает с консоли число N.
− Вычисляет сумму квадратов чисел масcива, больших N
− Передаёт cумму по анонимному каналу процессу-серверу.
− Передача сообщения инициируется двойным нажатием левой кнопки.
− Выводит полученное число на консоль.
4. Написать программы двух консольных процессов Server, Mult, Sum
которые обмениваются сообщениями по анонимному каналу. Сообщения
бывают двух типов:
1. сообщения первого типа содержат размер курсора и цвет фона консоли;
2. сообщения второго типа содержат массив символов.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Запрашивает размер массива.
− Запрашивает у пользователя размер курсора и цвет фона консоли;
− Генерирует целые числа и передает их по анонимному каналу
процессу-серверу.
− Запускает процессы Mult, Sum.
− Получает и передает по анонимным каналам массив символов.
86
Выводит полученные результаты и переданные массив на консоль.
− Передача первого сообщения инициируется нажатием левой кнопки
мыши. В этом случае данный процесс передаёт размер курсора и цвет
фона консоли процессу Sum.
− Передача второго сообщения инициируется нажатием клавиши «G» на
клавиатуре. В этом случае данный процесс передаёт массив чисел
другим процессам.
− Запрашивает результат от процессов- Mult, Sum посредством нажатия
клавиши «S» на клавиатуре.
− Закончить работу после нажатия клавиши - “Enter”
Процесс-Sum, который выполняет следующие действия.
− Получает массив чисел от сервера.
− Получает размер курсора и цвет фона консоли, устанавливает их;
− Вычисляет сумму чисел массива.
− Передаёт число серверу.
− Выводит сумму на консоль.
Процесс-Mult, который выполняет следующие действия.
− Получает массив чисел от сервера.
− Вычисляет произведение чисел массива
− Передаёт число серверу.
− Выводит сумму на консоль
5. Написать программы трёх консольных процессов Server и Client, Sort
которые обмениваются сообщениями по анонимному каналу.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Размер массива вводится с консоли.
− Запускает процесс Client.
− Запускает процесс Sort.
− Передача размер массива другому процессу сообщения инициируется
нажатием правой кнопки мыши.
− Получает по анонимным каналам массив чисел. Выводит полученные
результаты и переданные массивы на консоль.
− Запрашивает массив от процесса Client посредством нажатия любой
клавиши на клавиатуре.
− Получает массив от процесса Sort;
− Закончить работу после нажатия клавиши - “Q”.
Процесс-Client , который выполняет следующие действия.
− Получает размер массива .Генерирует целые числа и передает их по
анонимному каналу процессу-серверу.
− Выводит сгенерированный массив на консоль.
− Передаёт массив процессу Server. Элементы массива передаются
посимвольно.
Процесс-Sort, который выполняет следующие действия.
− Получает массив символов по анонимному каналу от процесса Server;
− Сортирует массив;
− Запрашивает у пользователя размер окна консоли и размер курсора
87
консоли и устанавливает их.
− Передаёт отсортированный массив по анонимному каналу процессу.
− Передача сообщения Server инициируется двойным нажатием левой
кнопки мыши.
− Элементы массива передаются посимвольно.
− Выводит полученный массив на консоль.
6. Написать программы трёх консольных процессов Server и Client, Hight
которые обмениваются сообщениями по анонимному каналу.
Одновременно сообщение может передаваться только одним из процессов.
Процесс-Server, который выполняет следующие действия.
− Создает массив, для хранения вещественных чисел.
− Размер массива вводится с консоли.
− Запрашивает у пользователя размер курсора экрана и размер буфера
окна;
− Запускает процесс Client и процесс Hignt.
− Получает и передает по анонимным каналам массив чисел. Выводит
полученные результаты и переданные массив на консоль. Элементы
массива передаются посимвольно.
− Передача массива процессу Client, размер курсора экрана и размер
буфера окна инициируется нажатием левой кнопки мыши.
− Закончить работу после нажатия клавиши – “E”.
Процесс- Client , который выполняет следующие действия.
− Генерирует вещественные числа.
− Получает размер курсора экрана и размер буфера окна и устанавливает
их.
− Передаёт массив процессу Server по анонимному каналу, посредством
нажатия любой клавиши на клавиатуре.
− Выводит сгенерированный массив на консоль.
Процесс-Hignt, который выполняет следующие действия.
− Получает массив чисел по анонимному каналу от процесса- Server,
посредством нажатия правой клавиши мыши.
− Запрашивает вещественное число N.
− Определяет какие из чисел >N передаёт их по анонимному каналу
процессу-серверу.
− Выводит полученные числа на консоль.
7. Написать программы трёх консольных процессов Server и Client, Simple
которые обмениваются сообщениями по анонимному каналу. Сообщения
бывают двух типов:
1. сообщения первого типа содержат цвет фона и размер курсора для
консольного приложения;
2. сообщения второго типа содержат массив чисел.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Размер массива вводится с консоли.
− Запрашивает в произвольной форме (лучше, используя палитру цветов)
у пользователя цвета фона и размер курсора консоли;
88
− Запускает процесс Client.
− Запускает процесс Simple.
− Получает и передает по анонимным каналам массив чисел. Выводит
полученные и переданные числа на консоль.
− Передача сообщения первого типа инициируется нажатием правой
кнопки мыши. В этом случае данный процесс устанавливает параметры
их на своей консоли и передать их другому процессу.
− Передача сообщения второго типа инициируется нажатием левой
кнопки мыши. Элементы массива передаются посимвольно.
− Закончить работу после нажатия клавиши - “Ctrl”.
Процесс- Client , который выполняет следующие действия.
− Генерирует целые числа и передает их по анонимному каналу
процессу-серверу.
− Получает цвет фона и размер курсора от Server и устанавливает их;
− Размер массива запрашивается с консоли.
− Передача сообщения второго типа инициируется нажатием кнопки на
клавиатуре.
− Выводит сгенерированные числа на консоль. -
Процесс-Simple, который выполняет следующие действия.
− Получает массив числа по анонимному каналу от процесса-сервера
− Находит и передает простые числа по анонимному каналу процессу-
серверу.
− Передача сообщения второго типа инициируется двойным нажатием
левой кнопки мыши. Элементы массива передаются посимвольно.
− Выводит полученные числа на консоль.
8. Написать программы трёх консольных процессов Server и Client, Small
которые обмениваются сообщениями по анонимному каналу. Сообщения
бывают двух типов:
1. сообщения первого типа содержат цвет фона и текста консольного
приложения;
2. сообщения второго типа содержат массив чисел.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Размер массива вводится с консоли.
− Запрашивает (использовать палитру цветов) у пользователя цвета фона
и цвет текста консоли;
− Запускает процесс Client.
− Запускает процесс Small.
− Передает по анонимным каналам размер массива;
− Получает и передает по анонимным каналам массив чисел. Выводит
полученные и переданные числа на консоль.
− Передача сообщения первого типа инициируется нажатием правой
кнопки.
− Передача сообщения второго типа инициируется нажатием кнопки. Alt
− Элементы массива передаются посимвольно.
− Закончить работу
89
Процесс- Client , который выполняет следующие действия.
− Генерирует целые числа и передает их по анонимному каналу
процессу-серверу.
− Получает цвет фона и размер курсора от Server и устанавливает их;
− Передача сообщения второго типа инициируется нажатием левой
кнопки.
− Выводит сгенерированные числа на консоль.
Процесс- Small, который выполняет следующие действия.
- Получает цвет фона и размер курсора от Server и устанавливает их;
− Получает размер массива и массив чисел по анонимному каналу от
процесса-сервера
− Запрашивает вещественное число N.
− Определяет какие из чисел >0 и <N передаёт их по анонимному каналу
процессу-серверу.
− Передача сообщения второго типа инициируется двойным нажатием
левой кнопки.
− Элементы массива передаются посимвольно.
- Выводит полученные числа на консоль.
9. Написать программы трёх консольных процессов Server и Client, Alfavit
которые обмениваются сообщениями по анонимному каналу. Сообщения
бывают двух типов:
1. сообщения первого типа содержат цвет фона и координаты позиции
курсора экрана для вывода с этой позиции символов для консольного
приложения;
2. сообщения второго типа содержат массив символов.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Размер массива вводится с консоли.
− Запрашивает в произвольной форме (лучше, используя палитру
цветов) у пользователя цвета фона, координаты позиции курсора
экрана;
− Запускает процесс Client.
− Запускает процесс Alfavit .
− Получает и передает по анонимным каналам массив символов.
Выводит полученные и переданные символы на консоль.
− Передача сообщения первого типа процессу Alfavit инициируется
нажатием кнопки на клавиатуре.
− Передача сообщения второго типа инициируется двойным нажатием
правой кнопки мыши. Элементы массива передаются посимвольно.
− Закончить работу после нажатия клавиши - “Esc”.
Процесс- Client , который выполняет следующие действия.
− Получает размер массива от процесса Server
− Генерирует символы и передает их по анонимному каналу процессу-
серверу.
− Передача сообщения второго типа инициируется нажатием левой
кнопки мыши.
90
− Символы передаются посимвольно.
− Выводит произведенные символы консоль.
Процесс-Alfavit, который выполняет следующие действия.
− Получает цвет фона и координаты позиции курсора от Server и
устанавливает их;
− Получает массив символов по анонимному каналу от процесса-сервера.
− Определяет символы, принадлежащие латинскому алфавиту и передает
их по анонимному каналу процессу-серверу.
− Выводит полученные символы на консоль, начина с позиции курсора.
− Передача сообщения второго типа инициируется нажатием правой
кнопки мыши. Символы передаются посимвольно.
10. Написать программы трёх консольных процессов Server и Client, Figure
которые обмениваются сообщениями по анонимному каналу. Сообщения
бывают двух типов:
1. сообщения первого типа содержат заголовок и координаты позиции
курсора экрана для вывода с этой позиции символов для консольного
приложения;
2. сообщения второго типа содержат массив символов, которые должны
выводиться
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
- Размер массива вводится с консоли.
− Запрашивает заголовок и координаты позиции курсора экрана у
пользователя;
− Запускает процесс Client.
− Запускает процесс Figure.
− Получает и передает по анонимным каналам массив символов.
Выводит полученные и переданные числа на консоль.
− Передача сообщения первого типа инициируется нажатием кнопки на
клавиатуре. В этом случае данный процесс установливает их на своей
консоли и передать их другим процессам
− Передача сообщения второго типа инициируется нажатием правой
кнопки мыши.
− Элементы массива предаются по одному.
− Закончить работу , после нажатия клавиши - “Esc”.
Процесс- Client , который выполняет следующие действия.
− Генерирует символы и передает их по анонимному каналу процессу-
серверу.
− Получает координаты позиции курсора от Server и устанавливает их;
− Передача сообщения второго типа инициируется нажатием левой
кнопки мыши.
− Символы передаются посимвольно.
Выводит произведенные символы консоль, начина с позиции курсора.
Процесс-Figure, который выполняет следующие действия.
− Получает заголовок от Server и устанавливает его;
− Получает массив символов по анонимному каналу от процесса-сервера.
91
− Определяет цифры и передает их по анонимному каналу процессу-
серверу.
− Передача сообщения второго типа инициируется двойным нажатием
левой кнопки мыши;
− Элементы массива предаются по одному;
− Начиная с позиции курсора выводит полученные символы на консоль.
11. Написать программы двух консольных процессов Server, Mult, Sum
которые обмениваются сообщениями
по анонимному каналу. Сообщения бывают двух типов:
1. сообщения первого типа содержат размер буфера экрана и размер
курсора;
2. сообщения второго типа содержат массив символов.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Запрашивает размер массив.
− Запрашивает у пользователя размер буфера экрана и размер курсора;
− Генерирует целые числа для массива и передает их по анонимному
каналу другим процессам
− Запускает процессы Mult, Sum.
− Получает и передает по анонимным каналам массив символов. Выводит
полученные результаты на консоль.
− Передача первого сообщения инициируется нажатием левой кнопки
мыши. В этом случае данный процесс передаёт буфера экрана и
позицию курсора процессу Sum.
− Передача второго сообщения инициируется нажатием клавиши «G» на
клавиатуре. В этом случае данный процесс передаёт массив чисел
другим процессам.
− Запрашивает результат от процессов- Mult, Sum посредством двойного
нажатия кнопки мыши.
− Закончить работу , после нажатия клавиши - “Enter”
Процесс- Sum, который выполняет следующие действия.
− Получает массив чисел от сервера.
− Получает размер буфера экрана и размер курсора, устанавливает их;
− Вычисляет сумму чисел массива.
− Передаёт число серверу.
− Выводит сумму на консоль.
Процесс- Mult, который выполняет следующие действия.
− Получает массив чисел от сервера.
− Вычисляет произведение чисел массива
− Передаёт число серверу.
− Выводит сумму на консоль
12. Написать программы трёх консольных процессов Server и Client,
Palindrom которые обмениваются сообщениями по анонимному каналу.
Сообщения бывают двух типов:
1. сообщения первого типа содержат цвет символов экрана и размер
курсора для консольного приложения;
92
2. сообщения второго типа содержат массив символов.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Создает массив, для хранения целых чисел.
− Размер массива вводится с консоли.
− Запрашивает у пользователя цвет символов экрана и размер курсора
консоли;
− Запускает процесс Client.
− Запускает процесс Palindrom.
− Получает и передает по анонимным каналам массив символов.
Выводит полученные результаты и переданные массив на консоль.
− Передача сообщения первого типа инициируется нажатием правой
кнопки. В этом случае данный процесс передаёт их (параметры)
другому процессу, который должен установить их (параметры) на
своей консоли.
− Передача сообщения второго типа инициируется нажатием левой
кнопки. Элементы массива передаются посимвольно клавиши мыши
− Закончить работу после нажатия клавиши.
Процесс- Client , который выполняет следующие действия.
− Генерирует целые числа и передает их по анонимному каналу
процессу-серверу.
− Получает цвет текста от Server и устанавливает их;
− Размер массива запрашивается с консоли.
− Передача сообщения второго типа инициируется нажатием правой
кнопки.
− Выводит сгенерированный массив на консоль.
− Закончить работу после нажатия клавиши мыши
Процесс-Palindrom, который выполняет следующие действия.
− Получает размер курсора от Server и устанавливает их;
− Получает массив символов по анонимному каналу от процесса-сервера
− Находит палиндром в строке и передает полином по анонимному
каналу процессу-серверу.
− Передача сообщения второго типа инициируется двойным нажатием
левой кнопки мыши.
− Выводит полученные палиндромы на консоль.
13. Написать программы консольных процессов Server, Produce и
Consume для управления параллельным доступом процессов к массиву.
Одновременно сообщение может передаваться только одним из процессов.
Процесс- Server, который выполняет следующие действия.
− Запускает процесс Producer, которые производят элементы для
массива.
− Запускает процесс Consumer, которые потребляют элементы из
массива.
− Дает процессам Produce и Consumer команду на начало работы.
− Получает и передает по анонимным каналам целые числа от
производителей и потребителей соответственно. Выводит полученные
93
и переданные числа на консоль.
− Передача элементов массива инициируется двойным нажатием левой
кнопки мыши.
− Закончить работу, после нажатия клавиши мыши
Процесс- Produce , который выполняет следующие действия:
− Запрашивает с консоли количество чисел для производства.
− Генерирует массив чисел и передает их по анонимному каналу
процессу-серверу.
− Передача целых чисел начинается по команде сервера.
− Выводит произведенные целые числа на консоль.
− Процесс- Consume, который выполняет следующие действия.
− Получает числа по анонимному каналу от процесса-сервера.
− Выводит полученные целые числа на консоль.
− Количество чисел, которые должны быть потреблены, запрашивается с
консоли.
− Отправка целых чисел начинается после нажатия клавиши мыши.
Контрольные вопросы
1. Перечислите основные способы взаимодействия процессов.
2. В чем отличие каналов от отображения файлов?
3. Перечислите основные параметры создания анонимных каналов.
4. В чем состоят недостатки анонимных каналов?
Лабораторная работа №7
Тема: Обмен данными по именованному каналу с сервером
Цель работы:
1. Изучение механизмов межпроцессного обмена в ОС семейства Windows.
2. Изучение функций для работы с именованными каналами.
3. В соответствии с заданным вариантом разработать приложение,
реализующее обмен данными между процессами с помощью именованных
каналов.
95
PIPE_UNLIMITED_INSTANCES.
Параметры nOutBufferSize и nInBufferSize определяют соответственно
размеры выходного и входного буферов для обмена данными по именованному
каналу. Однако, эти значения рассматриваются операционными системами
Windows только как пожелания пользователя, а сам выбор размеров буферов
остается за операционной системой.
Параметр nDefaultTimeOut устанавливает время ожидания клиентом связи с
сервером, если клиент вызывает функцию WaitNamedPipe, в которой интервал
ожидания интервал ожидания задается по умолчанию.
При удачном завершение функция CreateNamedPipe возвращает значение
дескриптор именованного канала, в случае неудачи - одно из двух значений:
INVALID_HANDLE_VALUE неудачное завершение,
ERROR_INVALID_PARAMETR значение параметра nMaxInstances
больше, чем величина
PIPE_UNLIMITED_INSTANCES.
Для связи сервера с несколькими клиентами по одному именованному
каналу сервер должен создать несколько экземпляров этого канала. Каждый
экземпляр именованного канала создается вызовом функции CreateNamedPipe,
в которой некоторые флаги должны быть установлены одинаково для всех
экземпляров одного и того же именованного канала. Каждый новый вызов этой
функции возвращает новый дескриптор на создаваемый экземпляр
именованного канала.
96
определить доступен ли какой-либо экземпляр этого канала для соединения. С
этой целью клиент должен вызвать функцию:
BOOL WaitNamedPipe (
LPCTSTR lpNamedPipeName, // указатель на имя канала
DWORD nTimeOut // интервал ожидания
);
которая в случае успешного завершения возвращает значение TRUE, а в случае
неудачи - FALSE. Параметры этой функции имеют следующие значения.
Параметр lpNamedPipeName указывает на строку, которая должна иметь вид
\\<server_name>\pipe\<pipe_name>
Здесь <server_name> обозначает имя компьютера, на котором выполняется
сервер именованного канала.
Параметр nTimeOut задает временной интервал в течение которого клиент
ждет связь с сервером. Этот временной интервал определяется в миллисекундах
или может быть равен одному из следующих значений:
NMPWAIT_USE_DEFAULT_WAIT интервал времени ожидания
определяется значением параметра nDefaultTimeOut, который задается в
функции CreateNamedPipe,
NMPWAIT_WAIT_FOREVER бесконечное время ожидания связи с
именованным каналом.
Сделаем два важных замечания относительно работы функции
WaitNamedPipe. Во-первых, если не существует экземпляров именованного
канала с именем lpNamedPipe, то эта функция немедленно завершается
неудачей, независимо от времени ожидания, заданного параметром nTimeOut.
Во-вторых, если клиент соединяется с каналом до вызова сервером функции
ConnectNamedPipe, то функция WaitNamedPipe возвращает значение FALSE и
функция GetLastError вернет код ERROR_PIPE_CONNECTED. Поэтому
функцию WaitNamedPipe нужно вызывать только после соединения сервера с
каналом посредством функции ConnectNamedPipe.
После того как обнаружен свободный экземпляр канала, для того чтобы
установить связь с этим каналом клиент должен вызвать функцию
HANDLE CreateFile (
LPCTSTR lpFileName, // указатель на имя канала
DWORD dwDesiredAccess, // чтение или запись в канал
DWORD dwShareMode, // режим совместного использования
LPSECURITY_ATTRIBUTES lpSecurity Attributes, // атрибуты защиты
DWORD dwCreationDisposition, // флаг открытия канала
DWORD dwFlagsAndAttributes, // флаги и атрибуты
HANDLE hTemplateFile // дополнительные атрибуты
);
которая в случае успешного завершения возвращает дескриптор именованного
канала, а в случае неудачи - значение INVALID_HANDLE_VALUE.
Параметры функции CreateFile могут принимать следующие значения, если
эта функция используется для открытия именованного канала.
Параметр lpFileName должен указывать на имя канала, которое должно быть
задано в том же формате, что и в функции WaitNamedPipe.
Параметр dwDesiredAccess может принимать одно из следующих значений:
0 разрешает получить атрибуты канала,
97
GENERIC_READ разрешает чтение из канала,
GENERIC_WRITE разрешает запись в канал.
Следует отметить, что функция CreateFile завершается неудачей, если доступ к
именованному каналу, заданный этими значениями, не соответствует
значениям параметра dwOpenMode в функции CreateNamedPipe. Кроме того, в
этом параметре программист может определить стандартные права доступа к
именованному каналу. За более подробной информацией по этому вопросу
нужно обратиться к MSDN.
Параметр dwShareMode определяет режим совместного использования
именованного канала и может принимать значение 0, которое запрещает
совместное использование именованного канала или любую комбинацию
следующих значений:
FILE_SHARE_READ разрешает совместное чтение из канала,
FILE_SHARE_WRITE разрешает совместную запись в канал.
Параметр lpSecurityAttributes задает атрибуты защиты именованного канала.
Для именованного канала параметр dwCreationDisposition должен быть равен
значению OPEN_EXISTING, так как клиент всегда открывает существующий
именованный канал.
Для именованного канала параметр dwFlagsAndAttributes можно задается
равным 0, что определяет флаги и атрибуты по умолчанию. Подробную
информацию о значениях этого параметра смотри в MSDN.
Значение параметра hTemplateFile задается равным NULL.
Сделаем следующие замечания относительно работы с функцией CreateFile
в случае её использования для открытия доступа к именованному каналу. Во-
первых, несмотря на то, что функция WaitNamedPipe может успешно
завершиться, последующий вызов функции CreateFile может завершиться
неудачей по следующим причинам:
между вызовами этих функций сервер закрыл канал,
между вызовами функций другой клиент связался с экземпляром этого
канала.
Для предотвращения последней ситуации сервер должен создавать новый
экземпляр именованного канала после каждого успешного завершения функции
ConnectNamedPipe или создать сразу несколько экземпляров именованного
канала. Во-вторых, если заранее известно, что сервер вызвал функцию
ConnectNamedPipe, то функция CreateFile может вызываться без
предварительного вызова функции WaitNamedPipe.
Кроме того следует отметить, что если клиент работает на той же машине,
что и сервер и использует для открытия именованного канала в функции
CreateFile имя сервера в виде:
\\.\pipe\<pipe_name>
то файловая система именованных каналов (NPFS) открывает этот
именованный канал в режиме передачи данных потоком. Чтобы открыть
именованный канал в режиме передачи данных сообщениями, нужно задавать
имя сервера в виде:
\\<server_name>\pipe\<pipe_name>
Отметим один момент, который касается связи сервера с клиентом
98
именованного канала. Может возникнуть такая ситуация, что сервер вызвал
функцию ConnectNamedPipe, а клиента, который хочет связаться с
именованным каналом, не существует. В этом случае серверное приложение
будет заблокировано. Чтобы иметь возможность обработать такую ситуацию,
функцию ConnectNamedPipe следует вызывать в отдельном потоке серверного
приложения. Тогда для разблокировки серверного приложения можно вызвать
функцию для связи клиента с именованным каналом из другого потока.
101
&i, // данные
sizeof(i), // размер данных
&dwBytes Written, // количество записанных байтов
(LPOVERLAPPED)NULL // синхронная запись
))
{
// ошибка записи
cerr << "Writing to the named pipe failed: " << endl
<< "The last error code: " << GetLastError() << endl; cout <<
"Press any char to finish the client: "; cin >> c;
CloseHandle(hNamedPipe); return 0;
}
// выводим число на консоль
cout << "The number " << i << " is written to the named pipe."
<< endl;
Sleep(1000);
}
// закрываем дескриптор канала CloseHandle(hNamedPipe);
// завершаем процесс
cout << "The data are written by the client." << endl << "Press
any char to finish the client: ";
cin >> c; return 0;
}
102
INFINITE, // клиент ждет связь 500 мс
(LPSECURITY_ATTRIBUTES)NULL // защита по умолчанию
);
// проверяем на успешное создание if (hNamedPipe ==
INVALID_HANDLE_VALUE)
{
cerr << "Creation of the named pipe failed." << endl
<< "The last error code: " << GetLastError() << endl; cout <<
"Press any char to finish server: "; cin >> c; return 0;
}
// ждем, пока клиент свяжется с каналом cout << "The server is
waiting for connection with a client." << endl;
if(!ConnectNamedPipe(
hNamedPipe, // дескриптор канала
(LPOVERLAPPED)NULL // связь синхронная
))
{
cerr << "The connection failed." << endl
<< "The last error code: "<<GetLastError() << endl;
CloseHandle(hNamedPipe); cout << "Press any char to finish the
server: "; cin >> c; return 0;
}
// читаем сообщение от клиента if (!ReadFile(
hNamedPipe, // дескриптор канала
lpszInMessage, // адрес буфера для ввода данных
sizeof(lpszInMessage), // число читаемых байтов
&dwBytesRead, // число прочитанных байтов
(LPOVERLAPPED)NULL // передача данных синхронная
))
{
cerr << "Data reading from the named pipe failed." << endl <<
"The last error code: "<< GetLastErrorO << endl;
CloseHandle(hNamedPipe); cout << "Press any char to finish the
server: "; cin >> c; return 0;
}
// выводим полученное от клиента сообщение на консоль cout <<
"The server has received the following message from a client: "
<< endl << "\t" << lpszInMessage << endl;
// отвечаем клиенту if (!WriteFile(
hNamedPipe, // дескриптор канала
lpszOutMessage, // адрес буфера для вывода данных
sizeof(lpszOutMessage), // число записываемых байтов
&dwBytesWrite, // число записанных байтов
(LPOVERLAPPED)NULL // передача данных синхронная
))
{
cerr << "Data writing to the named pipe failed." << endl
<< "The last error code: " << GetLastError() << endl;
CloseHandle(hNamedPipe);
cout << "Press any char to finish the server: ";
cin >> c;
return 0;
}
// выводим посланное клиенту сообщение на консоль cout << "The
103
server send the following message to a client: "
<< endl << "\t" << lpszOutMessage << endl;
// закрываем дескриптор канала CloseHandle(hNamedPipe);
// завершаем процесс
cout << "Press any char to finish the server: "; cin >> c;
return 0;
}
105
}
106
CloseHandle(hNamedPipe); return 0;
}
// выводим посланное сообщение на консоль cout << "The client
has send the following message to a server: "
<< endl << "\t" << lpszOutMessage << endl;
// читаем из именованного канала if (!ReadFile(
hNamedPipe, // дескриптор канала
lpszInMessage, // данные
sizeof(lpszInMessage), // размер данных &dwBytesRead, //
количество записанных байт
(LPOVERLAPPED)NULL // синхронная запись
))
{
// ошибка записи
cerr << "Reading to the named pipe failed: " << endl
<< "The last error code: " << GetLastError() << endl; cout <<
"Press any char to finish the client: "; cin >> c;
CloseHandle(hNamedPipe); return 0;
}
// выводим полученное сообщение на консоль cout << "The client
has received the following message from a server: " << endl <<
"\t" << lpszInMessage << endl;
// закрываем дескриптор канала CloseHandle(hNamedPipe);
// завершаем процесс
cout << "Press any char to finish the client: "; cin >> c;
return 0;
}
Задание на выполнение
1. Изучить теоретический материал, посвященный обмену данных между
процессами посредством именованных каналов.
2. Используя представленные в работе примеры программ, реализовать
приложения, демонстрирующие использование именованных каналов при
межпроцессном обмене.
3. В соответствии с заданным вариантом разработать приложение для
консольных процессов, которые обмениваются сообщениями по именованному
каналу.
4. Результаты работы представить преподавателю в виде отчета и
продемонстрировать функционирующее приложение.
Варианты заданий
Примечание: Процессы должны работать на разных компьютерах!
1. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя имя консоли процесса Client, цвет всего
фона (15 цветов) консоли Client.
− Передаёт данные процессу-клиенту.
− При нажатии клавиши мыши передаёт Client , следующий цвет в
палитре.
107
− Закончить работу, после нажатия клавиши.
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера данные о цвете.
− Устанавливает фон.
2. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя координаты и размер курсора консоли.
− Запрашивает строку символов.
При нажатии клавиши мыши печатает текст с позиции курсора мыши и
передаёт новые координаты процессу-клиенту.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера данные об координатах и размере
курсора консоли, строку.
− -Печатает строку с переданной позиции.
3. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя размер окна консоли и цвет фона.
− Запрашивает строку символов.
− Заполняет буфер экрана введённым символом и закрашивает фон
цветом.
− При нажатии клавиши мыши передаёт данные процессу-клиенту.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера данные о размере и цвете окна, строку
символов.
− Заполняет буфер экрана строкой символов.
4. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия:
− Запрашивает у пользователя цвет выводимых символов, координаты
вывода символов;
− Запрашивает строку;
− Передает эти параметры процессу-клиенту, который запущен на
другом компьютере в локальной сети;
− При нажатии клавиши мыши передаёт сообщение Client, что нужно
всё стереть с экрана.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера начальные данные о цвете символов,
положении курсора в окне консоли;
− Устанавливает курсор и выводит строку с заданной позиции;
− Стирает всё с экрана, если получено сообщение от сервера.
5. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя цвет фона консоли, цвет выводимых
108
символов, координаты вывода символов.
− Запрашивает строку.
− Передает эти параметры процессу-клиенту, который запущен на
другом компьютере в локальной сети.
− При нажатии левой клавиши мыши передаёт сообщение Client, что
нужно изменить цвет символов экрана
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера начальные данные о цвете символов,
положении курсора в окне консоли.
− Выводит строку с заданной позиции.
− Меняет цвет выведенных символов на экране, если получено
сообщение от сервера.
6. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия:
− Запрашивает у пользователя цвет фона консоли, цвет выводимых
символов, координаты вывода символов.
− Запрашивает строку.
− Передает эти параметры процессу-клиенту, который запущен на
другом компьютере в локальной сети.
− При двойном нажатии левой клавиши мыши передаёт сообщение
Client, что нужно изменить цвет экрана
− При нажатии правой клавиши мыши передаёт сообщение Client, что
нужно вывести символы с новой позиции (позиция курсора мыши)
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера начальные данные о цвете символов,
положении курсора в окне консоли.
− Выводит строку с заданной позиции.
− Меняет цвет экрана либо выводит символы с новой позиции, если
получено соответствующее сообщение от сервера.
7. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя размер, цвет и начальное положение
прямоугольника в окне консоли, число N.
− Запрашивает символы для заполнения прямоугольника.
− Отображает прямоугольник.
− При двойном нажатии правой клавиши мыши рисует прямоугольник
другим цветом (выбрать случайным образом), передает данные о
прямоугольнике процессу-серверу.
− При нажатии левой клавиши мыши - завершение работы процессов.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера данные о размере, цвете, символе
заполнителе и положении прямоугольника в окне консоли.
− Отображает прямоугольник.
109
8. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя размер, цвет и начальное положение
прямоугольника в окне консоли, число N.
− Запрашивает символы для заполнения прямоугольника.
− Отображает прямоугольник.
− При нажатии правой клавиши мыши увеличивает размер
прямоугольника, передает данные о прямоугольниках процессу-
серверу.
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера данные о размере, цвете, символе
заполнителе и положении прямоугольника в окне консоли.
− Увеличивает либо уменьшает прямоугольник в N раз.
9. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя строку символов, цвет выводимых
символов, координаты вывода символов, размер буфера для заполнения
строчками символов.
− Передает эти параметры процессу-клиенту, который запущен на
другом компьютере в локальной сети.
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера начальные данные о строке, размере,
цвете символов, положении курсора в окне консоли и размер буфера
для заполнения символами.
− Устанавливает курсор и заполняет буфер введённой строкой с заданной
позиции.
10. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя, размер, цвет прямоугольника в окне
консоли, числа N и M.
− Запрашивает, размер и массив символов для заполнения
прямоугольников.
− Отображает прямоугольник.
− Через интервалы времени М, увеличивает размер прямоугольника на
число N, и отображает прямоугольник правее (или с другой свободной
стороны) от предыдущего
− При нажатии клавиши мыши последовательно передает данные о
прямоугольниках процессу- серверу.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера данные о размере, цвете, символе
заполнителе и положении прямоугольника в окне консоли.
− Отображает прямоугольник.
110
11. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя количество, размер, цвет и начальное
положение прямоугольников в окне консоли.
− Рисует прямоугольники.
− При установке курсора мыши и нажатии клавиши мыши на одном из
прямоугольников окне процесса-сервера или в окне процесса-клиента.
− Передает данные о размере, цвете и положении прямоугольника
процессу-клиенту.
Процесс-Client, который выполняет следующие действия:
− Получает от процесса сервера начальные данные о размере, цвете и
положении прямоугольника в окне консоли.
− Рисует прямоугольник.
12. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия:
− Запрашивает у пользователя количество, размер, цвет символов-
заполнителей и начальное положение прямоугольников в окне консоли.
− Запрашивает символы для заполнения прямоугольников.
− Отображает прямоугольники.
− При двойном нажатии правой клавиши мыши последовательно
передает данные о прямоугольниках процессу-серверу.
− При нажатии левой клавиши мыши последовательно стирает
прямоугольники, и Процесс-клиент тоже стирает прямоугольники.
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера данные о размере, цвете, символе
заполнителе и положении прямоугольника в окне консоли.
− Отображает либо стирает прямоугольник.
13. Написать программы двух консольных процессов Server, Client,
работающих на разных компьютерах в локальной сети.
Процесс-Server, который выполняет следующие действия.
− Запрашивает у пользователя размер окна консоли и цвет фона.
− Запрашивает строку символов.
− Заполняет буфер экрана введённым символом и закрашивает фон
цветом.
− При нажатии клавиши мыши передаёт данные процессу-Client.
Процесс-Client, который выполняет следующие действия.
− Получает от процесса сервера данные о размере и цвете окна, строку
символов.
− Заполняет буфер экрана строкой символов.
Контрольные вопросы
111
1. Что такое именованные каналы (named pipes) в Windows и почему они могут
использоваться для межпроцессного взаимодействия?
2. Какие возможны в Windows варианты именованных каналов, каковы
ограничения и преимущества каждого из них?
3. Какими функциями Windows API осуществляется использование
именованных каналов в программе-сервере?
4. Какими функциями Windows API осуществляется использование
именованных каналов в программе-клиенте?
5. В чем заключается в Windows API наследование описателей (handle
inheritance) и как это может использоваться для межпроцессного
взаимодействия?
Лабораторная работа №8
Тема: Работа с файлами с помощью Win32 API функций
Цель работы:
1. Изучение основных функций Win32 API, используемых для работы с
файлами.
2. Разработка приложения, демонстрирующего создание и открытие файла.
BOOL MoveFile(
LPCTSTR lpExistingFileName, //имя существующего файла
LPCTSTR lpNewFileName, // имя нового файла
);
114
Листинг 1. Пример приложения, создающего файл для записи в него
данных.
#include <windows.h>
#include <iostream.h>
int main()
{
HANDLE hFile;
// создаем файл для записи данных
hFile = CreateFile(
"C:\\demo_file.dat", // имя файла
GENERIC_WRITE, // запись в файл
0, // монопольный доступ к файлу
NULL, // защиты нет
CREATE_NEW, // создаем новый файл
FILE_ATTRIBUTE_NORMAL, // обычный файл
NULL // шаблона нет
);
// проверяем на успешное создание
if (hFile == INVALID_HANDLE_VALUE)
{
cerr << "Create file failed." << endl
<< "The last error code: " << GetLastError() << endl;
cout << "Press any key to finish.";
cin.get();
return 0;
}
// пишем данные в файл
for (int i = 0; i < 10; ++i)
{
DWORD dwBytesWrite;
if (!WriteFile(
hFile, // дескриптор файла
&i, // адрес буфера, откуда идет
запись
sizeof(i), // количество записываемых байтов
&dwBytesWrite, // количество записанных байтов
(LPOVERLAPPED)NULL // запись синхронная
))
{
cerr << "Write file failed." << endl
<< "The last error code: " << GetLastError() << endl;
CloseHandle(hFile);
cout << "Press any key to finish.";
cin.get();
return 0;
}
}
// закрываем дескриптор файла
CloseHandle(hFile);
cout << "The file is created and written." << endl;
return 0;
}
115
Листинг 2. Исходный код приложения, позволяющее выбрать действие
над файлом: создание, открытие, удаление, запись и чтение данных,
копирование и перемещение файлов.
#include <windows.h>
#include <iostream>
#include <conio.h>
using namespace std;
int main()
{
int iMenu;DWORD dwBytesWrite;
char cMenu[200]="Выбрать:\n1 - СОЗДАТЬ ФАЙЛ\n2 - ОТКРЫТЬ
ФАЙЛ\n3 - УДАЛИТЬ ФАЙЛ\n4 - ЗАПИСАТЬ ДАННЫЕ В ФАЙЛ\n5 - ПРОЧИТАТЬ
ДАННЫЕ ИЗ ФАЙЛА\n6 - КОПИРОВАТЬ ФАЙЛ\n7 - ПЕРЕМЕСТИТЬ ФАЙЛ\n8 -
ВЫЙТИ\n";
CharToOem(cMenu, cMenu);
while(1)
{
system("cls");
cout << cMenu; cin >> iMenu;
switch(iMenu)
{
HANDLE hFile;
char buf[50],buf1[50];
case 1:
CharToOem("СОЗДАТЬ ФАЙЛ:\nИмя файла?\n", buf);
cout << buf;
cin >> buf;
hFile = CreateFile(buf,GENERIC_WRITE,
0,NULL,CREATE_NEW, FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
cerr << "Create file failed." << endl
<< "The last error code: " <<
GetLastError();
cout << "\nPress any key to finish.";
getch();
break;
}
cout << "The file is created." << endl;
CloseHandle(hFile);
getch();
break;
case 2:
CharToOem("ОТКРЫТЬ ФАЙЛ:\nИмя файла?\n", buf);
cout << buf;
cin >> buf;
hFile = CreateFile(buf,GENERIC_WRITE,0,NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
cerr << "Open file failed." << endl
<< "The last error code: " <<
116
GetLastError();
cout << "\nPress any key to finish.";
getch();
break;
}
cout << "The file is opened." << endl;
CloseHandle(hFile);
getch();
break;
case 3:
CharToOem("УДАЛИТЬ ФАЙЛ:\nИмя файла?\n", buf);
cout << buf;
cin >> buf;
if (!DeleteFile(buf))
{
cerr << "Delete file failed." << endl
<< "The last error code: " <<
GetLastError();
cout << "\nPress any key to finish.";
getch();
break;
}
cout << "The file is deleted." << endl;
getch();
break;
case 4:
CharToOem("ЗАПИСАТЬ ДАННЫЕ В ФАЙЛ:\nИмя
файла?\n", buf); cout << buf;
cin >> buf;
hFile =
CreateFile(buf,GENERIC_WRITE,0,NULL,OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
cerr << "Creat file failed." << endl
<< "The last error code: " <<
GetLastError();
cout << "\nPress any key to finish.";
getch();
break;
}
cout << "The file is opened." << endl;
CharToOem("Введите данные!\n", buf); cout <<
buf;
cin >> buf;
if
(!WriteFile(hFile,&buf,sizeof(buf),&dwBytesWrite,
(LPOVERLAPPED)NULL))
{
cerr << "Write file failed." << endl
<< "The last error code: " <<
GetLastError();
CloseHandle(hFile);
cout << "\nPress any key to
117
continue.";
getch();
break;
}
cout << "The Information is written." << endl;
CloseHandle(hFile);
getch();
break;
case 5:
CharToOem("ЧТЕНИЕ ДАННЫХ ИЗ ФАЙЛА:\nИмя
файла?\n", buf); cout << buf;
cin >> buf;
hFile =
CreateFile(buf,GENERIC_READ,0,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
cerr << "Open file failed." << endl
<< "The last error code: " <<
GetLastError();
cout << "\nPress any key to finish.";
getch();
break;
}
cout << "The file is opened." << endl;
if
(!ReadFile(hFile,&buf,sizeof(buf),&dwBytesWrite,
(LPOVERLAPPED)NULL))
{
cerr << "Read file failed." << endl
<< "The last error code: " <<
GetLastError();
CloseHandle(hFile);
cout << "\nPress any key to
continue.";
getch();
break;
}
if(!dwBytesWrite) cout << "The file is empty"
<< endl;
else cout << "Read information:"<< buf << endl;
CloseHandle(hFile);
getch();
break;
case 6:
CharToOem("КОПИРОВАНИЕ ФАЙЛОВ:\nИмя файла
источника?\n", buf); cout << buf;cin >> buf;
CharToOem("Имя нового файла?\n", buf1); cout <<
buf1;cin >> buf1;
if(!CopyFile(buf,buf1,true))
{
cerr << "Move file failed. The last error code:
" << GetLastError();
cout << "\nPress any key to continue.";
118
getch();
break;;
};
cout << "The file is copied."<< endl;
getch();
break;
case 7:
CharToOem("ПЕРЕМЕЩЕНИЕ ФАЙЛОВ:\nИмя файла
источника?\n", buf); cout << buf;cin >> buf;
CharToOem("Имя нового файла?\n", buf1); cout <<
buf1;cin >> buf1;
if(!MoveFile(buf,buf1))
{
cerr << "Move file failed. The last error code:
" << GetLastError();
cout << "\nPress any key to continue.";
getch();
break;;
};
cout << "The file is moved."<< endl;
getch();
break;
case 8: return 0;
}
}
}
Задание на выполнение
1. Набрать программу которая приведена в листинге 1. Ознакомиться с
работой функций CreateFile и WriteFile, протестировать работу программы при
разных значениях параметров для данных функций.
2. Написать программу в которой были бы реализованы следующие
функции работы с файлами:
− создание файла;
− открытие файла;
− удаление файла;
− запись данных в файл, данные вводятся с клавиатуры;
− чтение данных из файла;
− копирование фала;
− перемещение файла.
Выбор функции организовать в виде меню, пути до файлов вводить с
клавиатуры. Ознакомится с основными параметрами данных функций,
исследовать их работу при изменении основных параметров.
Контрольные вопросы
1. Какая функция используется при создании или открытии файла? Поясните
значения ее параметров.
2. Какие функции можно использовать для записи данных файл и чтения из
файла?
3. Какую функцию выполняет данный оператор: hIn=CreateFile(argv [1],
119
GENERIC_READ, 0, NULL, OPEN_EXISTING, 0,NULL)?
4. Поясните, какую функцию выполняет данный оператор:
hOut=CreateFile(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL).
5. Поясните, какую функцию выполняет данный оператор: while (ReadFile(hIn,
Buffer, BUF_SIZE, &nIn, NULL) && nIn > 0).
6.Какую функцию выполняет данный оператор: WriteFile (hOut,Buffer, nIn,
&nOut, NULL)?
7. Поясните, какую функцию выполняет данный оператор:
if(!СopyFile(argv[1],argv[2],FALSE)).
Лабораторная работа №9
Тема: Работа атрибутами файлов с помощью Win32 API функций
Цель работы:
1. Изучение основных функций Win32API, используемых для определения
и изменения атрибутов файлов.
2. Разработка приложения, демонстрирующего просмотр и изменение
атрибутов файла.
120
BOOL SetFileAttributes(
STSTR lpFileName, // имя файла
dwFileAttributes // атрибуты файла
);
3. Блокирование файла
BOOL LockFile (
HANDLE hFile, // дескриптор файла
DWORD dwFileOffsetLow // младшая часть смещения
DWORD dwFileOffsetHigh // старшая часть смещения
DWORD nNumberOfBytesToLockLow // младшая часть количества
байтов
DWORD nNumberOfBytesToLockHigh //старшая часть количества
байтов
);
Для обмены блокировки используется функция
BOOL UnlockFile (
HANDLE hFile, // дескриптор файла
DWORD dwFileOffsetLow // младшая часть смещения
DWORD dwFileOffsetHigh // старшая часть смещения
DWORD nNumberOfBytesToLockLow // младшая часть количества
байтов
DWORD nNumberOfBytesToLockHigh //старшая часть количества
байтов
);
4. Получение информации о файле
Чтобы получить информацию о файле, можно использовать
функцию:
GetFilelnformationByHandle (
HANDLE hFile, // дескриптор файла
// указатель на информацию
LPBY_HANDLE_FILE_INFORMATION lpFilelnformation
);
Параметр lpFilelnformation должен указывать на структуру типа
LPBY_HANDLE_FILE_INFORMATION, в которую функция запишет информацию
о файле.
121
FILETIME ftCreationTime; // время создания файла
FILETIME ftLastAccessTime; // время последнего доступа к
файлу
FILETIME ftLastWriteTime; // время последней записи в файл
DWORD dwVolumeSerialNumber; // серийный номер тома
DWORD nFileSizeHigh; // старшая часть размера файла
DWORD nFileSizeLow; // младшая часть размера файла
DWORD nNumberOfLinks; // количество ссылок на файл
DWORD nFileIndexHigh; // старшая часть индекса файла
DWORD nFileIndexLow; // младшая часть индекса файла
} BY_HANDLE_FILE_INFORMATION, *LPBY_HANDLE_FILE_INFORMATION;
123
cout << "\tFILE_ATTRIBUTE_SYSTEM - 3"<< endl;
cout << "\tFILE_ATTRIBUTE_DIRECTORY - 4"<< endl;
cout << "\tFILE_ATTRIBUTE_ARCHIVE - 5"<< endl;
cout << "\tFILE_ATTRIBUTE_NORMAL - 6"<< endl;
cout << "\tFILE_ATTRIBUTE_TEMPORARY - 7"<< endl;
cout << "\tFILE_ATTRIBUTE_COMPRESSED - 8"<< endl;
cout << "\tFILE_FLAG_POSIX_SEMANTICS - 9"<< endl;
cout << "\tFILE_FLAG_BACKUP_SEMANTICS- 10"<< endl;
cout << "\tFILE_FLAG_DELETE_ON_CLOSE - 11"<< endl;
cout << "\tFILE_FLAG_SEQUENTIAL_SCAN - 12"<< endl;
cout << "\tFILE_FLAG_RANDOM_ACCESS - 13"<< endl;
cout << "\tFILE_FLAG_NO_BUFFERING - 14"<< endl;
cout << "\tFILE_FLAG_OVERLAPPED - 15"<< endl;
cout << "\tFILE_FLAG_WRITE_THROUGH - 16"<< endl;
cout << "\nType a number!(1..16 - change attribute/0 -
return for main menu) ";
cin >> i;
if (!i) return 0;
else
{
cout << "\nType '1' to set attrebut and '0' to unset
!(1/0) ";
cin >> set;
if(set==0) {dwFileAttributes=dwFileAttributes ^
adwFileAttributes[i-1]; } else
if(set==1) {dwFileAttributes=dwFileAttributes |
adwFileAttributes[i-1];}
cout << endl<< "0x" << hex << dwFileAttributes << " : ";
dec( cout );
for( int i = 31; i >= 0; i--) if( dwFileAttributes & (1
<< i) ) cout << "1"; else cout << "0";
getch();
if(!SetFileAttributes(lpFileName,dwFileAttributes))
cerr << "Set File Attributes failed." << endl
<< "The last error code: " << GetLastError();
}
}
}
int main()
{
char buf[256],FileName[256]="";
HANDLE hFile;
DWORD lpFileSizeHigh;
BY_HANDLE_FILE_INFORMATION fi;
FILETIME ft;SYSTEMTIME st;
int iMenu;char cMenu[200]="ВЫБЕРИТЕ ДЕЙСТВИЕ:\n1 -
ПРОСМОТРЕТЬ/ИЗМЕНИТЬ АТРИБУТЫ ФАЙЛА\n2 - ПОСМОТРЕТЬ/ИЗМЕНИТЬ
РАЗМЕР ФАЙЛА\n3 - БЛОКИРОВАТЬ/РАЗБЛОКИРОВАТЬ ФАЙЛ\n4 - ПОСМОТРЕТЬ
ИНФОРМАЦИЮ О ФАЙЛЕ\n5 - ВЫЙТИ\n;
CharToOem(cMenu, cMenu);
CharToOem("Iм'я файлу?\n", buf); cout << buf; cin >> FileName;
if ((hFile=creatfile(FileName))!=INVALID_HANDLE_VALUE)
while(1)
{
124
system("cls");
cout << cMenu; cin >> iMenu;
switch(iMenu)
{
case 1:
CharToOem("АТРИБУТЫ ФАЙЛА:\n", buf); cout <<
buf;
cout<<getfileattributes(FileName)<<endl;
CharToOem("\nИзменить атрибуты?(y/n) ",buf);
cout << buf; cin >> buf;
if(*buf=='y') setfileattributes(FileName);
break;
case 2:
CharToOem("РАЗМЕР ФАЙЛА:\n", buf); cout << buf;
cout<<FileName<<" -
"<<(floor(((double)GetFileSize(hFile,&lpFileSizeHigh)/1024)*100)/1
00)<< " KB" <<endl;
CharToOem("\nИзменить размер?(y/n) ",buf); cout
<< buf; cin >> buf;
if(*buf=='y')
{
CharToOem("Новый размер? ", buf); cout <<
buf;cin >>lpFileSizeHigh;
SetFilePointer(hFile,lpFileSizeHigh,NULL,FILE_BEGIN);
if(SetEndOfFile(hFile)) cout<<"size
changed";
else cerr<<"size chang failed"<< endl
<<"The last error code: "<<
GetLastError();
}
cout<<"\nPress any key to continue!";
getch();
break;
case 3:
CharToOem("ДОСТУП К ФАЙЛУ:\n\tблокировать -
1\n\tразблокировать - 2\n", buf); cout << buf;cin>>buf;
if (*buf=='1')
{
if(LockFile(hFile,0,0,GetFileSize(hFile,&lpFileSizeHigh),0))
cout << "file locked";
else cerr<<"file lock filed"<< endl
<<"The last error code: "<<
GetLastError();
}else
if (*buf=='2')
{
if(UnlockFile(hFile,0,0,GetFileSize(hFile,&lpFileSizeHigh),0))
cout << "file unlocked";
else cerr<<"file unlock filed"<< endl
<<"The last error code: "<<
GetLastError();
}
cout<<"\nPress any key to continue!";
125
getch();
break;
case 4:
CharToOem("ИНФОРМАЦИЯ О ФАЙЛЕ:\n", buf); cout
<< buf;
if(GetFileInformationByHandle(hFile,&fi))
{
cout << endl << "ATTRIBUTES:" <<endl;
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_ARCHIVE) cout<<"\tFILE_ATTRIBUTE_ARCHIVE\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_COMPRESSED) cout<<"\tFILE_ATTRIBUTE_COMPRESSED\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY) cout<<"\tFILE_ATTRIBUTE_DIRECTORY\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_HIDDEN) cout<<"\tFILE_ATTRIBUTE_HIDDEN\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_NORMAL) cout<<"\tFILE_ATTRIBUTE_NORMAL\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_READONLY) cout<<"\tFILE_ATTRIBUTE_READONLY\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_SYSTEM) cout<<"\tFILE_ATTRIBUTE_SYSTEM\n";
if(fi.dwFileAttributes &
FILE_ATTRIBUTE_TEMPORARY) cout<<"\tFILE_ATTRIBUTE_TEMPORARY\n";
FileTimeToSystemTime(&fi.ftCreationTime,
&st);
cout<<endl<<"FILE TIME:"<<endl
<<"\tCreation Time "<<st.wYear<<"-
"<<st.wMonth<<"-"<<st.wDay<<"
"<<st.wHour<<":"<<st.wMinute<<":"<<st.wSecond<<endl;
FileTimeToSystemTime(&fi.ftLastAccessTime,
&st);
cout<<"\tLast Access "<<st.wYear<<"-
"<<st.wMonth<<"-"<<st.wDay<<"
"<<st.wHour<<":"<<st.wMinute<<":"<<st.wSecond<<endl;
FileTimeToSystemTime(&fi.ftLastWriteTime,
&st);
cout<<"\tLast Write "<<st.wYear<<"-
"<<st.wMonth<<"-"<<st.wDay<<"
"<<st.wHour<<":"<<st.wMinute<<":"<<st.wSecond<<endl;
cout<<endl
<<"Volume Serial Number "<<fi.dwVolumeSerialNumber<<endl;
cout<<"File Size
"<<(floor(((double)fi.nFileSizeLow/1024)*100)/100)<< " KB" <<endl;
cout<<"Number Of Links
"<<fi.nNumberOfLinks<<endl;
cout<<"File Index (Low High)
"<<fi.nFileIndexLow<<" "<<fi.nFileIndexHigh<<endl;
}
else cerr<<"Get file information filed"<< endl
<<"The last error code: "<<
GetLastError();
cout<<"\nPress any key to continue!";
126
getch();
break;
case 5: CloseHandle(hFile);return 0;
}
}
return 0;
}
Задание на выполнение
1. Написать программу в которой были бы реализованы следующие
функции работы с файлами:
− определение и изменение атрибутов файла;
− определение и изменение размеров файла;
− блокирование файла;
− получение информации о файле.
2. Выбор функции организовать в виде меню, пути до файлов вводить с
клавиатуры.
3. Ознакомится с основными параметрами данных функций, исследовать
их работу при изменении основных параметров.
Контрольные вопросы
1. Какие существуют функции для работы со временем?
2. Какие значения атрибутов имеют файлы и какой функцией они
устанавливаются?
3. Какой функцией можно изменить атрибуты файлов?
4. Какая функция создает имена для временных файлов?
Цель работы:
1. Изучение основных функций Win32API, используемых для работы с
каталогами.
2. Разработка приложения, демонстрирующего создание, удаление и
перемещение каталогов, поиск файлов и наблюдение за каталогом.
127
LPCTSTR lpNewDirectory, // имя нового каталога
LPSECUTITY_ATTRIBUTES lpSecurutyAttributes // атрибуты защиты
);
3. Удаление каталога
Для удаления пустого каталога предназначена функция:
BOOL RemoveDirectory(
LPCTSTR lpPathName // имя каталога
);
4. Перемещение каталога
BOOL MoveFile(
LPCTSTR lpExistingFileName, // имя существующего файла
LPCTSTR lpNewFileName // имя нового файла );
128
5. Определение и установка текущего каталога
DWORD GetCurrentDirectory(
DWORD nBufferLength, // длина буфера для имени каталога
LPTSTR lpBuffer // адрес буфера для имени каталога );
Приложение может изменить имя текущего каталога, используя функцию:
BOOL SetСurrentDirectory(
LPCTSTR lpPathName // имя нового текущего каталога );
130
break;
}
cout << "directory removed";
cout<<"\nPress any key to continue!";
getch();
break;
case 4:
CharToOem("ПЕРЕМЕСТИТЬ КАТАЛОГ\n Имя каталога
источника?", buf); cout << buf; cin >> buf;
CharToOem("Имя каталога получателя?", buf1); cout <<
buf1;cin >> buf1;
if(!MoveFile(buf,buf1))
{
cerr << endl << "Move Directory failed!" << endl
<< "The last error code: " << GetLastError()
<<endl;
cout << "Press any key to continue";
getch();
break;
}
cout << "directory moved";
cout<<"\nPress any key to continue!";
getch();
break;
case 5:
CharToOem("ТЕКУЩИЙ КАТАЛОГ:\n", buf); cout << buf;
GetCurrentDirectory(sizeof(buf),buf);
cout << buf <<endl;
CharToOem("Изменить текущий каталог?(y/n) ", buf);
cout << buf; cin >> buf;
if(*buf=='y')
{
CharToOem("Задайте новый каталог! ", buf);cout <<
buf; cin >> buf;
if(!SetCurrentDirectory(buf))
{
cerr << endl << "Set Current failed!" << endl
<< "The last error code: " << GetLastError()
<<endl;
}
else cout << "The current directory is set" << endl;
}
cout<<"\nPress any key to continue!";
getch();
break;
case 6:
CharToOem("НБЛЮДАТЬ ЗА КАТАЛОГОМ\n Имя каталога?",
buf); cout << buf; cin>>buf;
if ((hFind=FindFirstChangeNotification(buf,false,
FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CH
ANGE_SIZE)) == INVALID_HANDLE_VALUE)
{
cerr << endl << "Find First Notification failed!" <<
endl
<< "The last error code: " << GetLastError()
131
<<endl;
cout << "Press any key to continue";
getch();
break;
} else cout << "Watch.." << endl;
while
(WaitForSingleObject(hFind,10000)!=WAIT_OBJECT_0) { }
cout << endl << "The watched directory has been
changed." << endl;
bFindMore=false;
cout << "Find next change notification?(y/n)"; cin
>>buf;
if(*buf='y') bFindMore=true;
while (bFindMore)
{
if(bFindMore =
FindNextChangeNotification(hFind))
{
cout << "Watch.." << endl;
while (WaitForSingleObject(hFind, 10000) !=
WAIT_OBJECT_0) {}
cout << endl << "The watched directory has been
changed again." << endl;
cout << "Find next change notyfication?(y/n)";
cin >>buf;
if(*buf=='y') bFindMore=true; else
bFindMore=false;;
}
else
{
cerr << endl << "Find Notification failed!" <<
endl
<< "The last error code: " << GetLastError()
<<endl;
bFindMore=false;
}
}
FindCloseChangeNotification(hFind);
cout<<"\nPress any key to continue!";
getch();
break;
case 7: return 0;
}
}
return 0;
}
Задание на выполнение
1. Написать программу в которой были бы реализованы следующие функции
работы с каталогами:
− создание каталога;
− поиск файлов в каталоге;
− удаление каталога;
− перемещение каталога;
132
− определение и установка текущего каталога;
− наблюдение за изменениями в каталоге.
2. Выбор функции организовать в виде меню, пути до файлов и каталогов
вводить с клавиатуры.
3. Ознакомится с основными параметрами данных функций, исследовать их
работу при изменении основных параметров.
Контрольные вопросы
1. Какие функции используются для создания каталогов?
2. Какие функции используются для удаления, перемещения и копирования
каталогов?
3. С помощью какой функции можно организовать поиск файла в каталоге?
4. Поясните задачу функции: FindFirstFile и ее параметры.
5. Поясните задачу функци: FindNextFile и ее параметры.
6. Поясните задачу функции: FindClose и ее параметры.
7. Какую задачу выполняет функция GetFileAttributes и ее параметры?
8. Какова структура параметра lhffd?
9. Какой параметр функции FindFirstFile указывает на каталог?
Цель работы:
1. Изучение основных функций Win32 API, используемых для создания
динамических библиотек DLL.
2. Разработка приложения, демонстрирующего создание, подключение и
использование динамических библиотек.
133
Рисунок 1. Иллюстрация механизма связывания
1. Неявное связывание
Неявное связывание, или связывание во время загрузки – более простой из двух
способов. При использовании языка Microsoft Visual C+ + для этого необходим ряд
шагов:
1. Функции для новой библиотеки собираются и компонуются как библиотека
DLL, а не как, например, консольное приложение.
2. В процессе компоновки создается библиотечный файл .LIB, который является
суррогатом для основного кода. Этот файл необходимо поместить в каталог
вызывающей программы.
3. Процесс компоновки создает и файл .DLL, содержащий исполняемый образ.
Обычно этот файл помещается в тот же каталог, что и приложение, которое будет его
использовать, а приложение загружает .DLL во время инициализации.
134
программой для разрешения внешних ссылок и создания действительных связей с
файлом .DLL во время загрузки и подключаемый на стадии компоновки.
Вызывающая функцию программа должна определить импортируемую функцию
путем использования модификатора
_declspec (dllimport)
DWORD MyFunction (...);
При компоновке вызывающей библиотеку программы, т.е. перед созданием .exe
файла, необходимо в меню Project -->Setting на вкладке Link в окне Project_Options
набрать путь и имя файла библиотеки MyFunction.lib. После этого необходимо
убедиться в том, что файл библиотеки .DLL доступен ей. Обычно это обеспечивается
помещением файла .DLL в тот же каталог, в котором находится исполняемый файл.
3. Явное связывание
Явное связывание, или связывание во время выполнения, несколько сложнее и
требует от программы специального запроса для загрузки библиотеки (функция
LoadLibrary) или ее выгрузки (функция FreeLibrary). Затем программа получает адрес
нужной точки входа и использует его как указатель в вызове функции. В
вызывающей программе функция не объявляется; вместо этого необходимо объявить
переменную как указатель на функцию. Поэтому при компоновке программы нет
потребности в файле библиотеки. Необходимы три функции: LoadLibrary,
GetProcAddress и FreeLibrary.
HINSTANCE LoadLibrary (LPCTSTR lpLibFileName);
Возвращаемый дескриптор (типа HINSTANCE, а не HANDLE) не примет
значения NULL в случае неудачи. Расширение .DLL в имени файла не требуется. С
помощью функции LoadLibrary можно загрузить и файл типа .ЕХЕ. Так как
динамические библиотеки разделяемые, система ведет учет ссылок на каждую DLL
(их число увеличивается при вызове функции LoadLibrary), поэтому не требуется
отображения фактически существующего файла. Даже если файл DLL найден,
функция LoadLibrary выполнится неудачно в случае, если библиотека неявно связана
с другой библиотекой, которая не может быть найдена.
Аналогичная функция LoadLibraryEx имеет несколько флагов, используемых для
указания альтернативных путей поиска и загрузки библиотеки как файла данных.
После работы с данным экземпляром или для загрузки его другой версии
необходимо освободить дескриптор библиотеки, освобождая тем самым ресурсы,
включая и виртуальное адресное пространство, занятое библиотекой. Если счетчик
ссылок указывает на то, что библиотека используется другим процессом, она остается
загруженной в память:
BOOL FreeLibrary (HINSTANCE hLibModule);
После загрузки динамической библиотеки и до ее выгрузки можно получить
адреса точек входа с использованием функции GetProcAddress.
FARPROC GetProcAddress ( HMODULE module, LPCSTR lpProcName);
Параметр hModule, несмотря на другой тип (тип HINSTANCE определен как
HMODULE), является экземпляром, получаемым от функций LoadLibrary или
GetModuleHandle, которые здесь не описаны. Параметр lpProcName, который не
может быть строкой стандарта Unicode, является именем точки входа. В случае
неудачи возвращаемое функцией значение будет NULL.
Используя функцию GetModuleFileName, можно получить имя файла,
135
связанного с дескриптором hModule. И наоборот, по заданному имени файла (файл
типа .ехе или .dll) функция GetModuleHandle возвращает дескриптор, связанный с
файлом, если тот был загружен текущим процессом. Функция входа в библиотеку
также в выполняется при создании или завершении процессом новых потоков.
После выбора, вводите имя и нажимаете ОК, затем далее и выбираете Тип
приложения "библиотека DLL".
После этого вы компилируете dll и получаете 2 файла: dll.dll и dll.lib (в нашем
примере), затем необходимо создать файл dll.h (в нашем примере), где указываете
функции, которые находятся в этом dll файле.
Пример написания в хидер файл:
void LetterList();
int PutInt(int param);
В нем мы описываем прототипы наших функций. Этот файл вы будете вставлять
в вашу программу, где будете подключать dll-файл.
1.2. DLL вызов функции. После того как полчим откомпилированный dll-файл,
lib-файл и файл заголовков, все это нужно скопировать в нашу программу, где мы
будем подключать dll файл. Для этого необходимо создать новый проект: Файл -
Создать - Проект - Консольное Приложение Win32 - Готово. В этот проект вы
указываете, где будет exe файл dll.dll и dll.lib, а хидер файл (dll.h) вы указываете, где
ваши хидер файлы вашего проекта.
В полученный файл мы вписываем код программы, где вызываем наши две
функции (PutInt иLetterList)
#include "stdafx.h"
#include "dll.h"
#include<iostream>
137
#include<conio.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int x = PutInt(5);
LetterList();
cout<<x;
_getch();
return 0;
}
Обратите внимание, что подключается файл dll.h ( #include "dll.h" ), который мы
создали.
1.3. Подключение dll файла в Visual Studio. Далее необходимо подключить
файл. В Visual Studio это делается следущим образом: Проект - Свойства -
Компоновщик - Ввод - Дополнительные зависимости. Как показано на рисунке 3,
здесь нужно добавить путь к файлу.lib.
// Returns a - b
static MATHFUNCSDLL_API double Subtract(double a, double b);
// Returns a * b
static MATHFUNCSDLL_API double Multiply(double a, double b);
// Returns a / b
// Throws const std::invalid_argument& if b is 0
static MATHFUNCSDLL_API double Divide(double a, double b);
};
}
Когда символ MATHFUNCSDLL_EXPORTS определен, символ
MATHFUNCSDLL_API установит модификатор __declspec(dllexport) в объявлениях
функций-членов в этом коде. Этот модификатор разрешает экспорт функции
библиотекой DLL для использования ее другими приложениями. Если символ
MATHFUNCSDLL_EXPORTS не определен, например, когда файл заголовка
139
включен приложением, символ MATHFUNCSDLL_API определяет модификатор
__declspec(dllimport) в объявлениях функций-членов. Этот модификатор
оптимизирует импорт функции в приложении. По умолчанию шаблон нового проекта
для библиотеки DLL добавляет символ PROJECTNAME_EXPORTS в список
определенных символов для проекта DLL. В этом примере символ
MATHFUNCSDLL_EXPORTS определяется при сборке проекта MathFuncsDll.
Если проект DLL собирается в командной строке, воспользуйтесь параметром
компилятора /D, чтобы определить символ MATHFUNCSDLL_EXPORTS.
4. В проекте MathFuncsDll в обозревателе решений в папке Исходные файлы
откройте файл MathFuncsDll.cpp.
5. Реализуйте функциональность класса MyMathFuncs в исходном файле. Код
должен выглядеть примерно следующим образом:
//MathFuncsDll.cpp: Defines the exported functions
// for the DLL application.
#include "stdafx.h"
#include "MathFuncsDll.h"
#include <stdexcept>
using namespace std;
namespace MathFuncs
{
double MyMathFuncs::Add(double a, double b)
{
return a + b;
}
double MyMathFuncs::Subtract(double a, double b)
{
return a - b;
}
double MyMathFuncs::Multiply(double a, double b)
{
return a * b;
}
double MyMathFuncs::Divide(double a, double b)
{
if (b == 0)
{
throw invalid_argument("b cannot be zero!");
}
return a / b;
}
}
6. Скомпилируйте библиотеку динамической компоновки, выбрав Собрать
решение в меню Сборка.
Примечание: При использовании выпуска Express, в котором не отображается
меню Сборка, в строке меню выберите Сервис, Параметры, Расширенные
параметры, чтобы включить это меню, а затем выберите Сборка, Собрать решение.
Примечание: При сборке проекта из командной строки используйте параметр
компилятора /LD, указывающий на то, что выходной файл должен являться DLL-
файлом..Используйте параметр компилятора /EHsc для включения обработки
исключений С++.
140
2.3. Создание приложения, ссылающегося на DLL
1. Чтобы создать приложение С++, которое будет ссылаться и использовать
созданную ранее библиотеку DLL, в меню Файл выберите пункт Создать и
затем пункт Проект.
2. В левой области в категории Visual C++ выберите Win32.
3. В центральной области выберите Консольное приложение Win32.
4. Укажите имя проекта, например MyExecRefsDll, в поле Имя. В
раскрывающемся списке рядом с полем Решение выберите Добавить в
решение. В результате новый проект будет добавлен в решение, содержащее
библиотеку DLL. Нажмите кнопку ОК.
5. На странице Обзор диалогового окна Мастер приложений Win32 нажмите
кнопку Далее.
6. На странице Параметры приложения выберите в поле Тип приложения
пункт Консольное приложение.
7. На странице Параметры приложения в разделе Дополнительные
параметры снимите флажок Предкомпилированный заголовок.
8. Нажмите кнопку Готово, чтобы создать проект.
Использование функциональности из библиотеки классов в приложении
1. После создания консольного приложения будет создана пустая программа.
Имя исходного файла будет совпадать с ранее выбранным именем. В этом
примере он имеет имя MyExecRefsDll.cpp.
2. Для использования в приложении математических процедур, созданных в
библиотеке DLL, необходимо сослаться на эту библиотеку. Для этого в
Обозревателе решений выберите проект MyExecRefsDll, а затем в меню
Проект выберите пункт Ссылки. В диалоговом окне Страницы свойств
разверните узел Общие свойства, выберите .NET Framework и ссылки и
нажмите кнопку Добавить новую ссылку.
3. В диалоговом окне Добавление ссылки перечислены библиотеки, на которые
можно создать ссылку.На вкладке Проект перечислены все проекты текущего
решения и включенные в них библиотеки, если они есть. Установите флажок
рядом с MathFuncsDll на вкладке Проекты, а затем нажмите кнопку ОК.
4. Для создания ссылки на файлы заголовка DLL необходимо изменить путь к
каталогам включения. Для этого в диалоговом окне Страницы свойств
последовательно разверните узлы Свойства конфигурации и C/C++, а затем
выберите Общие. В поле Дополнительные каталоги включаемых файлов
укажите путь к месту размещения файла заголовка MathFuncsDll.h. Можно
использовать относительный путь, например ..\MathFuncsDll\. Затем нажмите
кнопку ОК.
5. Теперь класс MyMathFuncs можно использовать в приложении. Замените
содержимое файла MyExecRefsDll.cpp следующим кодом.
// MyExecRefsDll.cpp
// compile with: /EHsc /link MathFuncsDll.lib
#include <iostream>
#include "MathFuncsDll.h"
using namespace std;
int main()
{
double a = 7.4;
141
int b = 99;
cout << "a + b = " <<
MathFuncs::MyMathFuncs::Add(a, b) << endl;
cout << "a - b = " <<
MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
cout << "a * b = " <<
MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
cout << "a / b = " <<
MathFuncs::MyMathFuncs::Divide(a, b) << endl;
try
{
cout << "a / 0 = " <<
MathFuncs::MyMathFuncs::Divide(a, 0) << endl;
}
catch (const invalid_argument &e)
{
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
6. Соберите исполняемый файл, выбрав команду Собрать решение в меню
Сборка.
2.4. Запуск приложения
1. Убедитесь в том, что проект MyExecRefsDll выбран в качестве проекта по
умолчанию. В Обозревателе решений выберите MyExecRefsDll и затем в
меню Проект выберите Назначить запускаемым проектом.
2. Чтобы запустить проект, в строке меню выберите Отладка, Запуск без
отладки. Результат выполнения должен выглядеть примерно следующим
образом:
1. a + b = 106,4
2. a - b = -91,6
3. a * b = 732,6
4. a / b = 0,0747475
Перехвачено исключение: b не может быть равно нулю!
Контрольные вопросы
1.Какие способы создания динамических библиотек существуют?
2.Опишите процесс неявного связывания функций в динамической библиотеке.
3.Как осуществляется процесс экспорта, импорта функций в динамическую
библиотеку при неявном связывании?
4.Опишите функции, которые используются при явном связывании функций в
библиотеке (LoadLibrary, FreeLibrary, GetProcAddress).
5.Как создается .DLL файл для функции Asc2Un?
6.Как в вызывающей программе импортируется функция из динамической
библиотеки?
7.Поясните работу программы преобразования файла из кодировки ASCII в
кодировку Unicode.
142
Список рекомендованной литературы
143