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

Операционная система Microsoft Windows 3.1 для программиста

© Александр Фролов, Григорий Фролов
Том 13, М.: Диалог-МИФИ, 1993, 284 стр.

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

3.5. Приложение DISCARD

В предыдущей главе мы приводили исходные тексты приложений GMEM и LMEM, которые, кроме всего прочего, демонстрировали приемы работы с удаляемой памятью. В этих приложениях мы заказывали удаляемые блоки памяти из, соответственно, глобальной и локальной области памяти, удаляли их а затем пытались зафиксировать, чтобы получить их линейный адрес. Так как блоки памяти были удалены, нам приходилось их восстанавливать.

Мы отметили, что приложение может создать собственную функцию извещения, которая будет получать управление при попытке Windows удалить глобальные блоки памяти, заказанные как удаляемые (если для них определен флаг GMEM_NOTIFY). Однако, как мы уже говорили, функция извещения должна располагаться в DLL-библиотеке, причем в фиксированном сегменте кода, так как она может быть вызвана из любого контекста, а не только из контекста вашего приложения.

Теперь, когда мы умеем создавать свои DLL-библиотеки, мы можем испытать работу механизма оповещения о попытке удаления глобального блока памяти .

Приложение DISCARD (листинг 3.5), сделанное на базе приложения GMEM, заказывает удаляемый блок памяти и назначает собственную процедуру извещения об удалении глобальных блоков памяти. Процедура размещена в DLL-библиотеке dll.dll, исходные тексты которой мы также приведем.


Листинг 3.5. Файл discard/discard.cpp


#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <dos.h>

// Прототип функции извещения о том, что Windows
// планирует удалить блок памяти
// Так как функция NotifyProc составлена на языке
// программирования C (а не C++), мы описываем ее
// как extern "C"   
extern "C" BOOL CALLBACK _export NotifyProc(HGLOBAL hglbl);

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR     lpszCmdLine, int nCmdShow)
{
  BYTE szBuf[100];
  HGLOBAL hmemGlDiscard;
  LPVOID  lpvoidGlobal;
  LPVOID  lpvoidGlDiscard;
  DWORD   dwMaxFreeMem;

  // Определяем размер доступной памяти
  dwMaxFreeMem = GlobalCompact(-1l);

  wsprintf(szBuf, "Доступно памяти:\t%lu\n",
        dwMaxFreeMem);
  MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);

  // Устанавливаем процедуру извещения, которая
  // получит управление при попытке удалить
  // блок памяти 
  GlobalNotify((GNOTIFYPROC)NotifyProc);

  // Заказываем удаляемый блок памяти размером 200000 байт
  // Для включения режима извещения необходимо
  // указать флаг GMEM_NOTIFY
  hmemGlDiscard =
      GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE 
       | GMEM_NOTIFY, 200000l);

  if(hmemGlDiscard != NULL)
  {
    // Если мы его получили, пытаемся удалить блок
    GlobalDiscard(hmemGlDiscard);

    // Фиксируем блок памяти
    lpvoidGlDiscard = GlobalLock(hmemGlDiscard);

    if(lpvoidGlDiscard != (LPVOID) NULL)
    {
      // Так как наша процедура извещения запрещает
      // удаление блока, попытка его фиксирования
      // должна закончится успешно. В этом случае
      // мы выводим идентификатор блока памяти и его
      // логический адрес
      wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n"
        "lpvoidGlDiscard=\t%04.4X:%04.4X",
        hmemGlDiscard,
        FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard));
      MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK);

      // Разрешаем перемещение блока
      GlobalUnlock(hmemGlDiscard);
    }
    else
    {
      // Если блок памяти не удалось зафиксировать,
      // проверяем, не был ли он удален
      if(GlobalFlags(hmemGlDiscard) & GMEM_DISCARDED)
      {
         // Так как мы запретили удаление блока, следующее
         // сообщение не должно появиться на экране
         MessageBox(NULL, "Блок удален и мы его"
           " восстанавливаем",
           "Global Block", MB_OK);

         // Восстанавливаем удаленный блок памяти
         hmemGlDiscard = GlobalReAlloc(hmemGlDiscard,
           200000l,
           GMEM_MOVEABLE | GMEM_DISCARDABLE);

         // Фиксируем блок памяти
         lpvoidGlDiscard = GlobalLock(hmemGlDiscard);

         if(lpvoidGlDiscard != (LPVOID) NULL)
         {
           // Выводим идентификатор и логический адрес
           // зафиксированного блока памяти
           wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n"
             "lpvoidGlDiscard=\t%04.4X:%04.4X",
             hmemGlDiscard,
             FP_SEG(lpvoidGlDiscard),
             FP_OFF(lpvoidGlDiscard));
           MessageBox(NULL, (LPSTR)szBuf, "Global Block",
             MB_OK);

           // Освобождаем блок памяти
           GlobalUnlock(hmemGlDiscard);
         }
         else
         {
           MessageBox(NULL, "Ошибка при фиксировании блока",
           "Global Block", MB_OK);
         }
      }
    }

    // Отдаем удаляемый блок памяти операционной системе
    GlobalFree(hmemGlDiscard);
  }
  else
  {
    MessageBox(NULL, "Мало памяти для удаляемого блока",
      "Global Block", MB_OK);
  }
  return 0;
}

DLL-библиотека, в которой расположена функция извещения, составлена нами с использованием языка программирования C, а не C++. Это сделано для упрощения экспортирования функции, которая в данном случае экспортируется с использованием своего порядкового номера. Подробнее этот вопрос мы рассмотрим при описании исходного текста DLL-библиотеки, а пока заметим, что так как для функции извещения использован язык C, мы должны это отметить в прототипе функции, включаемой в исходный текст приложения, составленного на языке C++:

extern "C" BOOL CALLBACK _export NotifyProc(HGLOBAL hglbl);

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

После этого функция WinMain устанавливает процедуру извещения, вызывая функцию GlobalNotify и передавая ей в качестве параметра адрес внешней по отношению к приложению функции извещения NotifyProc, расположенной в DLL-библиотеке:

GlobalNotify((GNOTIFYPROC)NotifyProc);

Каждая копия приложения может вызывать функцию GlobalNotify только один раз.

Задача функции извещения заключается в том, чтобы предотвратить удаление блока памяти. Это сделать очень просто, достаточно того, чтобы функция извещения вернула нулевое значение.

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

hmemGlDiscard =
  GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE 
  | GMEM_NOTIFY, 200000l);

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

После получения блока памяти приложение пытается его удалить, вызывая функцию GlobalDiscard (можно было бы, конечно, подождать, пока этот блок будет удален самой операционной системой Windows, однако вы можете прождать до утра):

GlobalDiscard(hmemGlDiscard);

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

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

Файл определения модуля приложения DISCARD приведен в листинге 3.6.


Листинг 3.6. Файл discard/discard.def


; =============================
; Файл определения модуля
; =============================
NAME        DISCARD
DESCRIPTION 'Приложение DISCARD, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Исходный текст DLL-библиотеки приведен в листинге 3.7.


Листинг 3.7. Файл discard/dll.c


#define  STRICT
#include <windows.h>

// ========================================================
// Функция LibMain
// Получает управление только один раз при
// загрузке DLL-библиотеки в память
// ========================================================

#pragma argsused
int FAR PASCAL LibMain(HINSTANCE hInstance,
                       WORD wDataSegment,
                       WORD wHeapSize,
                       LPSTR lpszCmdLine)
{
  // После инициализации локальной области данных
  // функция LibEntry фиксирует сегмент данных.
  // Его необходимо расфиксировать.
  if(wHeapSize != 0)
    // Расфиксируем сегмент данных
    UnlockData(0);

  // Возвращаем 1. Это означает, что инициализация
  // DLL-библиотеки выполнена успешно
  return 1;
}

// ========================================================
// Функция WEP
// Получает управление только один раз при
// удалении DLL-библиотеки из памяти
// ========================================================

#pragma argsused
int FAR PASCAL WEP(int bSystemExit)
{
  return 1;
}

// ========================================================
// Функция NotifyProc
// Она получает управление, если в текущей задаче
// предпринимается попытка удаления блока памяти.
// В этом случае функция возвращает значение 0,
// в результате чего Windows отменяет удаление блока
// ========================================================

#pragma argsused
BOOL FAR PASCAL _export NotifyProc(HGLOBAL hglbl)
{
  return 0;
}

Эта библиотека исключительно проста, так как помимо стандартных функций LibMain и WEP в ней определена только одна функция извещения NotifyProc.

Изучение последней также не вызовет ни малейшего затруднения. Функция NotifyProc возвращает значение 0, запрещая Windows удалять блок памяти. Если надо разрешить удаление блока памяти, необходимо вернуть значение 1.

Обратим внимание на файл определения модуля DLL-библиотеки, приведенный в листинге 3.8.


Листинг 3.8. Файл discard/dll.def


; =============================
; Файл определения модуля
; =============================
LIBRARY        DLL
DESCRIPTION    'DLL-библиотека DLL, (C) 1994, Frolov A.V.'
EXETYPE        windows
CODE           preload fixed
DATA           preload moveable single
HEAPSIZE       1024
EXPORTS
  NotifyProc @10

Исходя из требований функции извещения, сегмент кода DLL-библиотеки сделан фиксированным. Кроме того, мы использовали оператор EXPORTS для экспортирования функции извещения и задали для этой функции порядковый номер.

Теперь о том, почему для создания DLL-библиотеки мы выбрали язык C, а не С++.

Как вы знаете, трансляторы языка C++ "разукрашивают" имена функций в объектном модуле, добавляя к ним символы, обозначающие типы аргументов и тип возвращаемого значения. Когда в файле определения модуля в разделе EXPORTS мы перечисляем имена функций, при сборке загрузочного файла DLL-библиотеки редактор связей будет искать в объектном модуле функции с перечисленными именами. Если же исходный текст составлен на языке C++, имена, расположенные в объектном модуле, будут мало напоминать использованные в исходном тексте. В результате редактор связей выведет сообщение о том, что он не может найти в объектном модуле указанные в файле определения модуля экспортируемые функции.

Если же исходный текст DLL-библиотеки составлен на языке C, эти проблемы исчезнут. Однако в этом случае при вызове таких экспортируемых функций из приложений, составленных на языке C++, вам придется объявить их как extern "C".

Если же для разработки DLL-библиотеки используется язык C++, для обеспечения доступа к экспортируемым функциям вы можете либо использовать библиотеку импорта, созданную приложением IMPLIB, либо описать экспортируемую функцию следующим образом (в качестве примера использован исходный текст одной из функций DLL-библиотеки, описанной в разделе "Приложение WINHOOK"):

extern "C"
void WINAPI _export RemoveKbHook(void)
{
  if(bHooked)
  {
    UnhookWindowsHookEx(hhookMsg);
    UnhookWindowsHookEx(hhook);
  }
}

Описание extern "C" отменяет для определяемой функции соглашение языка C++ об именах функций.

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