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

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

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

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

Обмен через файлы, отображаемые на память

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

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

Напомним, что отображение создается функцией CreateFileMapping.

Вот фрагмент кода из приложения Oem2Char, в котором создается отображение файла, а затем выполняется отображение этого файла в память:


hFileMapping = CreateFileMapping(hSrcFile,
  NULL, PAGE_READWRITE, 0, dwFileSize, NULL);
if(hFileMapping == NULL)
  return;

lpFileMap = MapViewOfFile(hFileMapping, 
  FILE_MAP_WRITE, 0, 0, 0);
if(lpFileMap == 0)
  return;

Здесь в качестве первого параметра для функции CreateFileMapping мы передаем идентификатор файла, открытого функцией CreateFile. Последний параметр указан как NULL, поэтому отображение не имеет имени.

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

Другое замечание касается идентификатора файла, передаваемого функции CreateFileMapping через первый параметр. Если вы создаете отображение только для того чтобы обеспечить передачу данных между процессами, вам не нужно создавать файл на диске компьютера. Указав в качестве идентификатора файла значение (HANDLE)0xFFFFFFFF, вы создадите отображение непосредственно в виртуальной памяти без использования дополнительного файла.

Ниже мы привели фрагмент кода, в котором создается отображение с именем $MyVerySpecialFileShareName$, причем это отображение создается в виртуальной памяти:


CHAR lpFileShareName[] = 
  "$MyVerySpecialFileShareName$";
hFileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF,
  NULL, PAGE_READWRITE, 0, 100, lpFileShareName);

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

Итак, первый процесс создал отображение. Второй процесс, который будет выполнять обмен данными с первым процессом, должен открыть это отображение по имени при помощи функции OpenFileMapping, например, так:


hFileMapping = OpenFileMapping(
  FILE_MAP_READ | FILE_MAP_WRITE, FALSE, lpFileShareName);

Далее второе приложение выполняет отображение, вызывая функцию MapViewOfFile:


lpFileMap = MapViewOfFile(hFileMapping, 
  FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

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

Перед завершением своей работы процессы должны отменить отображение файла и освободить идентификатор созданного объекта-отображения:


UnmapViewOfFile(lpFileMap);
CloseHandle(hFileMapping);

Приложение Fmap/Server

Для иллюстрации методики обмена данными мжеду различными процессами с использованием файлов, отображаемых на память, мы подготовили исходные тексты двух консольных приложений: Fmap/Server и Fmap/Client. Эти приложения работают в паре (рис. 2.1).

Рис. 2.1. Взаимодействие консольных приложений Fmap/Server и Fmap/Client

Приложение Fmap/Server создает отображение и два объекта-события. Первый объект предназначен для работы с клавиатурой, второй - для обнаружения момента завершения приложения Fmap/Client. Объекты-события были описаны нами в предыдущем томе “Библиотеки системного программиста”, посвященном программированию для операционной системы Microsoft Windows NT.

Приложение Fmap/Client открывает созданное отображение и объекты-события, а затем в цикле вводит символы с клавиатуры, переключая один из объектов-событий в отмеченное состояние при вводе каждого символа. Коды введенных символов записываются в отображенную память.

По мере того как пользователь вводит символы в окне приложения Fmap/Client, приложение Fmap/Server отображает их в своем окне, получая коды введенных символов из отображенной памяти. Для синхронизации используется объект-событие, выделенное для работы с клавиатурой.

Если пользователь нажимает клавишу <Esc> в окне приложения Fmap/Client, это приложение отмечает оба события и завершает свою работу. Приложение Fmap/Server, обнаружив, что второй объект-событие оказался в отмеченном состоянии, также завершает свою работу. Таким образом, если завершить работу приложения Fmap/Client, то приложение Fmap/Server также будет завершено.

Исходный текст приложения Fmap/Server представлен в листинге 2.1.

Листинг 2.1. Файл fmap/server/server.c


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

// Идентификаторы объектов-событий, которые используются
// для синхронизации задач, принадлежащих разным процессам
HANDLE hEventChar;
HANDLE hEventTermination;
HANDLE hEvents[2];

// Имя объекта-события для синхронизации ввода и отображения
CHAR lpEventName[] = 
  "$MyVerySpecialEventName$";

// Имя объекта-события для завершения процесса
CHAR lpEventTerminationName[] = 
  "$MyVerySpecialEventTerminationName$";

// Имя отображния файла на память
CHAR lpFileShareName[] = 
  "$MyVerySpecialFileShareName$";

// Идентификатор отображения файла на память
HANDLE hFileMapping;

// Указатель на отображенную область памяти
LPVOID lpFileMap;

int main()
{
  DWORD dwRetCode;

  printf("Mapped and shared file, server process\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");
  
  // Создаем объект-событие для синхронизации 
  // ввода и отображения, выполняемого в разных процессах
  hEventChar = CreateEvent(NULL, FALSE, FALSE, lpEventName);
  
  // Если произошла ошибка, получаем и отображаем ее код,
  // а затем завершаем работу приложения
  if(hEventChar == NULL)
  {
    fprintf(stdout,"CreateEvent: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Если объект-событие с указанным именем существует,
  // считаем, что приложение EVENT уже было запущено
  if(GetLastError() == ERROR_ALREADY_EXISTS)
  {
    printf("\nApplication EVENT already started\n"
      "Press any key to exit...");
    getch();
    return 0;
  }

  // Создаем объект-событие для определения момента
  // завершения работы процесса ввода
  hEventTermination = CreateEvent(NULL, 
    FALSE, FALSE, lpEventTerminationName);

  if(hEventTermination == NULL)
  {
    fprintf(stdout,"CreateEvent (Termination): Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }
  
  // Создаем объект-отображение
  hFileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF,
    NULL, PAGE_READWRITE, 0, 100, lpFileShareName);

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

  // Выполняем отображение файла на память.
  // В переменную lpFileMap будет записан указатель на
  // отображаемую область памяти
  lpFileMap = MapViewOfFile(hFileMapping, 
    FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

  // Если выполнить отображение не удалось,
  // выводим код ошибки
  if(lpFileMap == 0)
  {
    fprintf(stdout,"MapViewOfFile: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }
  
  // Готовим массив идентификаторов событий
  // для функции WaitForMultipleObjects
  hEvents[0] = hEventTermination;
  hEvents[1] = hEventChar;
  
  // Цикл отображения. Этот цикл завершает свою работу
  // при завершении процесса ввода
  while(TRUE)
  {
    // Выполняем ожидание одного из двух событий:
    //   - завершение клиентского процесса;
    //   - завершение ввода символа
    dwRetCode = WaitForMultipleObjects(2, 
      hEvents, FALSE, INFINITE);

    // Если ожидание любого из двух событий было отменено,
    // если произошло первое событие (завершение клиентского
    // процесса) или если произошла ошибка, прерываем цикл
    if(dwRetCode == WAIT_ABANDONED_0     ||
       dwRetCode == WAIT_ABANDONED_0 + 1 ||
       dwRetCode == WAIT_OBJECT_0        ||
       dwRetCode == WAIT_FAILED)
      break;

    // Читаем символ из первого байта отображенной
    // области памяти, записанный туда клиентским 
    // процессом, и отображаем его в консольном окне
    putch(*((LPSTR)lpFileMap));
  }
 
  // Закрываем идентификаторы объектов-событий  
  CloseHandle(hEventChar);
  CloseHandle(hEventTermination);

  // Отменяем отображение файла
  UnmapViewOfFile(lpFileMap);

  // Освобождаем идентификатор созданного
  // объекта-отображения
  CloseHandle(hFileMapping);

  return 0;
}

В глобальных переменных hEventChar и hEventTermination хранятся идентификаторы объектов-событий, предназначенных, соответственно, для работы с клавиатурой и для фиксации момента завершения работы приложения Fmap/Client. Эти же идентификаторы записываются в глобальный массив hEvents, который используется функцией WaitForMultipleObjects.

Глобальные имена объектов-событий хранятся в переменных lpEventName и lpEventTerminationName.

Имя отображения записывается в массив lpFileShareName, а идентификатор этого отображения - в глобальную переменную hFileMapping.

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

Функция main приложения Fmap/Server создает два объекта-события, пользуясь для этого функцией CreateEvent. Описание этой функции вы найдете в предыдущем томе “Библиотеки системного программиста”.

Далее функция main создает объект-отображение и выполняет отображение, вызывая для этого, соответственно, функции CreateFileMapping и MapViewOfFile. Так как в качестве идентификатора файла функции CreateFileMapping передается значение (HANDLE)0xFFFFFFFF, отображение будет создано непосредственно в виртуальной памяти без использования файла, расположенного на диске.

После инициализации массива hEvents функция main запускает цикл, в котором выполняется ожидание событий и вывод символов, записанных приложением Fmap/Client в отображенную область виртуальной памяти.

Для ожидания двух событий используется функция WaitForMultipleObjects. Через третий параметр этой функции передается значение FALSE, поэтому ожидание прекращается в том случае, если любое из событий переходит в отмеченное состояние.

В том случае, когда в отмеченное состояние перешел объект-событие hEventTermination, функция WaitForMultipleObjects возвращает значение WAIT_OBJECT_0. Обнаружив это, функция main завершает свою работу, потому что событие hEventTermination отмечается при завершении работы клиентского приложения Fmap/Client.

Если же в отмеченное состояние переходит объект-событие hEventChar, функция WaitForMultipleObjects возвращает значение WAIT_OBJECT_0 + 1. В этом случае функция main читает первый байт из отображенной области памяти и выводит его в консольное окно при помощи хорошо знакомой вам из программирования для MS-DOS функции putch:


putch(*((LPSTR)lpFileMap));

Перед своим завершением функция main закрывает идентификаторы объектов-событий, отменяет отображение и освобождает идентификатор этого отображения.

Приложение Fmap/Client

Исходные тексты приложения Fmap/Client, предназначенного для совместной работы с приложением Fmap/Server, представлены в листинге 2.2.

Листинг 2.2. Файл fmap/client/client.c


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

// Идентификаторы объектов-событий, которые используются
// для синхронизации задач, принадлежащих разным процессам
HANDLE hEvent;
HANDLE hEventTermination;

// Имя объекта-события для синхронизации ввода и отображения
CHAR lpEventName[] = 
  "$MyVerySpecialEventName$";

// Имя объекта-события для завершения процесса
CHAR lpEventTerminationName[] = 
  "$MyVerySpecialEventTerminationName$";

// Имя отображния файла на память
CHAR lpFileShareName[] = 
  "$MyVerySpecialFileShareName$";

// Идентификатор отображения файла на память
HANDLE hFileMapping;

// Указатель на отображенную область памяти
LPVOID lpFileMap;

int main()
{
  CHAR chr;

  printf("Mapped and shared file, client process\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n"
    "\n\nPress <ESC> to terminate...\n");
  
  // Открываем объект-событие для синхронизации 
  // ввода и отображения
  hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, lpEventName);
  
  if(hEvent == NULL)
  {
    fprintf(stdout,"OpenEvent: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Открываем объект-событие для сигнализации о
  // завершении процесса ввода
  hEventTermination = OpenEvent(EVENT_ALL_ACCESS, 
    FALSE, lpEventTerminationName);
  
  if(hEventTermination == NULL)
  {
    fprintf(stdout,"OpenEvent (Termination): Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Открываем объект-отображение
  hFileMapping = OpenFileMapping(
    FILE_MAP_READ | FILE_MAP_WRITE, FALSE, lpFileShareName);

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

  // Выполняем отображение файла на память.
  // В переменную lpFileMap будет записан указатель на
  // отображаемую область памяти
  lpFileMap = MapViewOfFile(hFileMapping, 
    FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

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

  // Цикл ввода. Этот цикл завершает свою работу,
  // когда пользователь нажимает клавишу <ESC>, 
  // имеющую код 27
  while(TRUE)
  {
    // Проверяем код введенной клавиши
    chr = getche();
    
    // Если нажали клавишу <ESC>, прерываем цикл
    if(chr == 27)
      break;

    // Записываем символ в отображенную память,
    // доступную серверному процессу
    *((LPSTR)lpFileMap) = chr;

    // Устанавливаем объект-событие в отмеченное
    // состояние
    SetEvent(hEvent);
  }
 
  // После завершения цикла переключаем оба события
  // в отмеченное состояние для отмены ожидания в
  // процессе отображения и для завершения этого процесса
  SetEvent(hEvent);
  SetEvent(hEventTermination);
  
  // Закрываем идентификаторы объектов-событий
  CloseHandle(hEvent);
  CloseHandle(hEventTermination);

  // Отменяем отображение файла
  UnmapViewOfFile(lpFileMap);

  // Освобождаем идентификатор созданного
  // объекта-отображения
  CloseHandle(hFileMapping);

  return 0;
}

После создания объектов-событий, предназначенных для синхронизации работы с приложением Fmap/Server, функция main приложения Fmap/Client открывает отображение при помощи функции OpenFileMapping, как это показано ниже:


hFileMapping = OpenFileMapping(
  FILE_MAP_READ | FILE_MAP_WRITE, FALSE, lpFileShareName);

В качестве имени отображения здесь указывается строка $MyVerySpecialFileShareName$ - точно такая же, что и в приложении Fmap/Server.

Далее в случае успеха выполняется отображение в память:


lpFileMap = MapViewOfFile(hFileMapping, 
    FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

Если отображение выполнено успешно, в глобальную переменную lpFileMap записывается указатель на отображенную область памяти, а затем запускается цикл ввода символов с клавиатуры.

Символы вводятся при помощи функции консольного ввода getche. Результат сохраняется в первом байте отображенной области памяти, откуда его будет брать для вывода приложение Fmap/Server:


chr = getche();
if(chr == 27)
   break;
*((LPSTR)lpFileMap) = chr;

После выполнения записи функция main устанавливает в отмеченное состояние объект-событие, предназначенное для работы с клавиатурой.

Если пользователь нажимает в окне приложения Fmap/Client клавишу <Esc>, имеющую код 27, цикл прерывается. Оба объекта-события переводятся в отмеченное состояние, после чего идентификаторы этих объектов освобождаются.

Перед завершением работы функция main отменяет отображение файла и освобождает идентификатор объекта-отображения.

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