Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ОС. Мет. указ-я по вып-ю лаб. работ на C++..pdf
Скачиваний:
24
Добавлен:
21.05.2015
Размер:
793.6 Кб
Скачать

ВВЕДЕНИЕ

Лабораторные работы по курсу "Операционные системы" выполняются в среде разработчика Visual C++ 6 на языке программирования С++. Программы должны устойчиво работать в операционных системах Windows 95–98, ME, NT4, 2000, XP (лабораторная работа № 5 выполняется только в операционных системах Windows NT, 2000, XP).

ПОРЯДОК ВЫПОЛНЕНИЯ ЛАБОРАТОРНЫХ РАБОТ

1.Подготовка и допуск к работе.

1.1.К выполнению лабораторной работы допускаются студенты, которые подготовились к работе и имеют не более двух незащищенных работ.

1.2.Перед работой каждый студент должен:

предъявить преподавателю полностью оформленный отчет о предыдущей работе;

ответить на вопросы преподавателя.

1.3.К работе не допускаются студенты, которые не выполнили одно из вышеперечисленных требований.

1.4.Лабораторные работы, которые студент пропустил, выполняются в конце семестра; допуск к работе производится в порядке, указанном в п.1.1.

2.Требования по содержанию отчета по лабораторным работам приведены в конце каждой лабораторной работы.

3

ЛАБОРАТОРНАЯ РАБОТА №1

"Многопоточное приложение"

Цель работы: Изучение принципов разработки программы, позволяющей использовать несколько потоков (На примере программы Threads).

Задание к лабораторной работе:

1. Запустить программу Threads. Результат работы программы представлен на рис.1.1. В результате исполнения создаются четыре вторичных потока, каждый из которых рисует в дочернем окне прямоугольники, задавая их размеры и цвет случайным образом. В верхней части окна находится список, хранящий информацию обо всех четырех потоках. Выделив какой-нибудь элемент списка и выбрав определенную команду меню Thread, можно приостановить любой из потоков, возобновить его выполнение или изменить приоритет. С помощью меню Options можно также активизировать исключающий семафор, который позволит в каждый момент времени выполняться только одному потоку.

Рис.1.1. Окно программы Threads'

4

2. Рассмотреть исходный код программы. Первоначально рассмотреть функции инициализации. Данный класс функций регистрируют два класса окон, один из которых предназначен для главного окна, а другой - для дочерних окон, в которых потоки выполняют свои графические операции. Кроме того, создается таймер, позволяющий с пятисекундным интервалом обновлять в списке информацию о каждом из потоков. Функция CreateWindows создает и позиционирует все окна, в том числе и список, в котором содержится информация о каждом потоке. Четыре потока создаются в обработчике сообщения WM_CREATE.

/* WIN MAIN - вызов функции инициализации и запуск цикла обработки сообщений */

int WINAPI WinMain ( HINSTANCE hinstThis, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int iCmdShow )

{

MSG msg;

hInst = hinstThis; // запись в глобальную переменную if (! InitializeApp ( ))

{//выход из программы, если приложение не было инициализировано return( 0 ) ;

}

ShowWindow ( hwndParent, iCmdShow ); UpdateWindow( hwndParent );

// получение сообщений из очереди while ( GetMessage( &msg, NULL, 0, 0 ) )

{

TranslateMessage( &msg ) ; DispatchMessage ( &msg ) ;

}

return( msg.wParam );

}

Обратить внимание на отсутствие функции PeekMessage. Это - наглядное свидетельство того, что в приложении реализована приоритетная многозадачностью. (Многозадачность данного типа подразумевает, что система прерывает выполнение потока, предоставляя другим потокам возможность получить доступ к ресурсам центрального процессора. При кооперативной многозадачности система ожидает, пока поток не вернет ей управление над процессором.) Потоки имеют возможность непрерывно производить экранные операции, не монополизируя процессор. В это же время могут выполняться и другие программы.

Помимо регистрации класса приложения и выполнения стандартных действий по инициализации, функция InitializeApp задает приоритеты потоков и запускает все потоки в режиме ожидания.

5

/*INITIALIZE APP - регистрация двух классов и создание окон. */ BOOL InitializeApp ( void )

{

//Пометить исходное состояние каждого потока как SUSPENDED

//сразу при их создании.

for( iCount = 0; iCount < 4; iCount++ )

{

iState[iCount] = SUSPENDED;

}

//Задать первичному потоку более высокий приоритет,

//что позволит ускорить ввод/вывод команд пользователем. SetThreadPriority( GetCurrentThread(),

THREAD_PRIORITY_ABOVE_NORMAL ); // Создать все окна.

return( CreateWindows( ) );

}

Вызов функции SetThreadPriority приводит к увеличению приоритета первичного потока. Если все вторичные потоки будут иметь такой же приоритет, как и первичный, то реакция на выбор команд меню будет очень медленной. Убедиться в этом, запустив программу и повысив приоритет вторичных потоков.

Функция CreateWindow создает не только основное окно, но и список, а также набор дочерних окон для потоков.

/* CREATE WINDOWS - создать главное окно, окно списка и четыре дочерних окна */

BOOL CreateWindows ( void )

{

char szAppName[MAX_BUFFER] ; char szTitle[MAX_BUFFER] ; char szThread[MAX_BUFFER]; HMENU hMenu;

int iCount;

// загрузка соответствующих строк

LoadString( hInst, IDS_APPNAME, szAppName, sizeof(szAppName)); LoadString( hInst, IDS_TITLE, szTitle, sizeof(szTitle));

LoadString( hInst, IDS_THREAD, szThread, sizeof(szThread)); // создать родительское окно

hMenu = LoadMenu( hInst, MAKEINTRESOURCE(MENU_MAIN) ); hwndParent = CreateWindow( szAppName, szTitle,

WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,

CW_USEDEFAULT, CW_USEDEFAULT,

6

CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hinst, NULL ) ;

if( ! hwndParent)

{

return( FALSE ) ;

}

// создать окно списка

hwndList = CreateWindow( "LISTBOX", NULL,

WS_BORDER | WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_NOINTEGRALHEIGHT, 0, 0, 0, 0, hwndParent, (HMENU)1, hinst, NULL ) ;

if( ! hwndList )

{

return( FALSE ) ;

}

// создать четыре дочерних окна for( iCount = 0; iCount < 4; iCount++ )

{

hwndChild[iCount] = CreateWindow( "ThreadClass", NULL, WS_BORDER | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0, hwndParent, NULL, hInst,

NULL ); if(! hwndChild ) return( FALSE );

}

return( TRUE );

}

3. Рассмотреть функции обработки сообщений. Большинство функций обработки сообщений очень просты. Функция Main_OnTimer вызывает функцию, которая очищает список, генерирует четыре новые информационные строки и выводит их в окне списка. Функция Main_OnSize приостанавливает все вторичные потоки, пока программа изменяет положение дочерних окон в соответствии с новыми размерами родительского окна. В противном случае работающие потоки будут замедлять выполнение операции отображения. Функция Main_OnCreate создает потоки, а также исключающий семафор.

/* MAIN_WNDPROC - обработка всех сообщений главного окна */

LRESULT WINAPI Main_WndProc( HWND hWnd,

// адрес сообщения

UINT uMessage,

// тип сообщения

WPARAM wParam,

// содержимое сообщения

LPARAM lParam ) // дополнительное содержимое

{

switch( uMessage )

7

{

HANDLE_MSG( hWnd, WM_CREATE, Main_OnCreate ); // Создание окна и потоков

HANDLE_MSG( hWnd, WM_SIZE, Main_OnSize );

//Согласование положения дочерних окон при изменении размеров

//главного окна

HANDLE_MSG( hWnd, WM_TIMER, Main_OnTimer ) ; // Обновление списка через каждые пять секунд

HANDLE_MSG( hWnd, WM_INITMENU, Main_OnInitMenu ) ;

//Если переменная bUseMutex равна TRUE, пометить пункт меню

//Use Mutex.

HANDLE_MSG( hWnd, WM_COMMAND, Main_OnCommand ); // Обработка команд меню

HANDLE_MSG( hWnd, WM_DESTROY, Main_OnDestroy ); // Очистка экрана и выход из программы

default:

return( DefWindowProc(hWnd, uMessage, wParam, lParam) );

}

return( 0L ) ;

}

В программе Threads используется макрокоманда обработки сообщений HANDLE_MSG, поэтому компилятор может выдать ряд предупреждений типа

"Unreferenced formal parameter" (Неопознанный формальный параметр). Во избежание этого в программу следует включить директиву argsused:

#ifdef _BORLANDC_ #pragma argsused #endif

Функция Main_OnCreate завершает процесс инициализации потоков и создает исключающий семафор:

/* MAIN_ONCREATE - создать четыре потока и установить таймер */ BOOL Main_OnCreate( HWND hWnd, LPCREATESTRUCT lpCreateStruct )

{

UINT uRet; int iCount;

// создание четырех потоков, приостановленных в исходном состоянии for( iCount = 0; iCount < 4; iCount++ )

{

iRectCount[iCount] = 0; dwThreadData[iCount] = iCount; hThread[iCount] = CreateThread( NULL, 0,

8

//не удалось создать исключающий семафор
KillTimer( hWnd, TIMER ); // остановка таймера return( FALSE ) ;
// создать таймер не удалось

(LPTHREAD_START_ROUTINE) StartThread, (LPVOID) ( & ( dwThreadData[iCount] ) ), CREATE_SUSPENDED, (LPDWORD) ( & (

dwThreadID(iCount] ) ) );

if( ! hThread[iCount] ) // Был ли поток создан?

{

return( FALSE ) ;

}

}

//создание таймера с пятисекундным периодом срабатывания;

//использование таймера для обновления списка

uRet = SetTimer( hWnd, TIMER, 5000, NULL ); if( ! uRet )

{

return( FALSE ) ;

}

// создание исключающего семафора hDrawMutex = CreateMutex( NULL, FALSE, NULL ); if( ! hDrawMutex )

{

}

//запуск потоков с приоритетом ниже стандартного for( iCount = 0; iCount < 4; iCount++ )

{

SetThreadPriority( hThread[iCount], THREAD_PRIORITY_BELOW_NORMAL );

iState[iCount] = ACTIVE; ResumeThread( hThread[iCount] );

}

//Теперь запущены все четыре потока! return( TRUE ) ;

}

Функция Main_OnSize не только изменяет размер окна приложения, но и корректирует размеры и положение всех дочерних окон.

/* MAIN_ONSIZE - Позиционирует окно списка и четыре дочерних окна. */ void Main_OnSize( HWND hWnd, UINT uState, int cxClient, int cyClient )

{

char* szText =-"No Thread Data"; int iCount;

//Приостанавливает активные потоки, пока не будет завершено

//изменение размеров и обновление соответствующих окон.

9

// Эта пауза значительно ускоряет процесс обновления содержимого экрана. for( iCount = 0; iCount < 4; iCount++ )

{

if ( iState[iCount] == ACTIVE ) SuspendThread ( hThread[iCount] );

}

//Размещает список в верхней четверти главного окна. MoveWindow( hwndList, 0, 0, cxClient, cyClient / 4, TRUE );

//Размещает четыре дочерних окна в трех нижних четвертях

//главного окна.

//Левая граница первого окна имеет координату 'х', равную 0. MoveWindow ( hwndChild[0], 0, cyClient /4-1, cxClient /4 + 1,

cyClient, TRUE );

for( iCount = 1; iCount < 4; iCount++ )

{

MoveWindow( hwndChild[iCount], (iCount * cxClient) / 4, cyClient /4-1, cxClient / 4 +1, cyClient, TRUE );

}

//Вводит в список строковые значения, заданные по

//умолчанию, и выполняет инициализацию.

//Начальное число прямоугольников равно 0.

for( iCount = 0; iCount < 4; iCount++ )

{

iRectCount[iCount] = 0; ListBox_AddString( hwndList, szText );

}

ListBox_SetCurSel(hwndList, 0);

//Возобновляет выполнение потоков, которые были приостановлены

//на время обновления окна.

for( iCount = 0; iCount < 4; iCount++ )

{

if( iState[iCount] == ACTIVE)

{

ResumeThread( hThread[iCount] );

}

}

return;

}

Обновление окна списка по сообщениям таймера - решение далеко не идеальное. Гораздо целесообразнее, чтобы этот процесс инициировался теми операциями, которые приводят к изменению исходных данных. Однако применение таймера можно считать простейшим способом выполнения поставленной задачи.

10

// модификация параметров потока; // включение или отключение // исключающего семафора
// установление белого цвета

/*MAIN_ONTIMER - Использует сообщение таймера для обновления списка. */ void Main_OnTimer( HWND hWnd, UINT uTimerID )

{

// Обновляет данные, представленные в списке

UpdateListBox() ; return;

}

Функция Main_OnlnitMenu просто выполняет установку или снятие отметки команды меню Use Mutex.

/* MAIN_ONINITMENU - Устанавливает или снимает отметку команды меню Use Mutex на основании значения переменной bUseMutex. */

void Main_OnInitMenu( HWND hWnd, HMENU hMenu )

{

CheckMenuItem ( hMenu, IDM_USEMUTEX, MF_BYCOMMAND | (UINT)( bUseMutex ? MF_CHECKED :

MF_UNCHECKED ) ) ;

return;

}

Функция Main_OnCommand объединяет все сообщения, для которых нет специальных обработчиков. (Обработчики сообщений группируются в функции Main_WndProc, которая с помощью макрокоманды HANDLE_MSG перенаправляет их на обработку соответствующим функциям.)

/* MAIN_ONCOMMAND - Реагирует на команды пользователя. */

void Main_OnCommand ( HWND hWnd, int iCmd, HWND hwndCtl, UINT uCode )

{

switch ( iCmd )

 

{

 

case IDM_ABOUT:

// вывод окна About

MakeAbout ( hWnd ) ;

 

break;

 

case IDM_EXIT:

// выход из программы

DestroyWindow( hWnd ) ;

 

break;

case IDM_SUSPEND: // изменение приоритета или состояния потока case IDM_RESUME:

case IDM_INCREASE: case IDM_DECREASE:

DoThread( iCmd ); case IDM_USEMUTEX:

ClearChildWindows( );

11

// для окон всех потоков bUseMutex = !bUseMutex; // переключение параметров

// исключающего семафора

default:

break;

}

return;

}

4. Рассмотреть функции изменения параметров. Функция DoThread в ответ на соответствующие команды меню изменяет параметры потока, выбранного в списке. Эта функция может повысить или понизить приоритет потока, а также приостановить или возобновить его выполнение. Текущее состояние каждого потока записывается в массив iState. В массиве hThreads сохраняются дескрипторы каждого из четырех вторичных потоков.

/*DO THREAD - Изменяет приоритет потока или его состояние в ответ на команды меню. */

void DoThread( int iCmd )

{

int iThread; int iPriority;

// Определяет, какой из потоков выбран. iThread = ListBox_GetCurSel ( hwndList ) ; switch ( iCmd )

{

case IDM_SUSPEND:

// Если поток не остановлен, останавливает его. if( iStatefiThread] != SUSPENDED )

{

Suspend/Thread ( hThread[iThread] ); iState[iThread] = SUSPENDED;

}

break;

case IDM_RESUME:

// Если поток не активен, активизирует его. if( iState[iThread] != ACTIVE )

{

ResumeThread( hThread(iThread] ); iState[iThread] = ACTIVE;

}

break;

case IDM_INCREASE:

// Повышает приоритет потока, если только он

12

// уже не является самым высоким iPriority = GetThreadPriority( hThread[iThread] ); switch( iPriority )

{

case THREAD_PRIORITY_LOWEST: SetThreadPriority( hThread[iThread],

THREAD_PRIORITY_BELOW_NORMAL ) ;

break;

case THREAD_PRIORITY_BELOW_NORMAL: SetThreadPriority( hThread[iThread],

THREAD_PRIORITY_NORMAL ) ;

break;

case THREAD_PRIORITY_NORMAL: SetThreadPriority( hThread[iThread],

THREAD_PRIORITY_ABOVE_NORMAL ) ;

break;

case THREAD_PRIORITY_ABOVE_NORMAL: SetThreadPriority( hThread[iThread],

THREAD_PRIORITY_HIGHEST ) ;

break;

default:

break;

}

break;

case IDM_DECREASE:

// Понижает приоритет потока, если только он //не является самым низким

iPriority = GetThreadPriority( hThread[iThread] ); switch( iPriority )

{

case THREAD_PRIORITY_BELOW_NORMAL: SetThreadPriorityt hThread[iThread],

THREAD_PRIORITY_LOWEST ) ;

break;

case THREAD_PRIORITY_NORMAL: SetThreadPriority (hThread[iThread],

THREAD_PRIORITY_BELOW_NORMAL ) ;

break;

case THREAD_PRIORITY_ABOVE_NORMAL: SetThreadPriority( hThread[iThread],

THREAD_PRIORITY_NORMAL ) ;

break;

case THREAD_PRIORITY_HIGHEST: SetThreadPriority( hThread[iThread],

13

THREAD_PRIORITY_ABOVE_NORMAL ) ;

break;

default:

break;

}

break;

default:

break;

}

return;

}

5. Рассмотреть функции для выполнения операций с потоками. Создав вторичные потоки, функция Main_OnCreate при каждом вызове функции CreateThread передает ей указатель на функцию StartThread. Последняя становится стартовой функцией для всех потоков. Она начинает и завершает их выполнение.

Если флаг bUseMutex имеет значение TRUE, потоки ожидают разрешения исключающего семафора, который обеспечивает одновременное выполнение графических операций только одним потоком.

/*START THREAD - Эта процедура вызывается на начальном этапе выполнения каждого потока. */

LONG StartThread ( LPVOID lpThreadData )

{

DWORD *pdwThreadID;

// указатель на переменную типа DWORD

 

// для записи идентификатора потока

DWORD dwWait;

// результирующее значение

 

// функции WaitForSingleObject

 

// получение идентификатора потока

pdwThreadID = lpThreadData;

//Выполнение графических операций до тех пор, пока

//значение флага bTerminate не станет равным TRUE. while( ! bTerminate )

{

if (bUseMutex)

// Используется ли исключающий семафор?

{

 

//Выполнение действий при получении разрешения

//от исключающего семафора.

dwWait = WaitForSingleObject( hDrawMutex, INFINITE ); if( dwWait == 0 )

{

DrawProc( *pdwThreadID ); // рисование

// прямоугольников

14

ReleaseMutex( hDrawMutex ); // разрешить выполнение

// других потоков

}

}

else

{

//Исключающий семафор не используется,

//разрешить выполнение потока. DrawProc( *pdwThreadID ) ;

}

}

// Этот оператор неявно вызывает команду ExitThread. return ( 0L ) ;

}

Функция DrawProc предназначена для рисования прямоугольников. Однако во избежание перегрузки системы из-за передачи большого количества мелких сообщений между программой и подсистемой Win32 обращение к GDI не всегда происходит сразу же. Поэтому графические команды становятся в очередь и периодически выполняются все одновременно. Такие задержки несколько преуменьшают влияние приоритетов потоков в программе Threads.

/*DRAW PROC - Рисует хаотически расположенные прямоугольники. */ void DrawProc ( DWORD dwID )

{

if (bUseMutex)

 

{

 

iTotal = 50;

// Если выполняется только один поток,

}

// разрешить ему рисовать большее число фигур.

else

 

{

 

iTotal = 1;

 

}

// сброс генератора случайных чисел

srand( iRandSeed++ );

 

// получение размеров окна bError = GetClientRect( hwndChild[dwID], &rcClient );

if( ! bError ) return;

cxClient = rcClient.right - rcClient.left; cyClierit = rcClient. bottom - rcClient.top;

//не рисовать, если не заданы размеры окна if( ( ! cxClient ) || ( ! cyClient ) )

{

15

return;

}

// получить контекст устройства для рисования hDC = GetDC( hwndCbild[dwID] );

if( hDC )

{ // нарисовать группу случайно расположенных фигур for( iCount = 0; iCount < iTotal; iCount++ )

{

iRectCount[dwID]++;

// задать координаты xStart = (int)( rand() % cxClient ); xStop = (int)( rand() % cxClient ); yStart = (int) ( rand() % cyClient ); yStop = (int)( rand() % cyClient );

// задать цвет iRed = rand() & 255;

iGreen = rand() &, 255; iBlue = rand() & 255;

// создать сплошную кисть

hBrush = CreateSolidBrush( // предотвратить появление

// полутоновых цветов

GetNearestColor( hDC, RGB( iRed, iGreen, iBIue ) )

);

hbrOld = SelectBrush( hDC, hBrush);

// нарисовать прямоугольник

Rectangle( hDC, min( xStart, xStop ), max ( xStart, xStop ), min( yStart, yStop ), max( yStart, yStop ) );

// удалить кисть

DeleteBrush( SelectBrush(hDC, hbrOld) );

}

//Если выполняется только один поток,

//очистить дочернее окно до начала выполнения

//другого потока. if( bUseMutex )

{

SelectBrush( hDC, GetStockBrush(WHITE_BRUSH) ); PatBlt( hDC, (int)rcClient.left, (int)rcClient.top,

(int)rcClient.right, (int)rcClient.bottom, PATCOPY ) ;

}

//освободить контекст устройства

ReleaseDC( hwndChild[dwID], hDC );

}

return;

}

16

Содержание отчета:

1.Цель работы;

2.Исходный текст программы;

3.Результаты работы программы (главное окно в ОС Windows);

4.Выводы по проделанной работе с указанием достоинств и недостатков предложенного исходного кода.

ЛАБОРАТОРНАЯ РАБОТА №2

"Выделение памяти"

Цель работы: Изучение принципов разработки программы, позволяющей резервировать и закреплять память (На примере программы List).

Задание к лабораторной работе:

1. Запустить программу List. Результат работы программы представлен на рис.2.1. В результате исполнения создается список, резервируется и закрепляется виртуальная память, когда пользователь вводит новые элементы списка. Список, заполняющий рабочую область окна, содержит перечень всех введенных элементов. Меню List позволяет добавлять и удалять элементы списка.

2. Рассмотреть исходный текст программы. В файле заголовков определены две константы, которые управляют размером и структурой списка. Для каждого элемента списка выделено 1024 символа. Поскольку размер системной страницы (4 Кб) кратен размеру элемента списка (1 Кб), ни один элемент не пересечет границу между страницами. Следовательно, для чтения любого элемента не потребуется загрузка с диска более одной страницы. В программе List максимальный размер массива составляет 500 элементов.

Среди глобальных переменных, определенных в программе, имеется два массива:

iListLookup и bInUse.

int iListLookup[MAX_ITEMS];

// согласует индекс элемента списка

// с его положением в массиве

BOOL bInUse[MAX_ITEMS];

// отмечает используемые элементы списка

Первый массив связывает строки списка с элементами динамического массива. Если элемент массива iListLookup[4] равен 7, строка с номером 5 из списка будет записана в элементе динамического массива с индексом 7. (Как в списке, так и в динамическом массиве нумерация элементов начинается с 0.)

17

int i;
// зарезервировать 1 Мб адресного пространства
pBase = VirtualAlloc( NULL, // начальный адрес (произвольный) MAX_ITEMS * ITEM_SIZE, // один мегабайт MEM_RESERVE, // зарезервировать;
//не закреплять
PAGE_NOACCESS ); // нет доступа if( pBase == NULL )
{
ShowErrorMsg( __LINE__ ) ; return( FALSE ) ;
18

Второй массив содержит значения типа Boolean для каждого элемента динамического массива. Когда программа добавляет или удаляет строки, она устанавливает для соответствующего элемента массива bInUse значение TRUE, если позиция занята, и FALSE, если она пуста. При добавлении строки программа ищет пустой элемент в массиве bInUse, а при удалении определяет позицию элемента массива, обращаясь к массиву iListLookup.

Рис 2.1 Окно, позволяющее добавлять новые элементы в список

При запуске программы осуществляется вызов функции CreateList, которая резервирует память и инициализирует вспомогательные структуры данных. Команда VirtualAlloc резервирует блок адресов размером 1 Мб. Подобно остальным зарезервированным страницам, адреса должны быть помечены флагом PAGE_NOACCESS, пока они не будут закреплены.

BOOL CreateList ( void )

{

}

// инициализация служебных массивов for ( i = 0; i < MAX_ITEMS; i++ )

{

bInUse[i] = FALSE;

// ни один из элементов

iListLookup[i] = 0;

// не используется

 

}

bListEmpty = TRUE; // обновить глобальные флаги bListFull = FALSE;

return ( TRUE ) ;

}

3. Добавить элемент списка. При выборе пункта Add Item в меню программы List функция AddItem выполняет следующие действия.

1.Находит первый свободный элемент в массиве (iIndex).

2.Предлагает пользователю ввести новую строку в диалоговом окне.

3.Копирует новую строку в блок памяти, выделенный при выполнении команды CreateList.

4.Обновляет список и несколько глобальных переменных.

Первый свободный элемент массива может занимать незакрепленную

страницу памяти. В этом случае команда lstrcpy, которая пытается записать новую строку, генерирует исключение. Обработчик исключений ожидает, когда поступит сигнал EXCEPTION_ACCESS_VIOLATIUN, и реагирует на него вызовом функции VirtualAlloc, которая закрепляет страницу из предварительно зарезервированного интервала.

void AddItem ( void )

 

{

 

char szText[ITEM_SIZE];

// текст для одного элемента

int iLen;

// длина строки

int iIndex;

// положение в массиве

int iPos;

// положение в списке

int iCount;

//счетчик элементов списка

BOOL bDone;

// TRUE, если найден свободный

//элемент массива

//определение положения первого свободного элемента bDone = FALSE;

iIndex = 0;

while( ( ! bDone ) && ( iIndex < MAX_ITEMS ) )

{

if( ! bInUse[iIndex] )

// используется ли данный элемент?

bDone = TRUE;

// обнаружен свободный элемент

19

else

iIndex++;

// переход к следующему элементу

}

// предложить пользователю ввести новую строку iLen = GetItemText(szText);

if( ! iLen ) return;

В блоке try новый текст копируется в свободную часть массива. Если соответствующая страница памяти не закреплена, функция lstrcpy порождает исключение. Фильтр исключений закрепляет страницу, и выполнение функции продолжается.

try

{// записать текст в элемент массива

lstrcpy( &( pBase[iIndex * ITEM_SIZE) ), szText );

}

except( CommitMemFilter( GetExceptionCode(), iIndex ) )

{

// вся работа выполняется фильтром исключений

}

// пометить данный элемент как занятый bInUse[iIndex] = TRUE;

bListEmpty = FALSE;

Далее программа добавляет новый текст в список. Строка вставляется в позицию, которая задана переменной iPos. При этом обновляется элемент iListLookup [ipos], который указывает, где в массиве записана новая строка

(iIndex).

iCount = ListBox_GetCount( hwndList ) ;

iPos = ListBox_InsertString( hwndList, iCount, szText ); iCount++;

ListBox_SetCurSel( hwndList, iPos ); iListLookup[iPos] = iIndex;

if (iCount == MAX_ITEMS) // заполнена ли последняя позиция?

{

bListFull= TRUE;

}

return;

}

Функция CommitMemFilter представляет собой фильтр обработки исключений для функции AddItem. При наличии страничной ошибки функция

20

CommitMemFilter пытается закрепить страницу и, если ее старания приводят к успеху, продолжает выполнение команды lstrcopy. Если функция CommitMemFilter не справляется с задачей, поиск соответствующего обработчика исключения продолжается.

LONG CommitMemFilter (

DWORD dwExceptCode, // код, идентифицирующий исключение int iIndex ) // элемент массива, в котором произошла ошибка

{

LPVOID lpvResult;

//Если исключение не является страничной ошибкой,

//отказаться обрабатывать его и поручить системе

//поиск соответствующего обработчика исключений. if( dwExceptCode != EXCEPTION_ACCESS_VIOLATION )

{

return( EXCEPTION_CONTINUE_SEARCH ) ;

}

//Попытка закрепить страницу.

lpvResult = VirtualAlloc(

&( pBase(iIndex * ITEM_SIZE] ), // нижняя граница закрепляемой области

ITEM_SIZE,

 

// размер закрепляемой области

MEM_COMMIT,

 

// новый флаг состояния

PAGE_READWRITE );

// уровень защиты

if( ! lpvResult )

// произошла ли ошибка выделения памяти?

{

 

 

// Если мы не можем закрепить страницу, то не сможем обработать исключение

return( EXCEPTION_CONTINUE_SEARCH );

}

//Недостающая страница теперь находится на своем месте.

//Система должна вернуться назад и повторить попытку. return( EXCEPTION_CONTINUE_EXECUTION ) ;

}

4. Удалить элемент списка. Функция DeleteItem удаляет элемент из виртуального массива, она проверяет, не осталось ли других элементов в этой странице памяти. Если все четыре элемента, которые находятся на странице, пусты, функция отменяет закрепление страницы, передавая дополнительные 4 Кб памяти в распоряжение системы. Виртуальные адреса страницы остаются зарезервированными. Независимо от того, освобождает команда DeleteItem страницу или нет, она удаляет строку из списка и обновляет глобальные переменные состояния.

21

Выполнению этих операций способствуют две дополнительные функции. Функция GetPageBaseEntry получает в качестве аргумента индекс массива и возвращает индекс первого элемента соответствующего страничного блока. Иными словами, эта функция производит округление до ближайшего меньшего значения, кратного четырем. Функция AdjustLookupTable удаляет запись, выделенную в списке, и смещает все последующие элементы для заполнения образовавшейся пустоты.

void DeleteItem ( void )

{

 

int iCurSel;

// позиция текущей выбранной строки в списке

int iPlace;

// позиция текущей выбранной строки в массиве элементов

int iStart;

// позиция первого элемента в той странице памяти,

 

// где находится выбранный элемент

int i;

// переменная цикла

BOOL bFree;

// TRUE, если все 4 элемента, содержащиеся в данной

 

// странице памяти, не используются

BOOL bTest;

// для проверки результатов

// определяет смещение в памяти текущей выбранной записи iCurSel = ListBox_GetCurSel( hwndList ) ;

iPLace = iListLookup [iCurSel] ; // обнуляет удаленный элемент

FillMemory( & (pBase [iPlace * ITEM_SIZE] ) , ITEM_SIZE, 0 ); // помечает этот элемент как свободный

bInUse[iPlace] = FALSE;

На данном этапе определяется номер первого элемента в текущей странице памяти. Если все четыре элемента, которые находятся на данной странице, свободны, закрепление страницы отменяется.

iStart = GetPageBaseEntry( iPlace ) ; bFree = TRUE;

for( i = 0; i < 4; i++ ) // проверка четырех записей

{

if( bInUse[i + iStart] )

// используется?

{

 

bFree = FALSE;

// страница занята

}

 

}

Если не используется вся страница памяти, она освобождается.

if( bFree )

// свободна ли страница?

{

// ДА; освободить ее

22

bTest = VirtualFree( &( pBase[iStart * ITEM_SIZE] ), ITEM_SIZE, MEM_DECOMMIT ) ;

if( ! bTest )

{

ShowErrorMsg( __LINE__ ); ExitProcess( (UINT)GetLastError() );

}

}

Далее обновляеся список и массив связей и проверяется, остались ли еще элементы в списке.

ListBox_DeleteString( hwndList, iCurSel ); AdjustLookupTable( iCurSel ); bListEmpty =TRUE;

i = 0;

while( ( i < MAX_ITEMS ) && ( bListEmpty ) )

{

// если элемент используется, значит, список не пустой bListEmpty = !bInUse[i++] ;

}

// изменить положение маркера выделения в списке if(! bListEmpty )

{

if( iCurSel )

// удален ли первый элемент списка?

{

// нет; выбрать элемент над удаленной

 

// записью

ListBox_SetCurSel( hwndList, iCurSel-1 );

}

 

else

// удаленная запись была самой верхней

 

// в списке;

{

// выбрать новую верхнюю запись

ListBox_SetCurSel ( hwndList, iCurSel );

}

}

return;

}

Когда программа удаляет все элементы списка, вызывается функция DeleteList, которая освобождает память, прежде занятую записями.

23

void DeleteList()

{

//отменить закрепление памяти и освободить адресное пространство

//Для операции MEM_DECOMMIT необходимо указать размер области if( ! VirtualFree( (void*) pBase, MAX_ITEMS*ITEM_SIZE,

МЕМ_DECOMMIT)) ShowErrorMsg ( __LINE__ ) ;

//освободить память, начиная с базового адреса;

//указывать размер не требуется

if( ! VirtualFree( (void*) pBase, 0, MEM_RELEASE ) ) ShowErrorMsg( __LINE__ );

return;

}

По индексу в списке функция GetPageBaseEntry определяет первый элемент соответствующей страницы памяти, округляя индекс до ближайшего значения, кратного четырем и меньшего или равного iPlace.

int GetPageBaseEntry ( int iPlace )

{

while( iPlace % 4 )

{

iPlace--;

}

return( iPlace ) ;

}

После удаления записи из списка необходимо обновить массив, который связывает элементы списка со значениями смещения в памяти. Параметр iStart задает позицию строки, удаленной из списка.

void AdjustLookupTable ( int iStart )

{

int i;

//Этот цикл начинается с позиции, из которой только что

//была удалена запись. Все последующие элементы смещаются

//таким образом, чтобы заполнить образовавшееся свободное место. for( i = iStart; i < MAX_ITEMS - 1; i++ )

{

iListLookup[i] = iListLookup[i + 1];

}

iListLookup[MAX_ITEMS - 1] = 0;

24