Программирование для Windows NT (том 2)

         

Переключение раскладки клавиатуры


   Последняя функция, которую мы рассмотрим в этом разделе и которая  предназначена для работы с раскладками клавиатуры, называется ActivateKeyboardLayout:

BOOL ActivateKeyboardLayout(

  HKL  hkl,    // идентификатор раскладки клавиатуры

  UINT Flags); // флаги режима работы функции

Эта функция делает текущей раскладку клавиатуры, идентификатор которой передается ей через параметр hkl. Вы можете определить этот идентификатор с помощью функции LoadKeyboardLayout или взять из списка загруженных идентификаторов раскладок, который определяется функцией GetKeyboardLayoutList.

   Параметр Flags определяет режимы работы функции и имеет следующие значения:



Константа

Описание

KLF_REORDER

Система выполняет циклический сдвиг раскладок клавиатур в списке

KLF_UNLOADPREVIOUS

Выгрузка раскладки, которая раньше была активна

Пример использования функций GetKeyboardLayoutList и ActivateKeyboardLayout вы найдете в исходных текстах приложения SETLOCAL, к описанию которых мы и переходим.



Получение идентификатора сервиса


Для выполнения операций с сервисом вы должны получить его идентификатор. Это нетрудно сделать с помощью функции OpenService, прототип которой мы привели ниже:

SC_HANDLE OpenService(

  SC_HANDLE schSCManager,      // идентификатор базы данных системы

                               // управления сервисами

  LPCTSTR   lpszServiceName,   // имя сервиса

  DWORD     fdwDesiredAccess); // тип доступа к сервису

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

Параметр lpszServiceName определяет имя сервиса, а параметр fdwDesiredAccess - желаемый тип доступа к сервису.



Получение идентификатора системы управления сервисами


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

SC_HANDLE OpenSCManager(

  LPCTSTR lpszMachineName,   // адрес имени рабочей станции

  LPCTSTR lpszDatabaseName,  // адрес имени базы данных

  DWORD   fdwDesiredAccess); // нужный тип доступа

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

Для наших примеров параметр lpszDatabaseName, определяющий имя базы данных системы управления сервисами, нужно указать как NULL. При этом по умолчанию будет использована база данных активных сервисов ServicesActive.

Через параметр fdwDesiredAccess нужно задать требуемый тип доступа. Здесь можно использовать следующие константы:

  

Константа

Разрешенный тип доступа

SC_MANAGER_ALL_ACCESS

Полный доступ

SC_MANAGER_CONNECT

Подключение к системе управления сервисами

SC_MANAGER_CREATE_SERVICE

Создание новых сервисов и добавление их к регистрационной базе данных

SC_MANAGER_ENUMERATE_SERVICE

Просмотр списка всех установленных сервисов при попщи функции EnumServicesStatus (в нашей книге эта функция  и следующие две функции не описаны)

SC_MANAGER_LOCK

Блокирование базыв данных функцией LockServiceDatabase

SC_MANAGER_QUERY_LOCK_STATUS

Определение состояние блокировки базы данных функцией QueryServiceLockStatus

Ниже мы привели пример вызова функции OpenSCManager:

schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

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

CloseServiceHandle(schSCManager);



Получение списка установленных раскладок


При установке операционной системы Microsoft Windows NT или Microsoft Windows 95 пользователь может выбрать для работы одну или несколько раскладок клавиатуры. Для определения списка установленных раскладок вы можете использовать функцию GetKeyboardLayoutList:

UINT GetKeyboardLayoutList(

  int nBuff,    // количество элементов в буфере

  HKL *lpList); // указатель на буфер

Функция GetKeyboardLayoutList записывает в буфер, адрес которого задан параметром lpList, массив идентификаторов установленных раскладок клавиатуры, имеющих тип HKL. Через параметр nBuff вы должны передать функции размер буфера, указанный в количестве идентификаторов типа HKL.

Как определить этот размер?

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

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

UINT uLayouts;

HKL  *lpList;

uLayouts = GetKeyboardLayoutList(0, NULL);

lpList   = malloc(uLayouts * sizeof(HKL));

uLayouts = GetKeyboardLayoutList(uLayouts, lpList);

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

Операция определения названия национального языка выполняется в приведенном ниже фрагменте кода:

GetLocaleInfo(MAKELCID(((UINT)hklCurrent & 0xffffffff),

  SORT_DEFAULT), LOCALE_SLANGUAGE, szBuf, 512);



Преобразование даты


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

Приведем прототип функции GetDateFormat:

int GetDateFormat(

  LCID    Locale,    // идентификатор набора параметров

  DWORD   dwFlags,   // флаги режима работы функции

  CONST   SYSTEMTIME *lpDate, // дата

  LPCTSTR lpFormat,  // строка формата даты

  LPTSTR  lpDateStr, // буфер для записи выходной строки

  int     cchDate);  // размер выходного буфера в байтах

Рассмотрим отличия этой функции от функции GetTimeFormat.

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

Константа

Описание

LOCALE_NOUSEROVERRIDE 

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

DATE_SHORTDATE

Сокращенный формат даты

DATE_LONGDATE

Полный формат даты

DATE_USE_ALT_CALENDAR

Использование альтернативного календаря, если таковой определен для данного идентификатора набора национальных параметров

Отличаются также специальные символы, которые можно использовать в строке формата даты:

Символ

Компонента даты

d

День месяца без ведущего нуля

dd

День месяца с ведущим нулем

ddd

Трехбуквенное сокращение дня недели

dddd

Полное название дня недели

M

Номер месяца без ведущего нуля

MM

Номер месяца с ведущим нулем

MMM

Трехбуквенное сокращение названия месяца

MMMM

Полное название месяца

y

Двухзначное обозначение года без ведущего нуля (последние две цифры года)

yy

Двухзначное обозначение года с ведущим нулем

yyyy

Полный номер года

gg

Название периода или эры

Пример использования функции GetDateFormat для получения строки текущей даты приведен ниже:

GetDateFormat(GetThreadLocale(),

  LOCALE_NOUSEROVERRIDE | DATE_LONGDATE,

  NULL, NULL, szBuf1, 512);



Преобразование времени


С помощью функции GetTimeFormat вы можете получить текстовую строку времени:

int GetTimeFormat(

  LCID    Locale,    // идентификатор набора параметров

  DWORD   dwFlags,   // флаги режимов работы функции

  CONST   SYSTEMTIME *lpTime, // время

  LPCTSTR lpFormat,  // строка формата времени

  LPTSTR  lpTimeStr, // буфер для полученной строки времени

  int     cchTime);  // размер буфера в байтах

Через параметр Locale вы должны передать функции GetTimeFormat идентификатор набора национальных параметров, для которого необходимо выполнить форматирование строки времени.

Параметр dwFlags определяет режимы работы функции. Для этого параметра вы можете указать следующие значения:

Константа

Описание

LOCALE_NOUSEROVERRIDE

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

TIME_NOMINUTESORSECONDS

Не использовать минуты или секунды

TIME_NOSECONDS

Не использовать секунды

TIME_NOTIMEMARKER

Не использовать маркер

TIME_FORCE24HOURFORMAT

Всегда использовать 24-часовой формат времени

   Параметр lpTime может принимать значение NULL или содержать указатель на заполненную структуру типа SYSTEMTIME. В первом случае после завершения работы функции в буфере lpTimeStr будет получена строка для текущего времени. Во втором случае строка будет соответствовать времени, записанному в структуре SYSTEMTIME.

Приведем формат структуры SYSTEMTIME:

typedef struct _SYSTEMTIME

{

  WORD wYear;         // год

  WORD wMonth;        // месяц (1 - январь, 2 - февраль

                      // и так далее)

  WORD wDayOfWeek;    // день недели (0 - воскресение.

                      // 1 - понедельник, и так далее)

  WORD wDay;          // день месяца

  WORD wHour;         // часы

  WORD wMinute;       // минуты

  WORD wSecond;       // секунды

  WORD wMilliseconds; // миллисекунды

} SYSTEMTIME;

Параметр lpFormat задает строку формата, в соответствии с которым будет отформатирована выходная строка.


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

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

Символ

Компонента времени

h

Часы без ведущего нуля в 12-часовом формате

hh

Часы с ведущим нулем в 12-часовом формате

H

Часы без ведущего нуля в 24-часовом формате

HH

Часы с ведущим нулем в 24-часовом формате

m

Минуты без ведущего нуля

mm

Минуты с ведущим нулем

s

Секунды без ведущего нуля

ss

Секунды с ведущим нулем

t

Маркер (такой как A или P)

tt

Многосимвольный маркер (такой как AM или PM)

Параметры lpTimeStr и cchTime указывают, соответственно, адрес и размер буфера, в который будет записана отформатированная строка. Если параметр cchTime равен нулю, функция GetTimeFormat вернет размер буфера, достаточный для записи полной выходной строки.

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

GetTimeFormat(GetThreadLocale(),

  LOCALE_NOUSEROVERRIDE, NULL, NULL, szBuf, 512);


Приложение DiskInfo


Если ваше приложение просто открывает файлы или создает новые, возможно, для выбора файлов вам будет достаточно использовать стандартные диалоговые панели, которые мы создавали в предыдущем приложении. Однако во многих случаях вам необходимо предоставить пользователю детальную информацию о дисковых устройствах, такую, например, как тип файловой системы, общий объем диска, размер свободного пространства на диске, а также тип диска (локальный, сетевой, со сменным или несменным носителем данных, устройство чтения CD-ROM и так далее).

В этой главе мы приведем исходные тексты приложения DiskInfo, которое получает и отображает подробную информацию о всех дисковых устройствах, имеющихся в системе, как локальных, так и удаленных (сетевых). Информация отображается в табличном виде с помощью органа управления List View, который мы подробно описали в 22 томе “Библиотеки системного программиста”, посвященному операционной системе Microsoft Windows 95.

Внешний вид главного окна приложения DiskInfo, запущенного в одном из режимов отображения, показан на рис. 1.6.

Рис. 1.6. Просмотр информации о дисках в табличном виде

В столбце Drive отображаются пиктограммы и названия дисковых устройств, имеющихся в системе. Для каждого типа устройства используется своя пиктограмма.

В столбце Volume name для каждого устройства располагается метка тома (если она есть).  В столбце File system мы отображаем имя файловой системы.

Так как максимальная длина имени файлов и каталогов разная для различных файловых систем, то в столбце File name length мы отображаем этй длину для каждого дискового устройства.

В столбцах Total Space и Free Space выводится, соответственно, емкость диска в байтах и размер свободного пространства на диске (также в байтах).

Заметим, что при первом запуске приложение DiskInfo не пытается определить параметры для устройств со сменными носителями данных (устройства НГМД, устройства чтения CD-ROM, магнитооптические накопители и так далее). Это связано с тем, что при попытке определить параметры устройства операционная система Microsoft Windows NT выполняет обращение к соответствующему накопителю. В том случае, если в накопителе нет носителя данных, на экране появится предупреждающее сообщение.


Тем не менее, наше приложение может определить параметры устройств со сменными носителями данных. Для этого вы должны вставить носитель (дискету, компакт-диск и так далее) в устройство, а затем сделать двойной щелчок левой клавишей мыши по пиктограмме устройства. После этого соответствующая строка таблицы будет заполнена, как это показано на рис.1.7.



Рис. 1.7. Полностью заполненная таблица параметров дисковых устройств

Кроме того, после двойного щелчка по пиктограмме любого дискового устройства на экране появляется диалоговая панель Logical Drive Information, в которой отображается имя устройства, имя файловой системы, серийный номер, а также системные флаги (рис. 1.8 - 1.11). Системные флаги мы описали в последней главе предыдущего тома “Библиотеки системного программиста”.



Рис. 1.8. Просмотр дополнительной информации о диске FAT



Рис. 1.9. Просмотр дополнительной информации о диске NTFS



Рис. 1.10. Просмотр дополнительной информации о диске HPFS



Рис. 1.11. Просмотр дополнительной информации о диске CDFS

С помощью меню Options, показанного на рис. 1.12, вы можете изменить режим отображения списка дисковых устройств.



Рис. 1.12. Меню Options, предназначенное для изменения режима отображения списка дисковых устройств

Если выбрать из меню Options строку Icon view, внешний вид главного окна приложения изменится. Этот список будет отображаться в виде набора пиктограмм стандартного размера с подписью (рис. 1.13).



Рис. 1.13. Просмотр дисковых устройств в виде пиктограмм стандартного размера

Если же из этого меню выбрать строку Small icon view, для отображения списка устройств будут использованы маленькие пиктограммы (рис. 1.14).



Рис. 1.14. Просмотр дисковых устройств в виде пиктограмм маленького размера

Есть и еще один вариант, показанный на рис. 1.15. Он используется при выборе из меню Options строки List View.



Рис. 1.15. Просмотр дисковых устройств в виде списка

Если же из меню Options выбрать строку Report view, список дисковых устройств будет отображаться в виде таблицы, как это было показано на рис. 1.6 и 1.7.



Приложение DiskInfo может работать и в среде операционной системы Microsoft Windows 95 (рис. 1.16).



Рис. 1.16. Просмотр информации о дисковых устройствах в среде операционной системы Microsoft Windows 95

Компьютер, на котором была запущена программа в этом случае, был оборудован 3,5 дюймовым НГМД (устройство A:), 5,25 дюймовым НГМД (устройство B:), обычным жестким диском (устройство C:), магнитооптическим накопителем MaxOptix с емкостью 1,3 Гбайт (устройство D:),  и устройством чтения CD-ROM (устройство E:).

Кроме того, этот компьютер был подключен к сети, в которой есть серверы на базе операционных систем Microsoft Windows NT Server и Novell NetWare. Устройства F: и G: отображаются на сетевые тома сервера Microsoft Windows NT Server, а устройства S:, T: и U: - на сетевые тома сервера Novell NetWare.

Заметим, что в отличие от сервера Microsoft Windows NT Server, сервер Novell NetWare не был настроен таким образом, чтобы можно было работать с длинными именами файлов и каталогов.


Приложение DLLCALL


Приложение DLLCALL работает совместно с DLL-библиотекой DLLDemo.DLL, описанной в предыдущем разделе.

Главное окно приложения DLLCALL и меню File показано на рис. 3.7.

Рис. 3.7. Главное окно приложения DLLCALL

Если из меню File выбрать строку Find App Window, на экране появится диалоговая панель Find Application Window, показанная на рис. 3.8.

Рис. 3.8. Диалоговая панель Find Application Window

Здесь в поле Enter application window title to find вы должны ввести заголовок окна приложения. Если приложение с таким заголовком запущено, оно будет найдено, после чего на экране появится соответствующее сообщение (рис. 3.9).

Рис. 3.9. Сообщение о том, что заданное окно найдено

При инициализации DLL-библиотеки, вызванной подключением процесса DLLCALL, на экране возникает сообщение, показанное на рис. 3.10.

Рис. 3.10. Собщение о подключении процесса к DLL-библиотеке

Когда приложение DLLCALL отключается от DLL-библиотеки, на экран выводится сообщение, показанное на рис. 3.11.

Рис. 3.11. Сообщение об отключении процесса от DLL-библиотеки

Главный файл исходных текстов приложения DLLCALL представлен в листинге 3.4.

Листинг 3.4. Файл dlldemo\dllcall\dllcall.c

// ==================================================

// Приложение DLLCall

// Вызов функции из DLL-библиотеки

//

// (С) Фролов А.В., 1996

// Email: frolov@glas.apc.org

// ==================================================

#define STRICT

#include <windows.h>

#include <windowsx.h>

#include "resource.h"

#include "afxres.h"

#include "dllcall.h"

// Определяем тип: указатель на функцию

typedef HWND (WINAPI *MYDLLPROC)(LPSTR);

// Указатель на функцию, расположенную в

// DLL-библиотеке

MYDLLPROC GetAppWindow;

// Буфер для заголовка окна, поиск которого

// будет выполняться

char szWindowTitle[512];

// Идентификатор DLL-библиотеки

HANDLE hDLL;

HINSTANCE hInst;

char szAppName[]  = "DLLCallApp";


char szAppTitle[] = "DLL Call Demo";

// -----------------------------------------------------

// Функция WinMain

// -----------------------------------------------------

int APIENTRY

WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

        LPSTR lpCmdLine, int nCmdShow)

{

  WNDCLASSEX wc;

  HWND hWnd;

  MSG msg;

 

  // Сохраняем идентификатор приложения

  hInst = hInstance;

  // Преверяем, не было ли это приложение запущено ранее

  hWnd = FindWindow(szAppName, NULL);

  if(hWnd)

  {

    // Если было, выдвигаем окно приложения на

    // передний план

    if(IsIconic(hWnd))

           ShowWindow(hWnd, SW_RESTORE);

         SetForegroundWindow(hWnd);

    return FALSE;

  }

  // Регистрируем класс окна

  memset(&wc, 0, sizeof(wc));

  wc.cbSize = sizeof(WNDCLASSEX);

  wc.hIconSm = LoadImage(hInst,

    MAKEINTRESOURCE(IDI_APPICONSM),

    IMAGE_ICON, 16, 16, 0);

  wc.style = 0;

  wc.lpfnWndProc = (WNDPROC)WndProc;

  wc.cbClsExtra  = 0;

  wc.cbWndExtra  = 0;

  wc.hInstance = hInst;

  wc.hIcon = LoadImage(hInst,

    MAKEINTRESOURCE(IDI_APPICON),

    IMAGE_ICON, 32, 32, 0);

  wc.hCursor = LoadCursor(NULL, IDC_ARROW);

  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);

  wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);

  wc.lpszClassName = szAppName;

  if(!RegisterClassEx(&wc))

    if(!RegisterClass((LPWNDCLASS)&wc.style))

         return FALSE;

   

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

  hWnd = CreateWindow(szAppName, szAppTitle,

     WS_OVERLAPPEDWINDOW,

     CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,

     NULL, NULL, hInst, NULL);

  if(!hWnd) return(FALSE);

  // Отображаем окно и запускаем цикл

  // обработки сообщений

  ShowWindow(hWnd, nCmdShow);

  UpdateWindow(hWnd);

  while(GetMessage(&msg, NULL, 0, 0))

  {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

  }

  return msg.wParam;

}

// -----------------------------------------------------



// Функция WndProc

// -----------------------------------------------------

LRESULT WINAPI

WndProc( HWND hWnd, UINT msg, WPARAM wParam,

        LPARAM lParam)

{

  switch(msg)

  {

    HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);

    HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);

    default:

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

  }

}

// -----------------------------------------------------

// Функция WndProc_OnDestroy

// -----------------------------------------------------

#pragma warning(disable: 4098)

void WndProc_OnDestroy(HWND hWnd)

{

  PostQuitMessage(0);

  return 0L;

}

// -----------------------------------------------------

// Функция WndProc_OnCommand

// -----------------------------------------------------

#pragma warning(disable: 4098)

void WndProc_OnCommand(HWND hWnd, int id,

  HWND hwndCtl, UINT codeNotify)

{

  switch (id)

  {

    case ID_FILE_EXIT: 

    {

      // Завершаем работу приложения

      PostQuitMessage(0);

      return 0L;

      break;

    }

        

    case ID_FILE_FINDWINDOW:

    {

      // Отображаем диалоговую панель для ввода

      // заголовка главного окна приложения,

      // поиск которого будет выполняться

      if(DialogBox(hInst, MAKEINTRESOURCE(IDD_DLGFIND),

        hWnd, DlgProc))

      {

        // Первый способ вызова функции из DLL-библиотеки:

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

/*

        // Выполняем поиск окна с заголовком, заданным

        // при помощи диалоговой панели

        if(FindApplicationWindow(szWindowTitle) != NULL)

          MessageBox(NULL, "Application window was found",

            szAppTitle, MB_OK | MB_ICONINFORMATION);

       

          else

          MessageBox(NULL, "Application window was not found",

            szAppTitle, MB_OK | MB_ICONINFORMATION);

*/

        // Второй способ вызова функции из DLL-библиотеки:

        // загрузка DLL-библиотеки функцией LoadLibrary



        // Загружаем DLL-библиотеку

        hDLL = LoadLibrary("DLLDEMO.DLL");

        // Если библиотека загружена успешно, выполняем

        // вызов функции

        if(hDLL != NULL)

        {

          // Получаем адрес нужной нам функции

          GetAppWindow =

            (MYDLLPROC)GetProcAddress(hDLL,

              "FindApplicationWindow");

          // Если адрес получен, вызываем функцию

          if(GetAppWindow != NULL)

          {

            // Выполняем поиск окна с заголовком, заданным

            // при помощи диалоговой панели

            if(GetAppWindow(szWindowTitle) != NULL)

              MessageBox(NULL, "Application window was found",

                szAppTitle, MB_OK | MB_ICONINFORMATION);

            else

              MessageBox(NULL,

                "Application window was not found",

                szAppTitle, MB_OK | MB_ICONINFORMATION);

          }

         

          // Освобождаем DLL-библиотеку

          FreeLibrary(hDLL);

        }

      }

 

      break;     

    }

   

    case ID_HELP_ABOUT:

    {

      MessageBox(hWnd,

        "DLL Call Demo\n"

        "(C) Alexandr Frolov, 1996\n"

        "Email: frolov@glas.apc.org",

        szAppTitle, MB_OK | MB_ICONINFORMATION);

            return 0L;

            break;

    }

       default:

           break;

  }

  return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify,

    DefWindowProc);

}

// -----------------------------------------------------

// Функция DlgProc

// -----------------------------------------------------

LRESULT WINAPI

DlgProc( HWND hdlg, UINT msg, WPARAM wParam,

        LPARAM lParam)

{

  switch(msg)

  {

    HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog);

    HANDLE_MSG(hdlg, WM_COMMAND,    DlgProc_OnCommand);

       default:

    return FALSE;

  }

}

// -----------------------------------------------------



// Функция DlgProc_OnInitDialog

// -----------------------------------------------------

BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus,

                          LPARAM lParam)

{

  return TRUE;

}

// -----------------------------------------------------

// Функция DlgProc_OnCommand

// -----------------------------------------------------

#pragma warning(disable: 4098)

void DlgProc_OnCommand(HWND hdlg, int id,

  HWND hwndCtl, UINT codeNotify)

{

  switch (id)

  {

    case IDOK:

    {

      // Если пользователь нажал кнопку OK,

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

      // сохраняем его в глобальном буфере szWindowTitle

      GetDlgItemText(hdlg, IDC_EDIT1, szWindowTitle, 512); 

     

      // Завершаем работу диалоговой панели

      EndDialog(hdlg, 1);

      return TRUE;

    }

    // Если пользователь нажимает кнопку Cancel,

    // завершаем работу диалоговой панели

    case IDCANCEL:

    {

      // Завершаем работу диалоговой панели

      EndDialog(hdlg, 0);

      return TRUE;

    }

    default:

      break;

  }

  return FALSE;

}

Файл dllcall.h (листинг 3.5) содержит прототипы функций, определенных в приложении DLLCALL.

Листинг 3.5. Файл dlldemo\dllcall\dllcall.h

// -----------------------------------------------------

// Описание функций

// -----------------------------------------------------

LRESULT WINAPI

WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

void WndProc_OnCommand(HWND hWnd, int id,

  HWND hwndCtl, UINT codeNotify);

void WndProc_OnDestroy(HWND hWnd);

HWND FindApplicationWindow(LPSTR lpszWindowTitle);

LRESULT WINAPI

DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);

BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus,

                          LPARAM lParam);

void DlgProc_OnCommand(HWND hdlg, int id,

  HWND hwndCtl, UINT codeNotify);

В файле dllcall.rc (листинг 3.6) определены ресурсы приложения DLLCALL. Это меню, две пиктограммы и диалоговая панель, а также текстовые строки, которые не используются.



Листинг 3.6. Файл dlldemo\dllcall\dllcall.rc

// Microsoft Developer Studio generated resource script.

//

#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////////////////////////////

//

// Generated from the TEXTINCLUDE 2 resource.

//

#include "afxres.h"

//////////////////////////////////////////////////////////////

#undef APSTUDIO_READONLY_SYMBOLS

//////////////////////////////////////////////////////////////

// Russian resources

#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_RUS)

#ifdef _WIN32

LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT

#pragma code_page(1251)

#endif //_WIN32

//////////////////////////////////////////////////////////////

//

// Menu

//

IDR_APPMENU MENU DISCARDABLE

BEGIN

    POPUP "&File"

    BEGIN

        MENUITEM "F&ind App Window",   ID_FILE_FINDWINDOW

        MENUITEM SEPARATOR

        MENUITEM "E&xit",              ID_FILE_EXIT

    END

    POPUP "&Help"

    BEGIN

        MENUITEM "&About...",          ID_HELP_ABOUT

    END

END

#ifdef APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////

//

// TEXTINCLUDE

//

1 TEXTINCLUDE DISCARDABLE

BEGIN

    "resource.h\0"

END

2 TEXTINCLUDE DISCARDABLE

BEGIN

    "#include ""afxres.h""\r\n"

    "\0"

END

3 TEXTINCLUDE DISCARDABLE

BEGIN

    "\r\n"

    "\0"

END

#endif    // APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////

//

// Icon

//

// Icon with lowest ID value placed first to ensure

// application icon

// remains consistent on all systems.

IDI_APPICON             ICON    DISCARDABLE     "Dllcall.ico"

IDI_APPICONSM           ICON    DISCARDABLE     "Dllcalsm.ico"

//////////////////////////////////////////////////////////////



//

// Dialog

//

IDD_DLGFIND DIALOG DISCARDABLE  0, 0, 247, 74

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "Find Application Window"

FONT 8, "MS Sans Serif"

BEGIN

    EDITTEXT        IDC_EDIT1,21,31,152,16,ES_AUTOHSCROLL

    DEFPUSHBUTTON   "OK",IDOK,190,7,50,14

    PUSHBUTTON      "Cancel",IDCANCEL,190,24,50,14

    GROUPBOX        "Enter application window title to find",

                    IDC_STATIC,13,13,167,49

END

//////////////////////////////////////////////////////////////

//

// DESIGNINFO

//

#ifdef APSTUDIO_INVOKED

GUIDELINES DESIGNINFO DISCARDABLE

BEGIN

    IDD_DLGFIND, DIALOG

    BEGIN

        LEFTMARGIN, 7

        RIGHTMARGIN, 240

        TOPMARGIN, 7

        BOTTOMMARGIN, 67

    END

END

#endif    // APSTUDIO_INVOKED

#endif    // Russian resources

//////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////

//

// Generated from the TEXTINCLUDE 3 resource.

//

//////////////////////////////////////////////////////////////

#endif    // not APSTUDIO_INVOKED

Файл resource.h (листинг 3.7) содержит определения констант для файла описания ресурсов приложения.

Листинг 3.7. Файл dlldemo\dllcall\resource.h

//{{NO_DEPENDENCIES}}

// Microsoft Developer Studio generated include file.

// Used by DLLCall.rc

//

#define IDR_MENU1                       101

#define IDR_APPMENU                     101

#define IDI_APPICON                     102

#define IDI_APPICONSM                   103

#define IDD_DLGFIND                     104

#define IDC_EDIT1                       1000

#define ID_FILE_EXIT                    40001

#define ID_HELP_ABOUT                   40002

#define ID_FILE_FINDWINDOW              40003

// Next default values for new objects

//

#ifdef APSTUDIO_INVOKED

#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        105

#define _APS_NEXT_COMMAND_VALUE         40004

#define _APS_NEXT_CONTROL_VALUE         1001

#define _APS_NEXT_SYMED_VALUE           101

#endif

#endif

Рассмотрим исходные тексты приложения DLLCALL.


Приложение 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 отменяет отображение файла и освобождает идентификатор объекта-отображения.


Приложение 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 закрывает идентификаторы объектов-событий, отменяет отображение и освобождает идентификатор этого отображения.


Приложение MSLOTS


Исходный текст серверного приложения MSLOTS представлен в листинге2.13.

Листинг 2.13. Файл mailslot/mslots/mslots.c

// ==================================================

// Приложение MSLOTS (серверное приложение)

// Демонстрация использования каналов Mailslot

// для передачи данных между процессами

//

// (С) Фролов А.В., 1996

// Email: frolov@glas.apc.org

// ==================================================

#include <windows.h>

#include <stdio.h>

#include <conio.h>

int main()

{

  // Код возврата из функций

  BOOL   fReturnCode;

  // Размер сообщения в байтах

  DWORD  cbMessages;

  // Количество сообщений в канале Mailslot

  DWORD  cbMsgNumber;

  // Идентификатор канала Mailslot

  HANDLE hMailslot;

  // Имя создаваемого канала Mailslot

  LPSTR  lpszMailslotName = "\\\\.\\mailslot\\$Channel$";

  // Буфер для передачи данных через канал

  char   szBuf[512];

  // Количество байт данных, принятых через канал

  DWORD  cbRead;

  printf("Mailslot server demo\n"

    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");

  // Создаем канал Mailslot, имеющий имя lpszMailslotName

  hMailslot = CreateMailslot(

    lpszMailslotName, 0,

    MAILSLOT_WAIT_FOREVER, NULL);

   

  // Если возникла ошибка, выводим ее код и зваершаем

  // работу приложения

  if(hMailslot == INVALID_HANDLE_VALUE)

  {

    fprintf(stdout,"CreateMailslot: Error %ld\n",

      GetLastError());

    getch();

    return 0;

  }

  // Выводим сообщение о создании канала

  fprintf(stdout,"Mailslot created\n");

  // Цикл получения команд через канал

  while(1)

  {

    // Определяем состояние канала Mailslot

    fReturnCode = GetMailslotInfo(

      hMailslot, NULL, &cbMessages,

      &cbMsgNumber, NULL);

    if(!fReturnCode)

    {

      fprintf(stdout,"GetMailslotInfo: Error %ld\n",



        GetLastError());

      getch();

      break;

    }

    // Если в канале есть Mailslot сообщения,

    // читаем первое из них и выводим на экран

    if(cbMsgNumber != 0)

    {

      if(ReadFile(hMailslot, szBuf, 512, &cbRead, NULL))

      {

        // Выводим принятую строку на консоль

        printf("Received: <%s>\n", szBuf);

     

        // Если пришла команда "exit",

        // завершаем работу приложения

        if(!strcmp(szBuf, "exit"))

          break;

      }

      else

      {

        fprintf(stdout,"ReadFile: Error %ld\n",

          GetLastError());

        getch();

        break;

      }

    }

   

    // Выполняем задержку на  500 миллисекунд

    Sleep(500);

  }

  // Перед завершением приложения закрываем

  // идентификатор канала Mailslot

  CloseHandle(hMailslot);

  return 0;

}

Прежде всего, серверное приложение создает канал Mailslot, пользуясь для этого функцией CreateMailslot:

hMailslot = CreateMailslot(lpszMailslotName, 0,

  MAILSLOT_WAIT_FOREVER, NULL);

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

Сообщение читается функцией ReadFile:

ReadFile(hMailslot, szBuf, 512, &cbRead, NULL);

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

Sleep(500);

Задержка необходима для того, чтобы ожидание сообщения в цикле не отнимало слишком много системных ресурсов у других приложений.

Перед завершением работы приложения мы закрываем идентификатор канала Mailslot с помощью функции CloseHandle:

CloseHandle(hMailslot);


Приложение Oem2Char


Приложение Oem2Char, исходные тексты которого мы приведем в этом разделе, выполняет перекодировку текстовых файлов из формата OEM (используется программами MS-DOS) в формат ANSI (используется приложениями Microsoft Windows) и обратно.

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

Главное окно приложения Oem2Char, запущенного в среде Microsoft Windows 95, показано на рис. 1.2. Точно такой же вид это окно будет иметь при запуске этого приложения в среде Microsoft Windows NT версии 4.0.

Рис. 1.2. Главное окно приложения Oem2Char

Выбирая из меню File строку Convert вы можете указать режим перекодировки: из OEM в ANSI или обратно, из ANSI в OEM. Соответстсвующая диалоговая панель, предназначенная для указания режима перекодировки, называется Conversion Options и показана на рис.1.3.

Рис. 1.3. Диалоговая панель Conversion Options панель, предназначенная для выбора режима перекодировки

По умолчанию приложение выполняет перекодировку текстовых файлов из формата OEM в формат ANSI.

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

Выбрав из меню File строку Convert, вы увидите на экране диалоговую панель Select source file, показанную на рис. 1.4.

Рис. 1.4. Диалоговая панель Select source file, предназначенная для выбора исходного файла

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

Если же приложение работает с файлами в синхронном или асинхронном режиме, после выбора исходного файла вам будет представлена возможность выбрать файл для записи результата перекодировки (рис. 1.5). Различные режимы работы с файлами были описаны нами в предыдущем томе “Библиотеки системного программиста”.

Рис. 1.5. Диалоговая панель Select destination file, с помощью которой можно выбрать файл для записи результата перекодировки



Приложение PIPES


Исходные тексты приложения PIPES приведены в листинге 2.11.

Листинг 2.11. Файл pipe/pipes/pipes.c

// ==================================================

// Приложение PIPES (серверное приложение)

// Демонстрация использования каналов Pipe

// для передачи данных между процессами

//

// (С) Фролов А.В., 1996

// Email: frolov@glas.apc.org

// ==================================================

#include <windows.h>

#include <stdio.h>

#include <conio.h>

int main()

{

  // Флаг успешного создания канала

  BOOL   fConnected;

  // Идентификатор канала Pipe

  HANDLE hNamedPipe;

  // Имя создаваемого канала Pipe

  LPSTR  lpszPipeName = "\\\\.\\pipe\\$MyPipe$";

  // Буфер для передачи данных через канал

  char   szBuf[512];

  // Количество байт данных, принятых через канал

  DWORD  cbRead;

  // Количество байт данных, переданных через канал

  DWORD  cbWritten;

  printf("Named pipe server demo\n"

    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");

  // Создаем канал Pipe, имеющий имя lpszPipeName

  hNamedPipe = CreateNamedPipe(

    lpszPipeName,

    PIPE_ACCESS_DUPLEX,

    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,

    PIPE_UNLIMITED_INSTANCES,

    512, 512, 5000, NULL);

   

  // Если возникла ошибка, выводим ее код и зваершаем

  // работу приложения

  if(hNamedPipe == INVALID_HANDLE_VALUE)

  {

    fprintf(stdout,"CreateNamedPipe: Error %ld\n",

      GetLastError());

    getch();

    return 0;

  }

  // Выводим сообщение о начале процесса создания канала

  fprintf(stdout,"Waiting for connect...\n");

  // Ожидаем соединения со стороны клиента

  fConnected = ConnectNamedPipe(hNamedPipe, NULL);

 

  // При возникновении ошибки выводим ее код

  if(!fConnected)

  {

    switch(GetLastError())

    {

      case ERROR_NO_DATA:

        fprintf(stdout,"ConnectNamedPipe: ERROR_NO_DATA");


        getch();

        CloseHandle(hNamedPipe);

        return 0;

      break;

      case ERROR_PIPE_CONNECTED:

        fprintf(stdout,

          "ConnectNamedPipe: ERROR_PIPE_CONNECTED");

        getch();

        CloseHandle(hNamedPipe);

        return 0;

      break;

      case ERROR_PIPE_LISTENING:

        fprintf(stdout,

          "ConnectNamedPipe: ERROR_PIPE_LISTENING");

        getch();

        CloseHandle(hNamedPipe);

        return 0;

      break;

      case ERROR_CALL_NOT_IMPLEMENTED:

        fprintf(stdout,

           "ConnectNamedPipe: ERROR_CALL_NOT_IMPLEMENTED");

        getch();

        CloseHandle(hNamedPipe);

        return 0;

      break;

      default:

        fprintf(stdout,"ConnectNamedPipe: Error %ld\n",

          GetLastError());

        getch();

        CloseHandle(hNamedPipe);

        return 0;

      break;

    }

    CloseHandle(hNamedPipe);

    getch();

    return 0;

  }

  // Выводим сообщение об успешном создании канала

  fprintf(stdout,"\nConnected. Waiting for command...\n");

  // Цикл получения команд через канал

  while(1)

  {

    // Получаем очередную команду через канал Pipe

    if(ReadFile(hNamedPipe, szBuf, 512, &cbRead, NULL))

    {

      // Посылаем эту команду обратно клиентскому

      // приложению

      if(!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1,

        &cbWritten, NULL))

        break;

      // Выводим принятую команду на консоль

      printf("Received: <%s>\n", szBuf);

     

      // Если пришла команда "exit",

      // завершаем работу приложения

      if(!strcmp(szBuf, "exit"))

        break;

    }

    else

    {

      fprintf(stdout,"ReadFile: Error %ld\n",

        GetLastError());

      getch();

      break;

    }

  }

  CloseHandle(hNamedPipe);

  return 0;

}



В области локальных переменных функции main определена строка lpszPipeName, в которой хранится имя канала:

LPSTR  lpszPipeName = "\\\\.\\pipe\\$MyPipe$";

Так как канал создается локально, в качестве имени компьютера указан символ точки. Канал называется $MyPipe$.

Буфер szBuf размером 512 байт нужен для хранения данных, передаваемых через канал.

В переменные cbRead и cbWritten при выполнении операций чтения и записи через канал записывается, соответственно, количество принятых и переданных байт данных.

После вывода “рекламной” строки приложение PIPES создает канал, вызывая для этого функцию CreateNamedPipe:

hNamedPipe = CreateNamedPipe(

    lpszPipeName,

    PIPE_ACCESS_DUPLEX,

    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,

    PIPE_UNLIMITED_INSTANCES,

    512, 512, 5000, NULL);

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

Константа PIPE_TYPE_MESSAGE определяет, что через канал будут передаваться сообщения заданной длины (а не просто последовательность байт данных).

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

После создания канала, который будет работать в блокирующем режиме, вызывается функция ConnectNamedPipe:

fConnected = ConnectNamedPipe(hNamedPipe, NULL);

Из-за блокирующего режима работы и из-за того, что канал работает без перекрытия в синхронном режиме, после вызова функции ConnectNamedPipe сервер перейдет в состояние ожидания. Он будет находиться в этом состоянии до тех пор, пока клиентское приложение PIPEC не установит с ним канал Pipe.

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



После успешного создания канала приложение PIPES входит в цикл получения команд от клиентского приложения PIPEC:

while(1)

{

  if(ReadFile(hNamedPipe, szBuf, 512, &cbRead, NULL))

  {

    if(!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1,

        &cbWritten, NULL))

        break;

    printf("Received: <%s>\n", szBuf);

    if(!strcmp(szBuf, "exit"))

        break;

  }

  else

  {

    fprintf(stdout,"ReadFile: Error %ld\n",

      GetLastError());

    getch();

    break;

  }

}

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

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

Далее серверное приложение сравнивает принятую команду со строкой exit. Если от клиента пришла эта строка, цикл получения команд завершается.


Приложение RCLOCK


Для иллюстрации методов работы с сообщением WM_COPYDATA мы подготовили два приложения, которые называются RCLOCK и STIME.

Приложение RCLOCK раз в секунду получает от приложения STIME сообщение WM_COPYDATA, вместе с которым передается строка текущего времени. Полученая строка отображается в небольшом окне, расположенном в левом нижнем углу рабочего стола (рис. 2.2).

Рис. 2.2. Окно приложения RCLOCK

Заметим, что сразу после запуска (если приложение STIME еще не активно) в окне приложения RCLOCK отображается строка <Unknown>, как это показано на рис. 2.3.

Рис. 2.3. Исхндное состояние окна приложения RCLOCK

Если при активном приложении RCLOCK завершить приложение STIME, последнее передаст строку <Terminated>, которая будет показана в окне приложения RCLOCK.

Для того чтобы завершить работу приложения RCLOCK вы должны сделать его окно текущим, щелкнув по нему левой клавишей мыши, а затем нажать комбинацию клавиш <Ctrl+F4>. При необходимости вы сможете реализовать более удобный способ самостоятельно. В 11 томе “Библиотеки системного программиста” мы привели исходные тексты приложения TMCLOCK, из которого вы можете взять некоторые идеи для улучшения пользовательского интерфейса. Например, вы можете организовать перемещение окна приложения мышью, обрабатывая сообщение WM_NCHITTEST.



Приложение SETLOCAL


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

Главное окно приложения SETLOCAL показано на рис. 4.1.

Рис. 4.1. Главное окно приложения SETLOCAL

Если из меню Keyboard выбрать строку Set Layout, на экране появится диалоговая панель Set Keyboard Layout, показанная на рис. 4.2. С помощью этой диалоговой панели вы можете изменить клавиатурную раскладку.

Рис. 4.2. Диалоговая панель Set Keyboard Layout

С помощью списка Keyboard layouts вы можете выбрать одну из установленных в системе клавиатурных раскладок. Если после выбора раскладки нажать кнопку Set layout или OK, выбранная раскладка станет активной.

В поле Enter text to test layout вы можете вводить символы для проверки выбранной раскладки клавиатуры. При этом удобно пользоваться кнопкой Set layout, нажатие на которую  не приводит к закрытию диалоговой панели.

Кнопка Cancel позволяет отменить изменение текущей раскладки клавиатуры.

Если из меню Keyboard выбрать строку Get Layout ID, на экране появится диалоговая панель с идентификатором текущей раскладки клавиатуры. На рис. 4.3 и 4.4 это сообщение показано для американской и русской раскладки клавиатуры.

Рис. 4.3. Идентификатор американской раскладки клавиатуры

Рис. 4.4. Идентификатор русской раскладки клавиатуры

С помощью строк меню Local Info (рис. 4.5) вы можете просмотреть отдельные национальные параметры и изменить текущий набор национальных параметров.

Рис. 4.5. Меню Local Info

Выбирая строки Set English и Set Russian, вы можете выбирать либо американский, либо русский набор национальных параметров. Заметим, что возможность динамического изменения набора национальных параметров отсутствует в операционной системе Microsoft Windows 95. Там вы можете изменить этот набор только при помощи приложения Control Panel с последующей перезагрузкой компьютера.

Для просмотра значений некоторых национальных параметров вы должны выбрать из меню Local Info строку Get Local Info. При этом на экране появится диалоговая панель Set and Get Local Info. На рис. 4.6 и 4.7 мы показали эту диалоговую панель для американского и русского набора параметров, соответственно.




Рис. 4.6. Значения некоторых параметров для американского набора национальных параметров



Рис. 4.7. Значения некоторых параметров для русского набора национальных параметров

Здесь мы отображаем название языка, код страны, номер кодовой страницы OEM и номер кодовой страницы ANSI.

Если из меню Local Info выбрать строку Get Date and Time, на экране появится диалоговая панель, отображающая строки даты и времени в формате, соответствующем текущему набору национальных параметров. На рис. 4.8 и 4.9 эти строки показаны для американского и русского набора параметров.



Рис. 4.8. Строка даты и времени для американского набора параметров



Рис. 4.9 Строка даты и времени для русского набора параметров


Приложение SRVCTRL


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

Главное меню приложения SRVCTRL и временное меню Service показано на рис. 5.4.

Рис. 5.4. Главное меню приложения SRVCTRL

С помощью строки Install вы можете установить сервис. Не забудьте перед этим записать загрузочный файл сервиса в каталог c:\ntbk2\src\service\small\debug, так как приложение SRVCTRL может установить сервис только из этого каталога.

После установки имя сервиса появится в списке сервисов, который можно просмотреть при помощи приложения Services из папки Control Panel (рис.5.5).

Рис. 5.5. В списке сервисов появился новый сервис Sample of simple service

Если теперь из меню Service нашего приложения выбрать строку Get configuration, на экране появится диалоговая панель, в которой будут отображены некоторые поля структуры конфигрурации сервиса (рис. 5.6).

Рис. 5.6. Просмотр конфигурации сервиса



Приложение STIME


Приложение STIME работает в паре с приложением RCLOCK, периодически раз в секунду посылая последнему текстовые строки с помощью сообщения WM_COPYDATA. Главное окно приложения STIME не имеет никаких особенностей, поэтому мы его не приводим.



использования функции CreateNamedPipe


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

HANDLE hNamedPipe;

LPSTR  lpszPipeName = "\\\\.\\pipe\\$MyPipe$";

hNamedPipe = CreateNamedPipe(

    lpszPipeName,

    PIPE_ACCESS_DUPLEX,

    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,

    PIPE_UNLIMITED_INSTANCES,

    512, 512, 5000, NULL);

Через создаваемый канал передаются сообщения (так как указана константа PIPE_TYPE_MESSAGE). Данная реализация предназначена только для чтения (константа PIPE_READMODE_MESSAGE).

При создании канала мы указали размер буферов ввода и вывода, равный 512 байт. Время ожидания операций выбрано равным 5 секунд. Атрибуты защиты не указаны, поэтому используются значения, принятые по умолчанию.



Примеры приложений


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

Первое из этих приложений называется PIPES. Оно выполняет роль сервера, который получает команды от клиентского приложения PIPEC и отображает их в консольном окне (рис. 2.4).

Рис. 2.4. Консольное окно серверного приложения PIPES

Сразу после запуска приложение PIPES переходит в состояние ожидания соединения с клиентским приложением. При этом в его окне отображается строка Waiting for connect.

Как только клиентское приложение PIPEC установит канал связи с приложением PIPES, в окно приложения PIPES выводятся строки Connected и Waiting for command. Команды, посылаемые серверу, выводятся в окне в режиме свертки. Никакой другой обработки команд не выполняется, однако принятые команды посылаются сервером обратно клиентскому приложению.

Консольное окно клиентского приложения PIPEC, запущенного в среде операционной системы Microsoft Windows 95, показано на рис. 2.5.

Рис. 2.5. Консольное окно клиентского приложения PIPEC

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

pipec netlab

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

После успешного создания канала клиентское приложение выводит сообщение Connected и предлагает вводить команды в приглашении cmd>. В качестве команд вы можете вводить произвольные последовательности символов, кроме exit. Команда exit используется для завершения работы приложений PIPEC и PIPES.

После того как команда посылается серверу, она возвращается обратно и отображается в окне клиентского приложения для контроля в строке Received back.


Для примера мы подготовили исходные тексты двух приложений - MSLOTS и MSLOTC, которые обмениваются информацией через канал Mailslot (рис. 2.6).

Рис. 2.6. Обмен сообщениями между приложениями MSLOTS и MSLOTC

Приложение MSLOTS выполняет роль сервера, создавая канал Mailslot. Периодически с интервалом 0,5 с это приложение проверяет, не появилось ли в канале сообщение. Если появилось, это сообщение отображается в консольном окне.

Клиентское приложение MSLOTC устанавливает связь с приложением MSLOTS. Если при запуске указать имя компьютера или домена, возможно подключение к серверу MSLOTS, запущенному на другой рабочей станции в сети.

Если вводить текстовые строки в приглашении cmd>, которое выводится в консольном окне клиентского приложения MSLOTC, они будут передаваться серверу через канал Mailslot и отображаться в его окне. Для завершения работы обоих приложений вы должны ввести в приглашении клиента команду exit.

Перейдем теперь к описанию исходных текстов наших приложений.



Принудительная запись измененных данных


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

BOOL FlushViewOfFile(

  LPCVOID lpBaseAddr, // начальный адрес сохраняемой области

  DWORD dwNumberOfBytesToFlush); // размер области в байтах

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



Реализации каналов


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

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

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



Состояние сервиса


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

BOOL SetServiceStatus(

  SERVICE_STATUS_HANDLE sshServiceStatus, // идентификатор

                                          // состояния сервиса

  LPSERVICE_STATUS lpssServiceStatus);    // адрес структуры,

                               // содержащей состояние сервиса

Через параметр sshServiceStatus функции SetServiceStatus вы должны передать идентификатор состояния сервиса, полученный от функции RegisterServiceCtrlHandler.

В параметре lpssServiceStatus вы должны передать адрес предварительно заполненной структуры типа SERVICE_STATUS:

typedef struct _SERVICE_STATUS 

{

  DWORD dwServiceType;       // тип сервиса

  DWORD dwCurrentState;      // текущее состояние сервиса

  DWORD dwControlsAccepted;  // обрабатываемые команды

  DWORD dwWin32ExitCode;     // код ошибки при запуске

                             // и остановке сервиса

  DWORD dwServiceSpecificExitCode; // специфический код ошибки

  DWORD dwCheckPoint;        // контрольная точка при

                             // выполнении длительных операций

  DWORD dwWaitHint;          // время ожидания

} SERVICE_STATUS, *LPSERVICE_STATUS;

В поле dwServiceType необходимо записать один из перечисленных ниже флагов, определяющих тип сервиса:

Флаг

Описание

SERVICE_WIN32_OWN_PROCESS

Сервис работает как отдельный процесс

SERVICE_WIN32_SHARE_PROCESS

Сервис работает вместе с другими сервисами в рамках одного и того же процесса

SERVICE_KERNEL_DRIVER

Сервис представляет собой драйвер операционной системы Microsoft Windows NT

SERVICE_FILE_SYSTEM_DRIVER

Сервис является драйвером файловой системы

SERVICE_INTERACTIVE_PROCESS 

Сервисный процесс может взаимодействовать с программным интерфейсом рабочего стола Desktop

В поле dwCurrentState вы должны записать текущее состояние сервиса. Здесь можно использовать одну из перечисленных ниже констант:

Константа

Состояние сервиса

SERVICE_STOPPED

Сервис остановлен

SERVICE_START_PENDING

Сервис находится в состоянии запуска, но еще не работает

SERVICE_STOP_PENDING

Сервис находится в состоянии остановки, но еще не остановился

SERVICE_RUNNING

Сервис работает

SERVICE_CONTINUE_PENDING

Сервис начинает запускаться после временной остановки, но еще не работает

SERVICE_PAUSE_PENDING

Сервис начинает переход в состояние временной остановки, но еще не остановился

SERVICE_PAUSED

Сервис находится в состоянии верменной остановки

<
Задавая различные значения в поле dwControlsAccepted, вы можете указать, какие команды обрабатывает сервис. Ниже приведен список возможных значений:

Значение

Команды,, которые может воспринимать сервис

SERVICE_ACCEPT_STOP

Команда остановки сервиса SERVICE_CONTROL_STOP

SERVICE_ACCEPT_PAUSE_CONTINUE

Команды временной остановки SERVICE_CONTROL_PAUSE и продолжения работы после временной остановки SERVICE_CONTROL_CONTINUE

SERVICE_ACCEPT_SHUTDOWN

Команда остановки при завершении работы операционной системы SERVICE_CONTROL_SHUTDOWN

Значение в поле dwWin32ExitCode определяет код ошибки WIN32, который используется для сообщения о возникновении ошибочной ситуации при запуске и остановки сервиса. Если в этом поле указать значение ERROR_SERVICE_SPECIFIC_ERROR, то будет использован специфический для данного сервиса код ошибки, указанной в поле dwServiceSpecificExitCode структуры SERVICE_STATUS. Если ошибки нет, в поле dwWin32ExitCode необходимо записать значение NO_ERROR.

Поле dwServiceSpecificExitCode используется в том случае, когда в поле dwWin32ExitCode указано значение ERROR_SERVICE_SPECIFIC_ERROR.

Теперь о поле dwCheckPoint.

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

Содержимое поля dwWaitHint определяет ожидаемое время выполнения (в миллисекундах) длительной операции запуска, остановки или продолжения работы после временной остановки. Если за указанное время не изменится содержимое полей dwCheckPoint или dwCurrentState, процесс управления сервисами будет считать, что произошла ошибка.

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

void ReportStatus(DWORD dwCurrentState,

       DWORD dwWin32ExitCode, DWORD dwWaitHint)



{

  static DWORD dwCheckPoint = 1;

  if(dwCurrentState == SERVICE_START_PENDING)

    ss.dwControlsAccepted = 0;

  else

    ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;

  ss.dwCurrentState  = dwCurrentState;

  ss.dwWin32ExitCode = dwWin32ExitCode;

  ss.dwWaitHint      = dwWaitHint;

  if((dwCurrentState == SERVICE_RUNNING)

     (dwCurrentState == SERVICE_STOPPED))

    ss.dwCheckPoint = 0;

  else

    ss.dwCheckPoint = dwCheckPoint++;

  SetServiceStatus(ssHandle, &ss);

}

При заполнении структуры SERVICE_STATUS  эта функция проверяет содержимое поля dwCurrentState. Если сервис находится в состоянии ожидания запуска, в поле допустимых команд dwControlsAccepted записывается нулевое значение. В противном случае функция записывает туда значение SERVICE_ACCEPT_STOP, в результате чего сервису может быть передана команда остановки. Далее функция заполняет поля dwCurrentState, dwWin32ExitCode и dwWaitHint значениями, полученными через параметры.

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

После подготовки структуры SERVICE_STATUS ее адрес передается функции установки состояния сервиса SetServiceStatus.

Для определения текущего состояния сервиса вы можете использовать функцию QueryServiceStatus, прототип которой приведен ниже:

BOOL QueryServiceStatus(

  SC_HANDLE        schService,         // идентификатор сервиса

  LPSERVICE_STATUS lpssServiceStatus); // адрес структуры

                                       // SERVICE_STATUS 

Идентификатор сервиса вы можете получить от функций OpenService или CreateService, которые будут описаны ниже.


Создание канала


Для создания именованных и анонимных каналов Pipes используются функции CreatePipe и CreateNamedPipe.



Создание канала Mailslot


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

Приведем прототип функции CreateMailslot:

HANDLE CreateMailslot(

  LPCTSTR lpName,          // адрес имени канала Mailslot

  DWORD   nMaxMsgSize,     // максимальный размер сообщения

  DWORD   lReadTimeout,    // время ожидания для чтения

  LPSECURITY_ATTRIBUTES lpSecurityAttributes); // адрес

                           // структуры защиты

Через параметр lpName вы должны передать функции CreateMailslot адрес строки символов с именем канала Mailslot. Эта строка имеет следующий вид:

\\.\mailslot\[Путь]ИмяКанала

В этом имени путь является необязательной компонентой. Тем не менее, вы можете указать его аналогично тому, как это делается для файлов. Что же касается имени канала Mailslot, то оно задается аналогично имени канала Pipes.

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

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

Параметр lpSecurityAttributes задает адрес структуры защиты, который мы в наших приложениях будем указывать как NULL.

При ошибке функцией CreateMailslot возвращается значение INVALID_HANDLE_VALUE. Код ошибки можно определить при помощи функции GetLastError.

Ниже мы привели пример использования функции CreateMailslot в серверном приложении:

LPSTR  lpszMailslotName = "\\\\.\\mailslot\\$MailslotName$";

hMailslot = CreateMailslot(lpszMailslotName, 0,

  MAILSLOT_WAIT_FOREVER, NULL);

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

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



Создание отображения файла


Рассмотрим процедуру создания отображения файла на память.

Прежде всего, приложение должно открыть файл при помощи функции CreateFile, известной вам из предыдущего тома “Библиоткеи системного программиста”. Ниже мы привели прототип этой функции:

HANDLE CreateFile(

  LPCTSTR lpFileName,              // адрес строки имени файла

  DWORD   dwDesiredAccess,         // режим доступа

  DWORD   dwShareMode,// режим совместного использования файла

  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // дескриптор

                                              // защиты

  DWORD  dwCreationDistribution,   // параметры создания

  DWORD  dwFlagsAndAttributes,     // атрибуты файла

  HANDLE hTemplateFile);   // идентификатор файла с атрибутами

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

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

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

Остальные параметры этой функции мы уже описали в предыдущем томе.

В случае успешного завершения, функция CreateFile возвращает идентификатор открытого файла. При ошибке возвращается значение INVALID_HANDLE_VALUE. Здесь все как обычно, пока никакого отображения еще не выполняется.

Для того чтобы создать отображение файла, вы должны вызвать функцию CreateFileMapping, прототип которой приведен ниже:

HANDLE CreateFileMapping(

  HANDLE hFile,           // идентификатор отображаемого файла


  LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // дескриптор

                                                 // защиты

  DWORD flProtect,         // защита для отображаемого файла

  DWORD dwMaximumSizeHigh, // размер файла (старшее слово)

  DWORD dwMaximumSizeLow,  // размер файла (младшее слово)

  LPCTSTR lpName);         // имя отображенного файла

Через параметр hFile этой функции нужно передать идентификатор файла, для которого будет выполняться отображение в память, или значение 0xFFFFFFFF. В первом случае функция CreateFileMapping отобразит заданный файл в память, а во втором - создаст отображение с использованием файла виртуальной памяти. Как мы увидим позже, отображение с использованием файла виртуальной памяти удобно для организации передачи данных между процессами.

Обратим ваше внимание на одну потенциальную опасность, связанную с использованием в паре функций CreateFile и CreateFileMapping. Если функция CreateFile завершится с ошибкой и эта ошибка не будет обработана приложением, функция CreateFileMapping получит через параметр hFile значение INVALID_HANDLE_VALUE, численно равное 0xFFFFFFFF. В этом случае она сделает совсем не то, что предполагал разработчик приложения: вместо того чтобы выполнить отображение файла в память, функция создаст отображение с использованием файла виртуальной памяти.

Параметр lpFileMappingAttributes задает адрес дескриптора защиты. В большинстве случаев для этого параметра вы можете указать значение NULL.

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

Значение

Описание

PAGE_READONLY

К выделенной области памяти предоставляется доступ только для чтения. При создании или открывании файла необходимо указать флаг GENERIC_READ

PAGE_READWRITE

К выделенной области памяти предоставляется доступ для чтения и записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE

PAGE_WRITECOPY

К выделенной области памяти предоставляется доступ для копирования при записи. При создании или открывании файла необходимо указать флаги GENERIC_READ и GENERIC_WRITE. Режим копирования при записи будет описан позже в главе, посвященной обмену данными между процессами

<


Эти значения можно комбинировать при помощи логической операции ИЛИ со следующими атрибутами:

Атрибут

Описание

SEC_COMMIT

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

SEC_IMAGE

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

SEC_NOCACHE

Отмена кэширования для всех страниц отображаемой области памяти. Должен использоваться вместе с атрибутами SEC_RESERVE или SEC_COMMIT

SEC_RESERVE

Если указан этот атрибут, вместо выделения выполняется резервирование страниц виртуальной памяти. Зарезервированные таким образом страницы можно будет получить в пользование при помощи функции VirtualAlloc. Атрибут SEC_RESERVE можно указывать только в том случае, если в качестве параметра hFile функции CreateFileMapping передается значение 0xFFFFFFFF

С помощью параметров dwMaximumSizeHigh и dwMaximumSizeLow необходимо  указать функции CreateFileMapping 64-разрядный размер файла. Параметр dwMaximumSizeHigh должен содержать старшее 32-разрядное слово размера, а параметр dwMaximumSizeLow - малдшее 32-разрядное слово размера. Для небольших файлов, длина которых укладывается в 32 разряда, нужно указывать нулевое значение параметра dwMaximumSizeHigh.

Заметим, что вы можете указать нулевые значения и для параметра dwMaximumSizeHigh, и для параметра dwMaximumSizeLow. В этом случае предполагается, что размер файла изменяться не будет.

Через параметр lpName можно указать имя отображения, которое будет доступно всем работающим одновременно приложениям. Имя должно представлять собой текстовую строку, закрытую двоичным нулем и не содержащую символов “\”.

Если отображение будет использоваться только одним процессом, вы можете не задавать для него имя. В этом случае значение параметра lpName следует указать как NULL.

В случае успешного завершения функция CreateFileMapping возвращает идентификатор созданного отображения. При ошибке возвращается значение NULL.

Так как имя отображения глобально, возможно возникновение ситуации, когда процесс пытается создать отображение с уже существующим именем. В этом случае функция CreateFileMapping возвращает идентификатор существующего отображения. Такую ситуацию можно определить с помощью функции GetLastError, вызвав ее сразу после функции CreateFileMapping. Функция GetLastError при этом вернет значение ERROR_ALREADY_EXISTS.


Создание сервисного процесса


Для того чтобы создать загрузочный модуль сервиса, вы должны подготовить исходные тексты обычного консольного приложения, имеющего функцию main (не WinMain, а именно main).



Статическая и динамическая компоновка


Прежде чем приступить к изучению особенностей использования библиотек динамической компоновки в операционной системе Microsoft Windows NT, напомним кратко, чем отличаются друг от друга статическая и динамическая компоновка.

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

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

В среде мультизадачной операционной системы статическая компоновка неэффективна, так как приводит к неэкономному использованию очень дефицитного ресурса - оперативной памяти. Представьте себе, что в системе одновременно работают 5 приложений, и все они вызывают такие функции, как sprintf, memcpy, strcmp и т. д. Если приложения были собраны с использованием статической компоновки, в памяти будут находится одновременно 5 копий функции sprintf, 5 копий функции memcpy, и т. д (рис. 3.1).

Рис. 3.1. Вызов функций при использовании статической компоновки

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

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

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


Таким образом, используя механизм динамической компоновки, в загрузочном файле программы можно расположить только те функции, которые являются специфическими для данной программы. Те же функции, которые нужны всем (или многим) программам, работающим параллельно, можно вынести в отдельные файлы - библиотеки динамической компоновки, и хранить в памяти в единственном экземпляре (рис. 3.2). Эти файлы можно загружать в память только при необходимости, например, когда какая-нибудь программа захочет вызвать функцию, код которой расположен в библиотеке.



Рис. 3.2. Вызов функции при использовании динамической компоновки

В операционной системе Windows NT файлы библиотек динамической компоновки имеют расширение имени dll, хотя можно использовать любое другое, например, exe. В первых версиях Windows DLL-библиотеки располагались в файлах с расширением имени exe. Возможно поэтому файлы krnl286.exe, krnl386.exe, gdi.exe и user.exe имели расширение имени exe, а не dll, несмотря на то, что перечисленные выше файлы, составляющие ядро операционной системы Windows версии 3.1, есть ни что иное, как DLL-библиотеки. Наиболее важные компоненты операционной системы Microsoft Windows NT расположены в библиотеках с именами kernel32.dll (ядро операционной системы), user32.dll (функции пользовательского интерфейса), gdi32.dll (функции для рисования изображений и текста).

Механизм динамической компоновки был изобретен задолго до появления операционных систем Windows и OS/2 (которая также активно использует механизм динамической компоновки). Например, в мультизадачных многопользовательских операционных системах VS1, VS2, MVS, VM, созданных для компьютеров IBM-370 и аналогичных, код функций, нужных параллельно работающим программам, располагается в отдельных библиотеках и может загружаться при необходимости в специально выделенную общую область памяти.


Точка входа сервиса


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

void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv);

Точка входа сервиса вызывается при запуске сервиса функцией StartService (эту функцию мы рассмотрим позже). Через параметр dwArgc передается счетчик аргументов, а через параметр lpszArgv - указатель на массив строк параметров. В качестве первого параметра всегда передается имя сервиса. Остальные параметры можно задать при запуске сервиса функцией StartService.

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

Первая задача решается с помощью функции RegisterServiceCtrlHandler, прототип которой приведен ниже:

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(

  LPCTSTR lpszServiceName,            // имя сервиса

  LPHANDLER_FUNCTION lpHandlerProc);  // адрес функции

                                      // обработки команд

Через первый параметр этой функции необходимо передать адрес текстовой строки имени сервиса, а через второй - адрес функции обработки команд (функция обработки команд будет рассмотрена ниже).

   Вот пример использования функции RegisterServiceCtrlHandler:

SERVICE_STATUS_HANDLE ssHandle;

ssHandle =

  RegisterServiceCtrlHandler(MYServiceName, ServiceControl);

Функция RegisterServiceCtrlHandler в случае успешного завершения возвращает идентификатор состояния сервиса. При ошибке возвращается нулевое значение.

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

Теперь перейдем к решению второй задачи - инициализации сервиса.

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

А что делать, если инициализация сервиса представляет собой достаточно длительный процесс?

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

После завершения инициализации функция точки входа сервиса должна указать процессу управления сервисами, что процесс запущен и находится в состоянии SERVICE_RUNNING.



Удаление сервиса из системы


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

Ниже мы привели фрагмент приложения, удаляющий сервис с именем MYServiceName из системы:

schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

schService = OpenService(

  schSCManager, MYServiceName, SERVICE_ALL_ACCESS);

ControlService(schService, SERVICE_CONTROL_STOP, &ss);

DeleteService(schService);

CloseServiceHandle(schSCManager);

Заметим, что перед удалением мы останавливаем сервис.



Управление сервисами


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



Установка набора национальных параметров


Как мы только что сказали, в операционной системе Microsoft Windows NT для установки набора национальных параметров, используемых текущей задачей, эта задача должна вызвать функцию SetThreadLocale:

BOOL SetThreadLocale(

  LCID Locale); // идентификатор национального набора

Работа с функцией SetThreadLocale проста - вам достаточно передать ей нужный идентификатор в качестве параметра. При успехе функция возвратит значение TRUE, а при ошибке - FASLE. В последнем случае вы можете определить код ошибки с помощью функции GetLastError.

Как задавать идентификатор национального набора параметров?

Вы можете передать  функции SetThreadLocale либо одну из констант, либо значение, полученное от макрокоманды MAKELCID. Список констант приведен ниже.

Константа

Описание

LOCALE_SYSTEM_DEFAULT

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

LOCALE_USER_DEFAULT

Идентификатор, который используется по умолчанию для текущего пользователя, работающего в среде Microsoft Windows NT

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

DWORD MAKELCID(

  WORD  wLanguageID, // идентификатор национального языка

  WORD  wSortID);    // идентификатор метода сортировки

#define MAKELCID(lgid, srtid) \

  ((DWORD)((((DWORD)((WORD)(srtid))) << 16) \

  | ((DWORD)((WORD)(lgid)))))

Что касается идентификатора сортировки, то здесь вы должны указывать значение SORT_DEFAULT. А для создания идентификатора национального языка вам придется воспользоваться еще одной макрокомандой с именем MAKELANGID:

WORD MAKELANGID(

  USHORT usPrimaryLanguage, // первичный идентификатор языка

  USHORT usSubLanguage);    // вторичный идентификатор языка

#define MAKELANGID(usPrimaryLanguage, usSubLanguage) \

  ((((WORD)( usSubLanguage)) << 10) \


  | (WORD)( usPrimaryLanguage))

Первичный идентификатор задает национальный язык, а вторичный - его диалект или разновидность.

Для первичного идентификатора вы можете указать одно из следующих значений:

  

Первичный идентификатор

Национальный язык

LANG_AFRIKAANS

Африканский

LANG_ALBANIAN

Албанский

LANG_ARABIC

Арабский

LANG_BASQUE

Баский

LANG_BULGARIAN

Болгарский

LANG_BYELORUSSIAN

Белорусский

LANG_CATALAN

Каталанский

LANG_CHINESE

Китайский

LANG_CROATIAN

Хорватский

LANG_CZECH

Чехословацкий

LANG_DANISH

Датский

LANG_DUTCH

Нидерландский

LANG_ENGLISH

Английский

LANG_ESTONIAN

Эстонский

LANG_FINNISH

Финнский

LANG_FRENCH

Французский

LANG_GERMAN

Немецкий

LANG_GREEK

Греческий

LANG_HEBREW

Еврейский

LANG_HUNGARIAN

Венгерский

LANG_ICELANDIC

Исландский

LANG_INDONESIAN

Индонезийский

LANG_ITALIAN

Итальянский

LANG_JAPANESE

Японский

LANG_KOREAN

Корейский

LANG_LATVIAN

Латвийский

LANG_LITHUANIAN

Литовский

LANG_NEUTRAL

Нейтральный

LANG_NORWEGIAN

Норвежский

LANG_POLISH

Польский

LANG_PORTUGUESE

Португальский

LANG_ROMANIAN

Румынский

LANG_RUSSIAN

Русский

LANG_SLOVAK

Словацкий

LANG_SLOVENIAN

Словенский

LANG_SORBIAN

Сербский

LANG_SPANISH

Испанский

LANG_SWEDISH

Шведский

LANG_THAI

Таиландский

LANG_TURKISH

Турецкий

LANG_UKRANIAN

Украинский

Ниже мы привели список допустимых вторичных идентификаторов:

Вторичный идентификатор

Диалект

SUBLANG_CHINESE_HONGKONG

Гонконгский диалект китайского

SUBLANG_CHINESE_SIMPLIFIED

Упрощенный диалект китайского

SUBLANG_CHINESE_SINGAPORE

Сингапурский диалект китайского

SUBLANG_CHINESE_TRADITIONAL

Традиционный китайский

SUBLANG_DEFAULT

Диалект, который используется по умолчанию

SUBLANG_DUTCH

Нидерландский

SUBLANG_DUTCH_BELGIAN

Бельгийский диалект нидерландского

SUBLANG_ENGLISH_AUS

Австрийский диалект английского

SUBLANG_ENGLISH_CAN

Канадский диалект английского

SUBLANG_ENGLISH_EIRE

Ирландский диалект английского

SUBLANG_ENGLISH_NZ

Новозеландский диалект английского

SUBLANG_ENGLISH_UK

Британский диалект английского

SUBLANG_ENGLISH_US

Американский диалект английского

SUBLANG_FRENCH

Французский

SUBLANG_FRENCH_BELGIAN

Бельгийский диалект французского

SUBLANG_FRENCH_CANADIAN

Канадский диалект французского

SUBLANG_FRENCH_SWISS

Шведский диалект французского

SUBLANG_GERMAN

Немецкий

SUBLANG_GERMAN_AUSTRIAN

Австрийский диалект немецкого

SUBLANG_GERMAN_SWISS

Швейцарский диалект немецкого

SUBLANG_ITALIAN

Итальянский

SUBLANG_ITALIAN_SWISS

Швейцарский диалект итальянского

SUBLANG_NEUTRAL

Нейтральный

SUBLANG_PORTUGUESE

Португальский

SUBLANG_PORTUGUESE_BRAZILIAN

Бразильский диалект португальского

SUBLANG_SPANISH

Испанский

SUBLANG_SPANISH_MEXICAN

Мексиканский диалект испанского

SUBLANG_SPANISH_MODERN

Современный испанский

SUBLANG_SYS_DEFAULT

Диалект, который используется операционной системой по умолчанию

<


Заметим, что хотя приложение может указывать любые из перечисленных выше идентификаторов национальных языков, функция SetThreadLocale сможет установить только те, что были выбраны при установке операционной системы Microsoft Windows NT.

И еще одно замечание.

Если в качестве первичного идентификатора языка указать константу LANG_NEUTRAL, то  комбинации с идентификаторами SUBLANG_NEUTRAL, SUBLANG_DEFAULT и SUBLANG_SYS_DEFAULT будут иметь специальное значение, как это показано ниже:

Вторичный идентификатор в комбинации с LANG_NEUTRAL

Национальный язык

SUBLANG_NEUTRAL

Нейтральный язык

SUBLANG_DEFAULT

Язык, который установлен по умолчанию для текущего пользователя, работающего с Microsoft Windows NT

SUBLANG_SYS_DEFAULT

Язык, который используется операционной системой по умолчанию

Ниже мы привели пример использования функции SetThreadLocale для установки английского и русского наборов национальных параметров:

// Установка английского набора параметров

fRc = SetThreadLocale(MAKELCID(

  MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL), SORT_DEFAULT));

// Установка русского набора параметров

fRc = SetThreadLocale(MAKELCID(

  MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL), SORT_DEFAULT));


Установка сервиса


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

Прототип функции CreateService мы привели ниже:

SC_HANDLE CreateService(

  SC_HANDLE hSCManager,    // идентификатор базы данных системы

                           // управления сервисами

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

                           // для запуска

  LPCTSTR lpDisplayName,   // имя сервиса для отображения

  DWORD   dwDesiredAccess, // тип доступа к сервису

  DWORD   dwServiceType,   // тип сервиса

  DWORD   dwStartType,     // способ запуска сервиса

  DWORD   dwErrorControl,  // действия при ошибках в момент запуска

  LPCTSTR lpBinaryPathName,   // путь к загрузочному файлу сервиса

  LPCTSTR lpLoadOrderGroup,   // имя группы порядка загрузки

  LPDWORD lpdwTagId,          // адрес переменной для сохранения

                              // идентификатора тега

  LPCTSTR lpDependencies,     // адрес массива имен взаимосвязей

  LPCTSTR lpServiceStartName, // адрес имени пользователя, права

                    // которого будут применены для работы сервиса

  LPCTSTR lpPassword );       // адрес пароля пользователя

Через параметр hSCManager вы должны передать функции CreateService идентификатор базы данных системы управления сервисами, полученный от функции OpenSCManager, описанной выше.

Через параметры lpServiceName и lpDisplayName задаются, соответственно, имя сервиса, которое будет использовано для запуска и имя сервиса для отображения в списке установленных сервисов.

С помощью параметра dwDesiredAccess вы должны указать тип доступа, разрешенный при обращении к данному сервису. Здесь вы можете указать следующие значения:

Значение

Разрешенный тип доступа

SERVICE_ALL_ACCESS

Полный доступ

SERVICE_CHANGE_CONFIG

Изменение конфигурации сервиса функцией ChangeServiceConfig

SERVICE_ENUMERATE_DEPENDENTS

Просмотр сервиса в списке сервисов, созданных на базе данного сервиса функцией EnumDependentServices

SERVICE_INTERROGATE

Выдача сервису команды немедленного определения текущего состояния сервиса при помощи функции ControlService (эта функция будет описана ниже)

SERVICE_PAUSE_CONTINUE

Временная остановка сервиса или продолжение работы после временной остановки

SERVICE_QUERY_CONFIG

Определение текущей конфигурации функцией QueryServiceConfig

SERVICE_QUERY_STATUS

Определение текущего состояния сервиса функцией QueryServiceStatus

SERVICE_START

Запуск сервиса функцией StartService

SERVICE_STOP

Остановка сервиса выдачей соответствующей команды функцией ControlService

SERVICE_USER_DEFINE_CONTROL

Возможность передачи сервису команды, определенной пользователем, с помощью функции ControlService

<
Через параметр dwServiceType необходимо передать тип сервиса. Здесь вы можете указывать те же самые флаги, что и в поле dwServiceType структуры SERVICE_STATUS, описанной выше:

Флаг

Описание

SERVICE_WIN32_OWN_PROCESS

Сервис работает как отдельный процесс

SERVICE_WIN32_SHARE_PROCESS

Сервис работает вместе с другими сервисами в рамках одного и того же процесса

SERVICE_KERNEL_DRIVER

Сервис представляет собой драйвер операционной системы Microsoft Windows NT

SERVICE_FILE_SYSTEM_DRIVER

Сервис является драйвером файловой системы

SERVICE_INTERACTIVE_PROCESS 

Сервисный процесс может взаимодействовать с программным интерфейсом рабочего стола Desktop

В параметре dwStartType указывается один из следующих способов запуска сервиса:

Константа

Способ запуска

SERVICE_BOOT_START

Используется только для сервисов типа SERVICE_KERNEL_DRIVER или SERVICE_FILE_SYSTEM_DRIVER (драйверы). Указывает, что драйвер должен загружаться при загрузке операционной системы

SERVICE_SYSTEM_START

Аналогично предыдущему, но драйвер запускается при помощи функции IoInitSystem, не описанной в нашей книге

SERVICE_AUTO_START

Драйвер или обычный сервис, который запускается при загрузке операционной системы

SERVICE_DEMAND_START

Драйвер или обычный сервис, который запускается функцией StartService

SERVICE_DISABLED

Отключение возможности запуска драйвера или обычного сервиса

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

Значение

Реакция на ошибку

SERVICE_ERROR_IGNORE

Протоколирование ошибки в системном журнале и продолжение процедуры запуска сервиса

SERVICE_ERROR_NORMAL

Протоколирование ошибки в системном журнале без продолжения процедуры запуска сервиса

SERVICE_ERROR_SEVERE

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

SERVICE_ERROR_CRITICAL

Криичная ошибка. Сообщение при возможности записывается в системный журнал. Операция запуска отменяется, система перезапускается с с использованием работоспособной конфигурации

<


В параметре lpBinaryPathName вы должны указать полный путь к загрузочному файлу сервиса.

Через параметр lpLoadOrderGroup передается указатель на имя группы порядка загрузки сервиса. Сделав сервис членом одной из групп порядка загрузки, вы можете определить последовательность загрузки вашего сервиса относительно других сервисов. Список групп порядка загрузки находится в регистрационной базе данных:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ServiceGroupOrder

Если относительный порядок загрузки не имеет значения, укажите для параметра lpLoadOrderGroup значение NULL.

Параметр lpdwTagId используется только в том случае, если значение параметра lpLoadOrderGroup не равно NULL.

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

Последние два параметра функции lpServiceStartName и lpPassword указывают, соответственно, имя и пароль пользователя, с правами которого данный сервис будет работать в системе (имя указывается в форме “ИмяДомена\имяПользователя”). Если параметр lpServiceStartName указан как NULL, сервис подключится к системе как пользователь LocalSystem. При этом параметр lpPassword должен быть указан как NULL.

Ниже мы привели фрагмент исходного текста приложения, в котором выполняется установка сервиса из каталога  c:\ntbk2\src\service\small\debug:

schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

schService = CreateService(

  schSCManager, MYServiceName, MYServiceName,

  SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,

  SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,

  "c:\\ntbk2\\src\\service\\small\\debug\\small.exe",

  NULL, NULL, "", NULL, NULL);

CloseServiceHandle(schSCManager);


Установка соединения с каналом со стороны клиента


Для создания канала клиентский процесс может воспользоваться функцией CreateFile. Как вы помните, эта функция предназначена для работы с файлами, однако с ее помощью можно также открыть канал, указав его имя вместо имени файла. Забегая вперед, скажем, что функция CreateFile позволяет открывать не только файлы или каналы Pipe, но и другие системные ресурсы, например, устройства и каналы Mailslot.

Функция CreateFile была нами описана в предыдущем томе “Библиотеки системного программиста”, однако для удобства мы повторим прототип этой функции и ее краткое описание:

Итак, прототип функции CreateFile:

HANDLE CreateFile(

  LPCTSTR lpFileName,     // адрес строки имени файла

  DWORD  dwDesiredAccess, // режим доступа

  DWORD  dwShareMode, // режим совместного использования файла

  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // дескриптор

                                              // защиты

  DWORD  dwCreationDistribution, // параметры создания

  DWORD  dwFlagsAndAttributes,   // атрибуты файла

  HANDLE hTemplateFile);  // идентификатор файла с атрибутами

Раньше при работе с файлами через параметр lpFileName вы передавали этой функции адрес строки, содержащей имя файла, который вы собираетесь создать или открыть. Строка должна быть закрыта двоичным нулем. Если функция CreateFile работает с каналом Pipe, параметр lpFileName определяет имя канала.

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

Константа

Описание

0

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

GENERIC_READ

Разрешен доступ на чтение из файла или канала Pipe

GENERIC_WRITE

Разрешен доступ на запись в файл или канал Pipe

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


С помощью параметра dwShareMode задаются режимы совместного использования открываемого или создаваемого файла. Для этого параметра вы можете указать комбинацию следующих констант:

Константа

Описание

0

Совместное использование файла запрещено

FILE_SHARE_READ

Другие приложения могут открывать файл с помощью функции CreateFile для чтения

FILE_SHARE_WRITE

Аналогично предыдущему, но на запись

Через параметр lpSecurityAttributes необходимо передать указатель на дескриптор защиты или значение NULL, если этот дескриптор не используется. В наших приложениях мы не работаем с дескриптором защиты.

Параметр dwCreationDistribution определяет действия, выполняемые функцией CreateFile, если приложение пытается создать файл, который уже существует. Для этого параметра вы можете указать одну из следующих констант:

Константа

Описание

CREATE_NEW

Если создаваемый файл уже существует, функция CreateFile возвращает код ошибки

CREATE_ALWAYS

Существующий файл перезаписывается, при этом содержимое старого файла теряется

OPEN_EXISTING

Открывается существующий файл. Если файл с указанным именем не существует, функция CreateFile возвращает код ошибки

OPEN_ALWAYS

Если указанный файл существует, он открывается. Если файл не существует, он будет создан

TRUNCATE_EXISTING

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

Параметр dwFlagsAndAttributes задает атрибуты и флаги для файла.

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

Атрибут

Описание

FILE_ATTRIBUTE_ARCHIVE

Файл был архивирован (выгружен)

FILE_ATTRIBUTE_COMPRESSED

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

FILE_ATTRIBUTE_NORMAL

Остальные перечисленные в этом списка атрибуты не установлены

FILE_ATTRIBUTE_HIDDEN

Скрытый файл

FILE_ATTRIBUTE_READONLY

Файл можно только читать

FILE_ATTRIBUTE_SYSTEM

Файл является частью операционной системы

<


В дополнение к перечисленным выше атрибутам, через параметр dwFlagsAndAttributes вы можете передать любую логическую комбинацию флагов, перечисленных ниже:

Флаг

Описание

FILE_FLAG_WRITE_THROUGH

Отмена промежуточного кэширования данных для уменьшения вероятности потери данных при аварии

FILE_FLAG_NO_BUFFERING

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

FILE_FLAG_OVERLAPPED

Асинхронное выполнение чтения и записи. Во время асинхронного чтения или записи приложение может продолжать обработку данных

FILE_FLAG_RANDOM_ACCESS

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

FILE_FLAG_SEQUENTIAL_SCAN

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

FILE_FLAG_DELETE_ON_CLOSE

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

FILE_FLAG_BACKUP_SEMANTICS

Файл будет использован для выполнения операции выгрузки или восстановления. При этом выполняется проверка прав доступа

FILE_FLAG_POSIX_SEMANTICS

Доступ к файлу будет выполняться в соответствии со спецификацией POSIX

И, наконец, последний параметр hTemplateFile предназначен для доступа к файлу шаблона с расширенными атрибутами создаваемого файла.

В случае успешного завершения функция CreateFile возвращает идентификатор созданного или открытого файла (или каталога), а при работе с каналом Pipe - идентификатор реализации канала.

При ошибке возвращается значение INVALID_HANDLE_VALUE (а не NULL, как можно было бы предположить). Код ошибки можно определить при помощи функции GetLastError.

В том случае, если файл уже существует и были указаны константы CREATE_ALWAYS или OPEN_ALWAYS, функция CreateFile не возвращает код ошибки. В то же время в этой ситуации функция GetLastError возвращает значение ERROR_ALREADY_EXISTS.

Приведем фрагмент исходного текста клиентского приложения, открывающего канал с именем $MyPipe$ при помощи функции CreateFile:

char   szPipeName[256];

HANDLE hNamedPipe;

strcpy(szPipeName, "\\\\.\\pipe\\$MyPipe$");

hNamedPipe = CreateFile(

    szPipeName, GENERIC_READ | GENERIC_WRITE,

    0, NULL, OPEN_EXISTING, 0, NULL);

Здесь канал открывается как для записи, так и для чтения.


Установка соединения с каналом со стороны сервера


После того как серверный процесс создал канал, он может перейти в режим соединения с клиентским процессом. Соединение со стороны сервера выполняется с помощью функции ConnectNamedPipe.

Прототип функции ConnectNamedPipe представлен ниже:

BOOL ConnectNamedPipe(

  HANDLE hNamedPipe,      // идентификатор именованного канала

  LPOVERLAPPED lpOverlapped); // адрес структуры OVERLAPPED

Через первый параметр серверный процесс передает этой функции идентификатор канала, полученный от функции CreateNamedPipe.

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

В случае успеха функция ConnectNamedPipe возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить с помощью функции GelLastError.

В зависимости от различных условий функция ConnectNamedPipe может вести себя по разному.

Если параметр lpOverlapped указан как NULL, функция выполняется в синхронном режиме. В противном случае используется асинхронный режим.

Для канала, созданного в синхронном блокирующем режиме (с использованием константы PIPE_WAIT), функция ConnectNamedPipe переходит в состояние ожидания соединения с клиентским процессом. Именно этот режим мы будем использовать в наших примерах программ, исходные тексты которых вы найдете ниже.

Если канал создан в синхронном неблокирующем режиме, функция ConnectNamedPipe немедленно возвращает управление с кодом TRUE, если только клиент был отключен от данной реализации канала и возможно подключение этого клиента. В противном случае возвращается значение FALSE. Дальнейший анализ необходимо выполнять с помощью функции GetLastError. Эта функция может вернуть значение ERROR_PIPE_LISTENING (если к серверу еще не подключен ни один клиент), ERROR_PIPE_CONNECTED (если клиент уже подключен) или ERROR_NO_DATA (если предыдущий клиент отключился от сервера, но клиент еще не завершил соединение).

Ниже мы привели пример использования функции ConnectNamedPipe:

HANDLE hNamedPipe;

LPSTR  lpszPipeName = "\\\\.\\pipe\\$MyPipe$";

hNamedPipe = CreateNamedPipe(

    lpszPipeName,

    PIPE_ACCESS_DUPLEX,

    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,

    PIPE_UNLIMITED_INSTANCES,

    512, 512, 5000, NULL);

fConnected = ConnectNamedPipe(hNamedPipe, NULL);

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



Библиотеки системного программиста” мы начали


В предыдущем, 26 томе “ Библиотеки системного программиста” мы начали изучение одной из наиболее перспективных операционных систем -  Microsoft WindowsNT. Мы рассказали вам о системе управления памятью, мультизадачности, рассмотрели проблемы синхронизации параллельно рабтающих задач и процессов, а также начали изучение файловой системы NTFS. Однако все это - только капля в море. Для того чтобы научиться создавать современные приложения для Microsoft Windows NT, вам предстоит узнать еще много нового.
В первой главе нашей новой книги мы продолжим изучение файловой системы NTFS. Прежде всего мы расскажем вам о принципиально новом для Windows способе работы с файлами - отображении их на виртуальную память. Как вы увидите, этот способ значительно упрощает выполнение файловых операций, сводя их в большинстве случаев к работе с оперативной памятью. При этом приложение не вызывает явно функции для чтения данных из файла или для записи их в файл, так как за него это делает операционная система. Причем она это делает высокоэффективными средствами, которые используются для работы с виртуальной памятью.
Помимо файлов, отображаемых на память, в первой главе мы также продолжим изучение традиционных методов работы с файлами. В частности, мы приведем исходные тексты и описания приложений, использующих для работы с файловой системой и файлами средства, описанные нами в предыдущем томе “Библиотеки системного программиста”.
Вторая глава посвящена организации передачи данных между процессами, работающими параллельно. Сложность здесь заключается в том, что такие процессы работают в изолированных адресных пространствах. В результате процессы не могут просто создавать глобальные области памяти, доступные всем приложениям (как это можно было делать в среде Microsoft Windows версии 3.1), а вынуждены пользоваться специальными средствами взаимодействия процессов, встроенными в Microsoft Windows NT.
Мы рассмотрим такие средства, как специальные сообщения, предназначенные для передачи данных между процессами, каналы передачи данных, создаваемые между процессами. Кроме того, для передачи данных между процессами можно использовать файлы, отображаемые на память. Этот способ мы также изучим на примере конкретного приложения.
Третья глава посвящена библиотекам динамической компоновки DLL, которые работают в Microsoft Windows NT совсем не так, как это было в среде Microsoft Windows версии 3.1.
В четвертой главе мы расскажем о том, как создавать приложения Microsoft Windows NT, способные работать с несколькими национальными языками и научим программно переключать раскладки клавиатуры.
Пятая глава посвящена организации сервисных процессов, которые используются в качестве драйверов или для решения других задач, таких, например, как создание систем управления базами данных.
Примеры приложений, приведенные в книге, транслировались в системе разработки Microsoft Visual C++ версии 4.0. Вы таже можете воспользоваться версией 2.0, 4.1, 4.2 или 4.2 Enterprise Edition этой системы. Для того чтобы не набирать исходные тексты вручную и избежать ошибок, мы рекомендуем приобрести дискету с исходными текстами приложений, которая продается вместе с книгой.

Выдача команд сервису


Приложение или сервис может выдать команду сервису, вызвав функцию ControlService:

BOOL ControlService(

  SC_HANDLE hService,                // идентификатор сервиса

  DWORD     dwControl,               // код команды

  LPSERVICE_STATUS lpServiceStatus); // адрес структуры состояния

                                     // сервиса SERVICE_STATUS

В качестве кода команды вы можете указать один из следующих стандартных кодов:

         

Код

Команда

SERVICE_CONTROL_STOP

Остановка сервиса

SERVICE_CONTROL_PAUSE

Временная остановка сервиса

SERVICE_CONTROL_CONTINUE

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

SERVICE_CONTROL_INTERROGATE

Запрос текущего состояния сервиса

Дополнительно вы можете указывать коды команд, определенные вами. Они должны находиться в интервале значений от 128 до 255.



Выгрузка раскладки клавиатуры


Зная идентификатор загруженной ранее расклдаки клавиатуры, вы можете выгрузить эту раскладку из памяти. Для этого следует вызвать функцию UnloadKeyboardLayout:

BOOL UnloadKeyboardLayout(

  HKL  hkl); // идентификатор выгружаемой раскладки

В качестве единственного параметра этой функции следует передать идентификатор выгружаемой раскладки клавиатуры hkl.



Выполнение отображения файла в память


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

Прототип функции MapViewOfFile приведен ниже:

LPVOID MapViewOfFile(

  HANDLE hFileMappingObject,  // идентификатор отображения

  DWORD dwDesiredAccess,   // режим доступа

  DWORD dwFileOffsetHigh,  // смещение в файле (старшее слово)

  DWORD dwFileOffsetLow,   // смещение в файле (младшее слово)

  DWORD dwNumberOfBytesToMap);// количество отображаемых байт

Функция MapViewOfFile создает окно размером dwNumberOfBytesToMap байт, которое смещено относительно начала файла на количество байт, заданное параметрами dwFileOffsetHigh и dwFileOffsetLow. Если задать значение параметра dwNumberOfBytesToMap равное нулю, будет выполнено отображение всего файла.

Смещение нужно задавать таким образом, чтобы оно попадало на границу минимального пространства памяти, которое можно зарезервировать. Значение 64 Кбайта подходит в большинстве случаев.

Более точно гранулярность памяти можно определить при помощи функции GetSystemInfo. Этой функции в качестве единственного параметра необходимо передать указатель на структуру типа SYSTEM_INFO, определенную следующим образом:

typedef struct _SYSTEM_INFO

{

  union {

    DWORD  dwOemId; // зарезервировано

    struct

    {

      WORD wProcessorArchitecture; // архитектура системы

      WORD wReserved; // зарезервировано

    };

  };

  DWORD  dwPageSize;                  // размер страницы

  LPVOID lpMinimumApplicationAddress; // минимальный адрес,

    // доступный приложениям и библиотекам DLL

  LPVOID lpMaximumApplicationAddress; // максимальный адрес,

    // доступный приложениям и библиотекам DLL


  DWORD  dwActiveProcessorMask;   // маски процессоров

  DWORD  dwNumberOfProcessors;    // количество процессоров

  DWORD  dwProcessorType;         // тип процессора

  DWORD  dwAllocationGranularity; // гранулярность памяти

  WORD   wProcessorLevel;         // уровень процессора

  WORD   wProcessorRevision;      // модификация процессора

} SYSTEM_INFO;

Функция заполнит поля этой структуры различной информацией о системе. В частности, в поле dwAllocationGranularity будет записан минимальный размер резервируемой области памяти.

Вернемся к описанию функции MapViewOfFile.

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

Значение

Описание

FILE_MAP_WRITE

Доступ на запись и чтение. При создании отображения функции CreateFileMapping необходимо указать тип защиты PAGE_READWRITE

FILE_MAP_READ

Доступ только на чтение. При создании отображения необходимо указать тип защиты PAGE_READWRITE или PAGE_READ

FILE_MAP_ALL_ACCESS

Аналогично FILE_MAP_WRITE

FILE_MAP_COPY

Доступ для копирования при записи. При создании отображения необходимо указать атрибут PAGE_WRITECOPY 

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

При необходимости приложение может запросить отображение в заранее выделенную область адресного пространства. Для этого следует воспользоваться функцией MapViewOfFileEx:

LPVOID MapViewOfFileEx(

  HANDLE hFileMappingObject,  // идентификатор отображения

  DWORD dwDesiredAccess,      // режим доступа

  DWORD dwFileOffsetHigh,  // смещение в файле (старшее слово)

  DWORD dwFileOffsetLow,   // смещение в файле (младшее слово)

  DWORD dwNumberOfBytesToMap, // количество отображаемых байт

  LPVOID lpBaseAddress);      // предполагаемый адрес

                              // для отображения файла



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

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

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

Приложение может создавать несколько отображений для разных или одинаковых фрагментов одного и того же файла.


Загрузка раскладки клавиатуры


С помощью функции LoadKeyboardLayout вы можете загрузить новую раскладку клавиатуры:

HKL LoadKeyboardLayout(

  LPCTSTR pwszKLID, // адрес буфера названия раскладки

  UINT    Flags);   // флаги режима работы функции

При помощи параметра pwszKLID задается имя загружаемой раскладки. Это имя должно задаваться в виде текстовой строки, содержащей значение идентификатора национального языка в текстовом виде. Для загрузки, например, американской раскладки клавиатуры необходимо задать строку 00000409, а для загрузки русской раскладки - строку 00000419.

   Параметр Flags задает режимы работы функции LoadKeyboardLayout и может иметь следующие значения:

Константа

Описание

KLF_ACTIVATE

Если указанная раскладка клавиатуры не была загружена ранее, она загружается и становится активной

KLF_REORDER

В этом случае раскладка циклически сдвигается в списке загруженных раскладок

KLF_SUBSTITUTE_OK

Использование альтернативной раскладки клавиатуры, указанной в регистрационной базе данных (ключ HKEY_CURRENT_USER\Keyboard Layout\Substitutes)

KLF_UNLOADPREVIOUS

Используется вместе с флагом KLF_ACTIVATE и только тогда, когда указанная раскладка уже загружена. В этом случае загруженная ранее раскладка клавиатуры выгружается



Закрывание идентификатора канала


Если канал больше не нужен, после отключения от клиентского процесса серверный и клиентский процессы должны закрыть его идентификатор функцией CloseHandle:

CloseHandle(hNamedPipe);



Запись данных в канал


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

HANDLE hNamedPipe;

DWORD  cbWritten;

char   szBuf[256];

WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1,

  &cbWritten, NULL);

Через первый параметр функции WriteFile передается идентификатор реализации канала. Через второй параметр передается адрес буфера, данные из которого будут записаны в канал. Размер этого буфера указывается при помощи третьего параметра. Предпоследний параметр используется для определения количества байт данных, действительно записанных в канал. И, наконец, последний параметр задан как NULL, поэтому запись будет выполняться в синхронном режиме.

Учтите, что если канал был создан для работы в блокирующем режиме, и функция WriteFile работает синхронно (без использования вывода с перекрытием), то эта функция не вернет управление до тех пор, пока данные не будут записаны в канал.



Запись сообщений в канал Mailslot


Запись сообщений в канал Mailslot выполняет клиентский процесс, вызывая для этого функцию WriteFile. С этой функцией вы уже имели дело:

HANDLE hMailslot;

char   szBuf[512];

DWORD  cbWritten;

WriteFile(hMailslot, szBuf, strlen(szBuf) + 1,

  &cbWritten, NULL);

В качестве первого параметра этой функции необходимо передать идентификатор канала Mailslot, полученный от функции CreateFile.

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



Запуск сервиса


Для запуска сервиса вы должны использовать функцию StartService:

BOOL StartService(

  SC_HANDLE schService,        // идентификатор сервиса

  DWORD     dwNumServiceArgs,  // количество аргументов

  LPCTSTR   *lpszServiceArgs); // адрес массива аргументов

Через параметр schService вы должны передать функции StartService идентификатор сервиса, полученный от функции OpenService.

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

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

schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

schService = OpenService(

  schSCManager, MYServiceName, SERVICE_ALL_ACCESS);

StartService(schService, 0, NULL);

CloseServiceHandle(schSCManager);