Переключение раскладки клавиатуры
Последняя функция, которую мы рассмотрим в этом разделе и которая предназначена для работы с раскладками клавиатуры, называется 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) |
Ниже мы привели пример использования функции 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 |
Поле 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. В этом случае предполагается, что размер файла изменяться не будет.
Через параметр 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 |
Язык, который используется операционной системой по умолчанию |
// Установка английского набора параметров
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 |
Константа |
Способ запуска |
SERVICE_BOOT_START |
Используется только для сервисов типа SERVICE_KERNEL_DRIVER или SERVICE_FILE_SYSTEM_DRIVER (драйверы). Указывает, что драйвер должен загружаться при загрузке операционной системы |
SERVICE_SYSTEM_START |
Аналогично предыдущему, но драйвер запускается при помощи функции IoInitSystem, не описанной в нашей книге |
SERVICE_AUTO_START |
Драйвер или обычный сервис, который запускается при загрузке операционной системы |
SERVICE_DEMAND_START |
Драйвер или обычный сервис, который запускается функцией StartService |
SERVICE_DISABLED |
Отключение возможности запуска драйвера или обычного сервиса |
Значение |
Реакция на ошибку |
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 |
Аналогично предыдущему, но на запись |
Параметр dwCreationDistribution определяет действия, выполняемые функцией CreateFile, если приложение пытается создать файл, который уже существует. Для этого параметра вы можете указать одну из следующих констант:
Константа |
Описание |
CREATE_NEW |
Если создаваемый файл уже существует, функция CreateFile возвращает код ошибки |
CREATE_ALWAYS |
Существующий файл перезаписывается, при этом содержимое старого файла теряется |
OPEN_EXISTING |
Открывается существующий файл. Если файл с указанным именем не существует, функция CreateFile возвращает код ошибки |
OPEN_ALWAYS |
Если указанный файл существует, он открывается. Если файл не существует, он будет создан |
TRUNCATE_EXISTING |
Если файл существует, он открывается, после чего длина файла устанавливается равной нулю. Содержимое старого файла теряется. Если же файл не существует, функция CreateFile возвращает код ошибки |
При этом можно использовать любые логические комбинации следующих атрибутов (кроме атрибута 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 |
В случае успешного завершения функция 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 |
При необходимости приложение может запросить отображение в заранее выделенную область адресного пространства. Для этого следует воспользоваться функцией 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);