Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Приложение HEAPMEM

На примере приложения HEAPMEM мы покажем вам, как можно использовать функции, предназначенные для работы с пулами памяти.

В отличие от предыдущего приложения, приложение HEAPMEM работает в так называемом консольном (или текстовом) режиме. Такое приложение может пользоваться стандартными функциями консольного ввода/вывода из библиотеки C++. Для него система создает отдельное окно (рис. 1.19).

Рис. 1.19. Окно консольного приложения HEAPMEM

Что делает наше приложение?

Приложение HEAPMEM тремя различными способами решает одну и ту же задачу: получение небольшого блока памяти, запись в нее текстовой строки и отображение этой строки в консольном окне, показанном на рис. 1.19.

Первый способ предполагает использование динамического пула памяти и обработку исключений. В приложении намеренно создаются две ситуации, в которых происходят исключения с кодами C0000017 и C0000005. Во второй раз приложение работает со стандартным пулом памяти и не обрабатыает исключения, проверяя код завершения функций. И, наконец, третий способ связан с использованием функций malloc XE "malloc" и free XE "free" .

Исходный текст приложения

Исходный текст приложения HEAPMEM представлен в листинге 1.5. Файлы описания ресурсов и определения модуля не используются.

Листинг 1.5. Файл heapmem/heapmem.c


#include <windows.h>
#include <stdio.h>
#include <conio.h>

int main()
{
  // Идентификатор динамического пула
  HANDLE hHeap;

  // Указатель, в который будет записан адрес
  // полученного блока памяти
  char *lpszBuff;

  // =================================================
  // Работа с динамическим пулом
  // Используем структурную обработку исключений
  // =================================================
    
  // Создаем динамический пул
  hHeap = HeapCreate(0, 0x1000, 0x2000);

  // Если произошла ошибка, выводим ее код и
  // завершаем работу приложения
  if(hHeap == NULL)
  {
    fprintf(stdout,"HeapCreate: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Пытаемся получить из пула блок памяти
  __try
  {
    lpszBuff = (char*)HeapAlloc(hHeap, 
      HEAP_GENERATE_EXCEPTIONS, 0x1500);
  }
  
  // Если память недоступна, происходит исключение,
  // которое мы обрабатываем
  __except (EXCEPTION_EXECUTE_HANDLER) 
  {
    fprintf(stdout,"1. HeapAlloc: Exception %lX\n", 
      GetExceptionCode());
  }

  // Пытаемся записать в буфер текстовую строку
  __try
  {
    strcpy(lpszBuff, "Строка для проверки");
  }
  
  // Если содержимое указателя lpszBuff равно NULL,
  // произойдет исключение
  __except (EXCEPTION_EXECUTE_HANDLER) 
  {
    fprintf(stdout,"1. strcpy: Exception %lX \n", 
      GetExceptionCode());
  }

  // Выполняем повторную попытку, указывая меньший
  // размер блока памяти
  __try
  {
    lpszBuff = (char*)HeapAlloc(hHeap, 
      HEAP_GENERATE_EXCEPTIONS, 0x100);
  }
  __except (EXCEPTION_EXECUTE_HANDLER) 
  {
    fprintf(stdout,"2. HeapAlloc: Exception %lX\n", 
      GetExceptionCode());
  }

  __try
  {
    strcpy(lpszBuff, "Test string");
  }
  __except (EXCEPTION_EXECUTE_HANDLER) 
  {
    fprintf(stdout,"2. strcpy: Exception %lX \n", 
      GetExceptionCode());
  }

  // Отображаем записанную строку
  if(lpszBuff != NULL)
    printf("String:>%s<\n", lpszBuff);

  // Изменяем размер блока памяти
  __try
  {
    HeapReAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS | 
      HEAP_REALLOC_IN_PLACE_ONLY, lpszBuff, 150);
  }
  __except (EXCEPTION_EXECUTE_HANDLER) 
  {
    fprintf(stdout,"HeapReAlloc: Exception %lX \n", 
      GetExceptionCode());
  }

  // Освобождаем блок памяти
  if(lpszBuff != NULL)
    HeapFree(hHeap, HEAP_NO_SERIALIZE, lpszBuff);
  
  // Удаляем пул памяти
  if(!HeapDestroy(hHeap))
    fprintf(stdout,"Ошибка %ld при удалении пула\n", 
      GetLastError());
  
  // =================================================
  // Работа со стандартным пулом
  // Исключения не обрабатываем
  // =================================================

  // Получаем блок памяти из стандартного пула
  lpszBuff = (char*)HeapAlloc(GetProcessHeap(), 
    HEAP_ZERO_MEMORY, 0x1000);
  
  // Если памяти нет, выводим сообщение об ошибке
  // и завершаем работу программы
  if(lpszBuff == NULL)
  {
    fprintf(stdout,"3. HeapAlloc: Error %ld\n", 
      GetLastError());

    getch();
    return 0;
  }

  // Выполняем копирование строки
  strcpy(lpszBuff, "Test string");
  
  // Отображаем скопированную строку
  printf("String:>%s<\n", lpszBuff);

  // Освобождаем блок памяти
  if(lpszBuff != NULL)
    HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, lpszBuff);

  // =================================================
  // Работа со стандартными функциями
  // Исключения не обрабатываем
  // =================================================
  
  lpszBuff = malloc(1000);

  if(lpszBuff != NULL)
  {
    strcpy(lpszBuff, "Test string");
    printf("String:>%s<\n", lpszBuff);
    free(lpszBuff);
  }

  printf("Press any key...");
  getch();
  return 0;
}

В отличие от обычного приложения Microsoft Windows NT, исходный текст консольного приложения должен содержать функцию main (аналогично программе MS-DOS). В теле этой функции мы определили переменные hHeap и lpszBuff. Первая из них используется для хранения идентификатора динамического пула памяти, вторая - для хранения указателя на полученный блок памяти.

Работа с динамическим пулом памяти

Вначале наше приложение создает динамический пул памяти, вызывая для этого функцию HeapCreate XE "HeapCreate" . Для пула резервируется 2 Кбайта памяти, причем для непосредственного использования выделяется только 1 Кбайт.

При возникновении ошибки ее код определяется с помощью функции GetLastError XE "GetLastError" и отображается в консольном окне хорошо знакомой вам из MS-DOS функцией fprintf. Затем работа приложения завершается.

Заметим, что многие (но не все) функции программного интерфейса Microsoft Windows NT в случае возникновения ошибки перед возвращением управления устанавливают код ошибки, вызывая для этого функцию SetLastError XE "SetLastError" . При необходимости приложение может извлечь этот код сразу после вызова функции, как это показано в нашем приложении.

Далее приложение пытается получить блок памяти размером 0x1500 байт, вызывая функцию HeapAlloc XE "HeapAlloc" :


__try
{
  lpszBuff = (char*)HeapAlloc(hHeap, 
    HEAP_GENERATE_EXCEPTIONS, 0x1500);
}

Так как во втором параметре мы передали этой функции значение HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" , в случае ошибки возникнет исключение. Поэтому вызов функции HeapAlloc XE "HeapAlloc" выполняется с обработкой исключений. Соответствующий обработчик получает код исключения при помощи функции GetExceptionCode XE "GetExceptionCode" и отображает его в консольном окне.

В нашем приложении мы пытаемся получить больше памяти, чем доступно, поэтому исключение действительно произойдет.

На следующем шаге, невзирая на исключение, наше приложение пытается записать в блок памяти, указатель на который находится в переменной lpszBuff, текстовую строку:


__try
{
  strcpy(lpszBuff, "Строка для проверки");
}

Так как при получении блока памяти произошло исключение, в указателе lpszBuff находится неправильный адрес. Это, в свою очередь, приведет к возникновению исключения при попытке записи строки. Поэтому на рис. 1.19 в верхней части консольного окна находятся два сообщения об исключениях.

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

Затем приложение пытается изменить размер полученного блока памяти, вызывая функцию HeapReAlloc XE "HeapReAlloc" :


__try
{
  HeapReAlloc(hHeap, HEAP_GENERATE_EXCEPTIONS | 
    HEAP_REALLOC_IN_PLACE_ONLY, lpszBuff, 150);
}

Так как указан флаг HEAP_REALLOC_IN_PLACE_ONLY XE "HEAP_REALLOC_IN_PLACE_ONLY" , при изменении размера блок не будет перемещен, поэтому мы игнорируем значение, возвращаемое функцией HeapReAlloc XE "HeapReAlloc" .

А что произойдет, если размер блока увеличится настолько, что он не поместится в адресном пространстве, отведенном для него ранее?

Мы указали флаг HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" , поэтому в этом случае произойдет исключение, которое наше приложение обработает.

После изменения размера блока памяти приложение освобождает его функцией HeapFree XE "HeapFree" , а затем удаляет динамический пул памяти, так как мы больше не будем с ним работать.

Работа со стандартным пулом памяти

Второй способ выделения блока памяти основан на использовании стандартного пула. Для получения памяти из стандартного пула мы пользуемся функцией HeapAlloc XE "HeapAlloc" , передавая ей в качестве первого параметра значение идентификатора стандартного пула памяти, полученное от функции GetProcessHeap XE "GetProcessHeap" :


lpszBuff = (char*)HeapAlloc(GetProcessHeap(), 
  HEAP_ZERO_MEMORY, 0x1000);

Так как мы указали флаг HEAP_ZERO_MEMORY XE "HEAP_ZERO_MEMORY" , полученный блок памяти будет расписан нулями. Флаг HEAP_GENERATE_EXCEPTIONS XE "HEAP_GENERATE_EXCEPTIONS" не указан, поэтому после вызова функции мы должны проверить значение, полученное от нее.

На следующем этапе приложение выполняет копирование строки в блок памяти и отображение ее в консольном окне:


strcpy(lpszBuff, "Test string");
printf("String:>%s<\n", lpszBuff);

Так как исключения не обрабатываются, при их возникновении работа приложения завершится аварийно.

После использования приложение освобождает блок памяти, полученный из стандартного пула, для чего вызывается функция HeapFree XE "HeapFree" :


HeapFree(GetProcessHeap(), HEAP_NO_SERIALIZE, lpszBuff);

Последний фрагмент приложения демонстрирует использование функций malloc XE "malloc" и free XE "free" для работы со стандартным пулом памяти и в комментариях не нуждается.

[Назад] [Содеожание] [Дальше]