Функция WndProc_OnCreate
Эта функция обрабатывает сообщение WM_CREATE, поступающее в функцию окна при его создании.
Вначале функция WndProc_OnCreate вызывает функцию GetDiskInfo, которая получает информацию о логических дисках, имеющихс в системе, и сохраняет ее в массиве структур DISKINFO.
Далее обработчик сообщения WM_CREATE определяет размеры внутренней области главного окна приложения, инициализирует библиотеку стандартных органов управления и создает орган управления List View на базе предопределенного класса окна WC_LISTVIEW, вызывая для этого функцию CreateWindowEx.
На следующем этапе приложение создает два списка изображений. Первый из них (с идентификатором himlSmall) будет содержать пиктограммы дисковых устройств маленького размера, а второй (с идентификатором himlLarge) - эти же пиктограммы, но стандартного размера.
С помощью макрокоманды ImageList_AddIcon в эти списки добавляются пиктограммы с изображениями дисков. Каждое такое изображение хранится в списке под собственным номером. Например, под номером 0 хранятся пиктограммы с идентификатором IDI_DREMOVE и IDI_DREMOVSM (сетевые устройства), под номером 1 - пиктограммы с идентификатором IDI_DFIXED и IDI_DFIXEDSM (диск с несменным носителем данных) и так далее. Номера пиктограмм используются при формировании строк списка, отображаемого при помощи органа управления List View.
После добавления всех пиктограмм сформированные списки подключаются к органу управления List View с помощью макрокоманды ListView_SetImageList.
Далее обработчик сообщения WM_CREATE вставляет столбцы, задавая для них различное выравнивание текста. Текст в столбцах Drive, Volume name и File system выравнивается по левой границе, а текст в столбцах File name length, Total Space и Free Space - по правой.
Вставка строк выполняется в цикле с помощью макрокоманды ListView_InsertItem.
Более подробную информацию о работе с органом управления List View вы можете найти в 22 томе “Библиотеки системного программиста”, посвященному программированию для операционной системы Microsoft Windows 95.
Функция WndProc_OnCreate вызывается при создании главного окна приложения. Эта функция получает контекст отображения, выбирает в него шрифт с фиксированной шириной букв и определяет его метрики. Ширина и высота символов сохраняются, соответственно, в глобальных переменных cxChar и cyChar. Эти значения используются для вычисления размеров главного окна приложения.
В заверешении функция WndProc_OnCreate записывает в глобальный буфер szBuf строку <Unknown>:
strcpy(szBuf, (LPSTR)"<Unknown>");
Эта строка будет отображаться в главном окне приложения до тех пор, пока вы не запустите приложение STIME.
При создании главного окна приложения STIME обработчик сообщения WM_CREATE создает таймер с периодом работы 1 сек, вызывая для этого функцию SetTimer:
SetTimer(hWnd, CLOCK_TIMER, 1000, NULL);
Созданный таймер будет иметь идентификатор CLOCK_TIMER.
Функция WndProc_OnDestroy
Эта функция вызывается при уничтожении главного окна приложения для обработки сообщения WM_DESTROY. Функция WndProc_OnDestroy вызывает функцию PostQuitMessage, в результате чего цикл обработки сообщений завершает свою работу.
При уничтожении главного окна приложения обработчик сообщения WM_DESTROY удаляет орган управления List View и освобождает память, заказанную у операционной системы для списка строк с именами дисков и массива структур DISKINFO.
Далее обработчик вызывает функцию PostQuitMessage, инициируя завершения цикла обработки сообщений.
Эта функция завершает цикл обработки сообщений, вызывая для этого функцию PostQuitMessage.
Перед завершением своей работы приложение STIME посылает приложению RCLOCK строку <Terminated>, которая будет отображена в окне приложения RCLOCK. Для посылки используется только что описанная нами методика:
cd.lpData = szTerminated;
cd.cbData = strlen(szTerminated) + 1;
SendMessage(hWndServer, WM_COPYDATA,
(WPARAM)hWnd, (LPARAM)&cd);
Далее функция WndProc_OnDestroy удаляет таймер и завершает цикл обработки сообщений, вызывая для этого функцию PostQuitMessage.
Эта функция вызывается при уничтожении главного окна приложения. Ее задачей является завершение цикла обработки сообщений, для чего она вызывает функцию PostQuitMessage.
Функция WndProc_OnNotify
Функция WndProc_OnNotify обрабатывает извещения, поступающие от органа управления List View в главное окно приложения.
Обработчик извещения LVN_GETDISPINFO выбирает информацию из элементов массива структур DISKINFO, и предоставляет ее для отображения в окне органа управления List View.
Для нас сейчас больший интерес представляет обработчик извещения NM_DBLCLK, который получает управление, когда пользователь делает двойной щелчок левой клавишей мыши по пиктограмме дискового устройства в окне органа управления List View.
Вначале с помощью макрокоманды ListView_GetNextItem обработчик извещения NM_DBLCLK определяет номер выбранного элемента и записывает его в локальную переменную index.
Далее вызывается функция GetVolumeInformation, с помощью которой выполняется заполнение соответствующего элемента массива структур DISKINFO. Значение, записанное в переменную index служит при этом индексом в массиве структур.
Заполнение структуры завершается функцией GetDiskFreeSpace, с помощью которой определяется такая информация, как общий объем диска в байтах и объем свободного пространства на диске. Эта функция сохраняет в локальных переменных dwClusters и dwFreeClusters, соответственно, общее количество кластеров, имеющихся на диске, и количество свободных кластеров.
В локальные переменные dwSectors и dwBytes записывается количество секторов в одном кластере и размер сектора в байтах. Эти значения используются для вычисления общего и свободного объема диска исходя из общего количества кластеров и количества свободных кластеров.
После обновления элемента массива структур DISKINFO, соответтсвующего выбранному диску, обработчик извещения NM_DBLCLK отображает на экране диалоговую панель с такой информацией, как название диска, имя файловой системы, серийный номер диска и системные флаги.
Перед выводом указанной диалоговой панели мы перерисовываем содержимое окна органа управления List View, для чего вызываем функцию InvalidateRect.
Зачем мы это делаем?
При запуске приложения мы получаем список логических дисковых устройств и определяем их параметры, однако только для устройств с несменными носителями данных. В противном случае операционная система попытается выполнить обращение к устройству (например, к НГМД) и, если вы заранее не вставите в него носитель данных, на экране появится сообщение об ошибке.
Если же вы вначале вставите носитель в устройство, а затем в окне нашего приложения DiskInfo сделаете двойной щелчок левой клавишей мыши по пиктограмме устройства, обработчик извещения NM_DBLCLK получит параметры этого устройства и сохранит их в соответствующем элементе массива структур DISKINFO. Однако само по себе это не вызовет никаких изменений в списке параметров устройств, который отображается нашим приложением.
Вызывая функцию InvalidateRect, мы выполняем перерисовку главного окна приложения и его дочернего окна - окна органа управления List View. При этом обработчик извещения LVN_GETDISPINFO получает и отображает в окне обновленную информацию о параметрах устройства.
Функция WndProc_OnPaint
При обработке сообщения WM_PAINT функция WndProc_OnPaint отображает в главном окне приложения RCLOCK текстовую строку, записанную в буфере szBuf. Для рисования строки используется функция DrawText, так как с ее помощью легко выполнить центровку строки в окне по вертикали и горизонтали.
Функция WndProc_OnSize
В задачу функции WndProc_OnSize, обрабатывающей сообщение WM_SIZE, входит изменение размеров органа управления List View при изменении размеров главного окна приложения.
Функция WndProc_OnTimer
Функция WndProc_OnTimer получает управление примерно один раз в секунду, обрабатывая сообщения WM_TIMER, поступающие от таймера с идентификатором CLOCK_TIMER.
Прежде всего эта функция формирует строку символов с текущим временем в формате “ЧЧ:ММ:СС”, вызывая библиотечные фукнции системы разработки time и localtime:
time(&t);
ltime = localtime(&t);
wsprintf(szBuf, "%02d:%02d:%02d",
ltime->tm_hour, ltime->tm_min,ltime->tm_sec);
Затем выполняется инициализация полей структуры cd типа COPYDATASTRUCT. В процессе инициализации мы записываем адрес буфера, содержащего строку символов, в поле lpData, а размер этого буфера (с учетом двоичного нуля, закрывающего строку) - в поле cbData:
cd.lpData = szBuf;
cd.cbData = strlen(szBuf) + 1;
Поле dwData не используется.
Заметим, что хотя серверное приложение RCLOCK при копировании полученных данных не использует поле cbData (так как мы передаем строку символов, закрытую двоичным нулем), при подготовке сообщения WM_COPYDATA к отправке необходимо заполнить оба поля: и lpData, и cbData.
Посылка сообщения WM_COPYDATA выполняется очень просто:
SendMessage(hWndServer, WM_COPYDATA,
(WPARAM)hWnd, (LPARAM)&cd);
Сообщение посылается в окно с идентификатором hWndServer, который был определен в функции WinMain. В качестве параметра wParam вместе с этим сообщением необходимо передать идентификатор окна посылающего приложения, то есть идентификатор окна приложения STIME. Через параметр lParam передается адрес заполненной структуры cd типа COPYDATASTRUCT.
Глобальные переменные и определения
В начале файла dllcall.c (листинг 3.4) мы определили тип MYDLLPROC как указатель на функцию, возвращающую знначение HWND и принимающую один параметр типа LPSTR.
Далее в области глобальных переменных мы определили переменную GetAppWindow с типом MYDLLPROC:
MYDLLPROC GetAppWindow;
Эта переменная будет использована для хранения указателя на функцию из динамически загружаемой DLL-библиотеки.
Глобальный буфер szWindowTitle предназначен для хранения заголовка окна, поиск которого будет выполнять наше приложение.
В глобальной переменной hDLL хранится идентификатор динамически загруженной DLL-библиотеки.
Имена каналов
Имена каналов в общем случае имеют следующий вид:
\\ИмяСервера\pipe\ИмяКанала
Если процесс открывает канал, созданный на другой рабочей станции, он должен указать имя сервера. Если же процесс создает канал или открывает канал на своей рабочей станции, вместо имени указывается символ точки:
\\.\pipe\ИмяКанала
В любом случае процесс может создать канал только на той рабочей станции, где он запущен, поэтому при создании канала имя сервера никогда не указывается.
Именованные и анонимные каналы
Существуют две разновидности каналов Pipe - именованные (Named Pipes) и анонимные (Anonymous Pipes).
Как видно из названия, именованным каналам при создании присваивается имя, которое доступно для других процессов. Зная имя какой-либо рабочей станции в сети, процесс может получить доступ к каналу, созданному на этой рабочей станции.
Анонимные каналы обычно используются для организации передачи данных между родительскими и дочерними процессами, запущенными на одной рабочей станции или на “отдельно стоящем” компьютере.
Импортирование функций
Когда вы используете статическую компоновку, то включаете в файл проекта приложения соответствующий lib-файл, содержащий нужную вам библиотеку объектных модулей. Такая библиотека содержит исполняемый код модулей, который на этапе статической компоновки включается в exe-файл загрузочного модуля.
Если используется динамическая компоновка, в загрузочный exe-файл приложения записывается не исполнимый код функций, а ссылка на соответствующую DLL-библиотеку и функцию внутри нее. Как мы уже говорили, эта ссылка может быть организована с использованием либо имени функции, либо ее порядкового номера в DLL-библиотеке.
Откуда при компоновке приложения редактор связей узнает имя DLL-библиотеки, имя или порядковый номер экспортируемой функции? Для динамической компоновки функции из DLL-библиотеки можно использовать различные способы.
Инициализация DLL-библиотеки в среде Microsoft Windows NT
В 32-разрядных DLL-библиотеках операционной системы Microsoft Windows NT вместо функций LibMain и WEP используется одна функция DLLEntryPoint, которая выполняет все необходимые задачи по инициализации библиотеки и при необходимости освобождает заказанные ранее ресурсы (имя функции инициализации может быть любым).
Функции LibMain и WEP вызываются только один раз при загрузке библиотеки в память и при ее выгрузке. В отличие от них, функция DLLEntryPoint вызывается всякий раз, когда выполняется инициализация процесса или задачи, обращающихся к функциям библиотеки, а также при явной загрузке и выгрузке библиотеки функциями LoadLibrary и FreeLibrary.
Ниже мы привели прототип функции DLLEntryPoint:
BOOL WINAPI DllEntryPoint(
HINSTANCE hinstDLL, // идентификатор модуля DLL-библиотеки
DWORD fdwReason, // код причины вызова функции
LPVOID lpvReserved); // зарезервировано
Через параметр hinstDLL функции DLLEntryPoint передается идентификатор модуля DLL-библиотеки, который можно использовать при обращении к ресурсам, расположенным в файле этой библиотеки.
Что же касается параметра fdwReason, то он зависит от причины, по которой произошел вызов функции DLLEntryPoint. Этот параметр может принимать следующие значения:
Значение | Описание | ||
DLL_PROCESS_ATTACH | Библиотека отображается в адресное пространство процесса в результате запуска процесса или вызова функции LoadLibrary | ||
DLL_THREAD_ATTACH | Текущий процесс создал новую задачу, после чего система вызывает функции DLLEntryPoint всех DLL-библиотек, подключенных к процессу | ||
DLL_THREAD_DETACH | Этот код причины передается функции DLLEntryPoint, когда задача завершает свою работу нормальным (не аварийным) способом | ||
DLL_PROCESS_DETACH | Отображение DLL?библиотеки в адресное пространство отменяется в результате нормального завершения процесса или вызова функции FreeLibrary |
Параметр lpvReserved зарезервирован. В SDK, тем не менее, сказано, что значение параметра lpvReserved равно NULL во всех случаях, кроме двух следующих:
когда параметр fdwReason равен DLL_PROCESS_ATTACH и используется статическая загрузка DLL-библиотеки;
когда параметр fdwReason равен DLL_PROCESS_DETACH и функция DLLEntryPoint вызвана в результате завершения процесса, а не вызова функции FreeLibrary
В процессе инициализации функция DLLEntryPoint может отменить загрузку DLL-библиотеки. Если код причины вызова равен DLL_PROCESS_ATTACH, функция DLLEntryPoint отменяет загрузку библиотеки, возвращая значение FALSE. Если же инициализация выполнена успешно, функция должна возвратить значение TRUE.
В том случае, когда приложение пыталось загрузить DLL-библиотеку функцией LoadLibrary, а функция DLLEntryPoint отменила загрузку, функция LoadLibrary возвратит значение NULL. Если же приложение выполняет инициализацию DLL-библиотеки неявно, при отмене загрузки библиотеки приложение также не будет загружено для выполнения.
Приведем пример функции инициализации DLL-библиотеки:
BOOL WINAPI DLLEntryPoint(
HMODULE hModule, // идентификатор модуля
DWORD fdwReason, // причина вызова функции DLLEntryPoint
LPVOID lpvReserved)// зарезервировано
{
switch(fdwReason)
{
// Подключение нового процесса
case DLL_PROCESS_ATTACH:
{
// Обработка подключения процесса
. . .
break;
}
// Подключение новой задачи
case DLL_THREAD_ATTACH:
{
// Обработка подключения новой задачи
. . .
break;
}
// Отключение процесса
case DLL_PROCESS_DETACH:
{
// Обработка отключения процесса
. . .
break;
}
// Отключение задачи
case DLL_THREAD_DETACH:
{
// Обработка отключения задачи
. . .
break;
}
}
return TRUE;
}
Исходные тексты DLL-библиотеки DLLDEMO
В качестве примера приведем исходные тексты простейшей DLL-библиотеки DLLDemo.DLL, в которой определены всего две функции. Первая из них - это функция инициализации DLLEntryPoint, а вторая - функция FindApplicationWindow.
Функция инициализации DLLEntryPoint в нашем случае не выполняет никакой работы, однако когда она получает управление, на экране появляется одно из четырех сообщений (в зависимости от значения кода причины вызова). Таким образом, вы сможете проследить ход инициализации DLL-библиотеки при ее отображении в адресное пространство процессов, а также при отключении DLL-библиотеки от процессов.
В задачу функции FindApplicationWindow входит поиск главного окна приложения по заголовку этого окна. В случае успеха функция FindApplicationWindow возвращает идентификатор первого найденного окна с подходящим заголовком, а при неудаче - значение NULL. Вы можете использовать эту функцию для проверки, запущено ли указанное вами приложение, или нет.
Исходный текст DLL-библиотеки представлен в листинге 3.1.
Листинг 3.1. Файл dlldemo\dlldemo.c
// ==================================================
// DLL-библиотека DLLDemo.DLL
// Поиск окна по заданному заголовку
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#include <windows.h>
#include <windowsx.h>
#include "dlldemo.h"
// Глобальная переменная, в которую записывается
// идентификатор найденного окна или значение NULL,
// если окно с заданным заголовком не найдено
HWND hwndFound;
// -------------------------------------------------
// Функция DLLEntryPoint
// Точка входа DLL-библиотеки
// -------------------------------------------------
BOOL WINAPI DLLEntryPoint(
HMODULE hModule, // идентификатор модуля
DWORD fdwReason, // причина вызова функции DLLEntryPoint
LPVOID lpvReserved) // зарезервировано
{
switch(fdwReason)
{
// Подключение нового процесса
case DLL_PROCESS_ATTACH:
{
MessageBox(NULL, "Process attached", "DLL Demo", MB_OK);
break;
}
// Подключение новой задачи
case DLL_THREAD_ATTACH:
{
MessageBox(NULL, "Thread attached", "DLL Demo", MB_OK);
break;
}
// Отключение процесса
case DLL_PROCESS_DETACH:
{
MessageBox(NULL, "Process detached", "DLL Demo", MB_OK);
break;
}
// Отключение задачи
case DLL_THREAD_DETACH:
{
MessageBox(NULL, "Thread detached", "DLL Demo", MB_OK);
break;
}
}
return TRUE;
}
// -------------------------------------------------
// Функция FindApplicationWindow
// Поиск главного окна приложения по его заголовку
// -------------------------------------------------
HWND FindApplicationWindow(LPSTR lpszWindowTitle)
{
// Запускаем цикл поиска окна с заголовком,
// адрес которого передан функции через
// параметр lpszWindowTitle
EnumWindows(EnumWindowsProc, (LPARAM)lpszWindowTitle);
// Возвращаем значение глобальной переменной hwndFound,
// которое устанавливается функцией обратного вызова
// EnumWindowsProc в зависимости от результата поиска
return hwndFound;
}
// -------------------------------------------------
// Функция EnumWindowsProc
// -------------------------------------------------
BOOL CALLBACK EnumWindowsProc(
HWND hwnd, // идентификатор родительского окна
LPARAM lParam) // адрес строки заголовка окна
{
// Буфер для хранения заголовка окна
char szBuf[512];
// Получаем заголовок окна
GetWindowText(hwnd, szBuf, 512);
// Сравниваем заголовок со строкой, адрес которой
// передан в функцию EnumWindowsProc через параметр lParam
if(!strcmp((LPSTR)lParam, szBuf))
{
// Если заголовок совпал, сохраняем идентификатор
// текущего окна в глобальной переменной hwndFound
hwndFound = hwnd;
// Завершаем цикл просмотра окон
return FALSE;
}
// Если заголовок не совпал, продолжаем поиск
else
{
// Записываем в глобальную переменную hwndFound
// значение NULL. Это признак того, что окно
// с заданным заголовком не было найдено
hwndFound = NULL;
// Для продолжения поиска возвращаем значение TRUE
return TRUE;
}
}
В файле dlldemo.h (листинг 3.2) находятся прототипы функций, определенных в нашей DLL-библиотеке.
Листинг 3.2. Файл dlldemo\dlldemo.h
BOOL WINAPI DLLEntryPoint(HMODULE hModule,
DWORD fdwReason, LPVOID lpvReserved);
HWND FindApplicationWindow(LPSTR lpszWindowTitle);
BOOL CALLBACK EnumWindowsProc(
HWND hwnd, // идентификатор родительского окна
LPARAM lParam); // произвольное значение
Файл определения модуля dlldemo.def DLL-библиотеки представлен в листинге 3.3.
Листинг 3.3. Файл dlldemo\dlldemo.def
EXPORTS
FindApplicationWindow @1
Итак, займемся исходными текстами DLL-библиотеки.
В области глобальных переменных определена переменная hwndFound. В эту переменную будет записан идентификатор найденного окна или значение NULL, если поиск окончился неудачно.
Функция DLLEntryPoint предназначена для инициализации библиотеки. В нашем случае функция инициализации просто выводит на экран различные сообщения в зависимости от кода причины вызова.
Функция FindApplicationWindow ищет главное окно приложения по заголовку этого окна, адрес которого передается ей через параметр lpszWindowTitle.
Для поиска окна мы использовали функцию EnumWindows. В качестве первого параметра этой функции передается адрес функции обратного вызова, которая будет использована для сравнения заголовков всех окон с заданным, а в качестве второго - адрес искомого заголовка:
EnumWindows(EnumWindowsProc, (LPARAM)lpszWindowTitle);
Если функция EnumWindowsProc найдет окно, она запишет его идентификатор в глобальную переменную hwndFound. Если же приложение с таким заголовком не запущено, в эту переменную будет записано значение NULL.
Функции обратного вызова EnumWindowsProc ( имя функции может быть любым) передаются два параметра: идентификатор окна и 32-разрядное значение, которое передавалось функции EnumWindows в качестве второго параметра. В нашем случае это адрес заголовка искомого окна:
BOOL CALLBACK EnumWindowsProc(
HWND hwnd, // идентификатор родительского окна
LPARAM lParam) // адрес строки заголовка окна
{
char szBuf[512];
GetWindowText(hwnd, szBuf, 512);
if(!strcmp((LPSTR)lParam, szBuf))
{
hwndFound = hwnd;
return FALSE;
}
else
{
hwndFound = NULL;
return TRUE;
}
}
После вызова функции EnumWindows функция EnumWindowsProc будет вызываться в цикле для окна вернего уровня каждого запущенного приложения.
Зная идентификатор окна (который передается функции EnumWindowsProc через первый параметр), мы с помощью функции GetWindowText получаем заголовок окна и записываем его в буфер szBuf. Затем этот заголовок сравнивается с заданным при помощи функции strcmp. Адрес заданного заголовка мы получаем через параметр lParam.
Функция обратного вызова, адрес которой указан в первом параметре функции EnumWindows, может вернуть значение FALSE или TRUE.
В первом случае цикл просмотра окон заканчивается и функция EnumWindows возвращает управление вызвавшему ее приложению. Во втором случае просмотр окон будет продолжен до тех пор, пока функции обратного вызова не будут переданы идентификаторы главных окон всех запущенных приложений.
Если окно найдено, функция обратного вызова записывает его идентификатор в глобальную переменную hwndFound и возвращает значение FALSE, после чего поиск продолжается. Если же заголовки не совпадают, в эту переменную записывается значение NULL, после чего для продолжения поиска функция EnumWindowsProc возвращает значение TRUE.
Несколько слов о настройке проекта DLL-библиотеки DLLDemo.DLL.
При создании проекта DLL-библиотеки “с нуля” вы должны указать тип рабочего пространства проекта (Project Workspace) как Dynamic-Link Library (рис. 3.5).
Рис. 3.5. Создание нового проекта для DLL-библиотеки
Если в вашей DLL-библиотеке определена точка входа (функция инициализации), то в параметрах проекта вы должны указать ее имя. Для этого в системе Microsoft Visual C++ версии 4.0 выберите из меню Build строку Settings. На экране появится диалоговая панель Project Settings, показанная на рис. 3.6.
Рис. 3.6. Диалоговая панель Project Settings
Пользуясь кнопками в правом верхнем углу этой диалоговой панели, откройте страницу Link. Затем выберите из списка Category строку Output. В поле Entry-point symbol введите имя вашей функции инициализации и нажмите кнопку OK.
Если вы выполняете редактирование загрузочного модуля DLL-библиотеки в пакетном режиме, при запуске редактора связей укажите параметр /entry:”ИмяФункцииИнициализации”.
Напомним, что для функции инициализации DLL-библиотеки вы можете выбрать любое имя. Нужно только указать его в проекте или в параметрах редактора связей.
Исходные тексты приложения RCLOCK
Главный файл исходных текстов приложения RCLOCK представлен в листинге 2.3.
Листинг 2.3. Файл rclock/rclock.c
// ==================================================
// Приложение RCLOCK (серверное)
// Демонстрация использования сообщения WM_COPYDATA
// для передачи данных между процессами
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include "resource.h"
#include "afxres.h"
#include "rclock.h"
HINSTANCE hInst;
char szAppName[] = "RclockApp";
char szAppTitle[] = "Remote Clock";
// Метрики шрифта с фиксированной шириной символов
LONG cxChar, cyChar;
RECT rc;
char szBuf[80];
// -----------------------------------------------------
// Функция 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_POPUPWINDOW | WS_THICKFRAME,
100, 100, 100, 100,
NULL, NULL, hInst, NULL);
if(!hWnd) return(FALSE);
// Размещаем окно в нижней левой части рабочего стола
GetWindowRect(GetDesktopWindow(), &rc);
MoveWindow(hWnd,
rc.right - cxChar * 25,
rc.bottom - cyChar * 3,
cxChar * 10, cyChar * 2, TRUE);
// Отображаем окно и запускаем цикл
// обработки сообщений
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)
{
// Это сообщение посылается приложением STIME
case WM_COPYDATA:
{
// Копируем данные, полученные от приложения STIME,
// во внутренний буфер
strcpy(szBuf, ((PCOPYDATASTRUCT)lParam)->lpData);
// Перерисовываем содержимое окна, отображая в нем
// полученную строку символов
InvalidateRect(hWnd, NULL, TRUE);
break;
}
HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
HANDLE_MSG(hWnd, WM_PAINT, WndProc_OnPaint);
default:
return(DefWindowProc(hWnd, msg, wParam, lParam));
}
}
// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct)
{
HDC hdc;
TEXTMETRIC tm;
hdc = GetDC(hWnd);
// Выбираем в контекст отображения шрифт с фиксированной
// шириной букв
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Определяем и сохраняем метрики шрифта
GetTextMetrics(hdc, &tm);
cxChar = tm.tmMaxCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hWnd, hdc);
// Выполняем инициализацию буфера szBuf, содержимое
// которого отображается в окне приложения
strcpy(szBuf, (LPSTR)"<Unknown>");
return TRUE;
}
// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
PostQuitMessage(0);
return 0L;
}
// -----------------------------------------------------
// Функция WndProc_OnPaint
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnPaint(HWND hWnd)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rc;
// Перерисовываем внутреннюю область окна
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rc);
// Рисуем в окне строку символов, полученную от
// приложения STIME
DrawText(hdc, szBuf, -1, &rc,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
return 0;
}
Исходные тексты приложения SETLOCAL
Главный файл исходных текстов приложения SETLOCAL приведен в листинге 4.1.
Листинг 4.1. Файл setlocal\setlocal.c
// ==================================================
// Приложение SETLOCAL
// Работа с национальными языками
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include "resource.h"
#include "afxres.h"
#include "setlocal.h"
HINSTANCE hInst;
char szAppName[] = "SetLocalApp";
char szAppTitle[] = "Set and Get Local Info";
// Количество установленных раскладок клавиатуры
UINT uLayouts;
// Указатель на массив идентификаторов
// раскладок клавиатуры
HKL * lpList;
// -----------------------------------------------------
// Функция 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;
}
// Определяем количество установленных
// раскалдок клавиатуры
uLayouts = GetKeyboardLayoutList(0, NULL);
// Заказываем массив для хранения идентификаторов
// раскладок клавиатуры
lpList = malloc(uLayouts * sizeof(HKL));
// Заполнение массива идентификаторов
// раскладок клавиатуры
uLayouts = GetKeyboardLayoutList(uLayouts, lpList);
// Регистрируем класс окна
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);
}
// Перед завершением работы приложения освобождаем
// память, полученную для хранения идентификаторов
// раскладок клавиатуры
free(lpList);
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)
{
char szBuf[1024];
char szBuf1[1024];
char szKbLayoutName[KL_NAMELENGTH];
BOOL fRc;
switch (id)
{
case ID_FILE_EXIT:
{
// Завершаем работу приложения
PostQuitMessage(0);
return 0L;
break;
}
// Установка набора национальных параметров для Англии
case ID_LOCALINFO_SETENGLISH:
{
// Выполнение установки для текущей задачи
fRc = SetThreadLocale(MAKELCID(
MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL),
SORT_DEFAULT));
// При возникновении ошибки получаем и отображаем ее код
if(fRc == FALSE)
{
wsprintf(szBuf1,"SetThreadLocale: Error %ld\n",
GetLastError());
MessageBox(hWnd, szBuf1, "Error", MB_OK);
}
break;
}
// Установка набора национальных параметров для России
case ID_LOCALINFO_SETRUSSIAN:
{
fRc = SetThreadLocale(MAKELCID(
MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL),
SORT_DEFAULT));
if(fRc == FALSE)
{
wsprintf(szBuf1,"SetThreadLocale: Error %ld\n",
GetLastError());
MessageBox(hWnd, szBuf1, "Error", MB_OK);
}
break;
}
// Получение и отображение некоторых
// национальных параметров
case ID_LOCALINFO_GETLOCALINFO:
{
// Отображение полного названия национального языка
strcpy(szBuf, "LOCALE_SLANGUAGE: ");
GetLocaleInfo(
GetThreadLocale(), LOCALE_SLANGUAGE, szBuf1, 512);
strcat(szBuf, szBuf1);
// Отображение кода страны
strcat(szBuf, "\nLOCALE_ICOUNTRY: ");
GetLocaleInfo(
GetThreadLocale(), LOCALE_ICOUNTRY, szBuf1, 512);
strcat(szBuf, szBuf1);
// Отображение кодовой страницы OEM
strcat(szBuf, "\nLOCALE_IDEFAULTCODEPAGE: ");
GetLocaleInfo(
GetThreadLocale(), LOCALE_IDEFAULTCODEPAGE,
szBuf1, 512);
strcat(szBuf, szBuf1);
// Отображение кодовой страницы ANSI
strcat(szBuf, "\nLOCALE_IDEFAULTANSICODEPAGE: ");
GetLocaleInfo(
GetThreadLocale(), LOCALE_IDEFAULTANSICODEPAGE,
szBuf1, 512);
strcat(szBuf, szBuf1);
MessageBox(hWnd, szBuf, szAppTitle, MB_OK);
break;
}
// Определение и отображение идентификатора
// текущей раскладки клавиатуры
case ID_KEYBOARD_GETLAYOUTID:
{
GetKeyboardLayoutName(szKbLayoutName);
wsprintf(szBuf1,"Layout ID: %s", szKbLayoutName);
MessageBox(hWnd, szBuf1, szAppTitle, MB_OK);
break;
}
// Установка новой раскладки клавиатуры
case ID_KEYBOARD_SETLAYOUT:
{
// Отображение диалоговой панели для выбора
// раскладки клавиатуры
DialogBox(hInst,
MAKEINTRESOURCE(IDD_DIALOG_SETLAYOUT),
hWnd, DlgProc);
break;
}
// Просмотр текущей даты и времени в формате,
// принятом для выбранной страны
case ID_LOCALINFO_GETDATE:
{
strcpy(szBuf, "Date: ");
// Получаем строку даты
GetDateFormat(
GetThreadLocale(),
LOCALE_NOUSEROVERRIDE | DATE_LONGDATE,
NULL, NULL, szBuf1, 512);
strcat(szBuf, szBuf1);
strcat(szBuf, "\nTime: ");
// Получаем строку времени
GetTimeFormat(
GetThreadLocale(),
LOCALE_NOUSEROVERRIDE,
NULL, NULL, szBuf1, 512);
strcat(szBuf, szBuf1);
// Отображаем время и дату
MessageBox(hWnd, szBuf, szAppTitle, MB_OK);
break;
}
case ID_HELP_ABOUT:
{
MessageBox(hWnd,
"Set and Get Local Information\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)
{
UINT i;
HKL hklCurrent;
char szBuf[256];
// При инициализации диалоговой панели получаем в цикле
// идентификаторы установленных раскладок клавиатуры
// и заполняем названиями соответствующих национальных
// языков список типа COMBOBOX, расположенный в
// диалоговой панели
for(i=0; i<uLayouts; i++)
{
// Берем очередной идентификатор раскладки
hklCurrent = *(lpList + i);
// Получаем название национального языка
GetLocaleInfo(
MAKELCID(((UINT)hklCurrent & 0xffffffff),
SORT_DEFAULT),
LOCALE_SLANGUAGE, szBuf, 512);
// Вставляем название национального языка в список
// типа COMBOBOX
SendMessage(GetDlgItem(hdlg, IDC_COMBO1),
CB_ADDSTRING, 0, (LPARAM)(LPSTR)szBuf);
}
return TRUE;
}
// -----------------------------------------------------
// Функция DlgProc_OnCommand
// -----------------------------------------------------
#pragma warning(disable: 4098)
void DlgProc_OnCommand(HWND hdlg, int id,
HWND hwndCtl, UINT codeNotify)
{
// Номер выбранной строки в списке типа COMBOBOX
LRESULT uSelectedItem;
switch (id)
{
// Когда пользователь нажимает клавишу OK,
// устанавливаем выбранную раскладку клавиатуры
// и завершаем работу диалоговой панели
case IDOK:
{
// Определяем номер выбранной в списке строки
uSelectedItem = SendMessage(
GetDlgItem(hdlg, IDC_COMBO1),
CB_GETCURSEL, 0, 0);
// Активизируем выбранную раскладку клавиатуры
ActivateKeyboardLayout(*(lpList + uSelectedItem), 0);
// Завершаем работу диалоговой панели
EndDialog(hdlg, 1);
return TRUE;
}
// Когда пользователь нажимает клавишу Set layout,
// устанавливаем выбранную раскладку клавиатуры,
// не завершая работы диалоговой панели
case IDC_BUTTON1:
{
uSelectedItem = SendMessage(
GetDlgItem(hdlg, IDC_COMBO1),
CB_GETCURSEL, 0, 0);
ActivateKeyboardLayout(*(lpList + uSelectedItem), 0);
return TRUE;
}
// Если пользователь нажал кнопку Cancel, отменяем
// смену расклдаки клавиатуры, завершая
// работу диалоговой панели
case IDCANCEL:
{
EndDialog(hdlg, 0);
return TRUE;
}
default:
break;
}
return FALSE;
}
Файл setlocal.h (листинг 4.2) содержит прототипы функций, определенных в приложении SETLOCAL.
Листинг 4.2. Файл setlocal\setlocal.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);
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);
В файле resource.h (листинг 4.3), который создается автоматически системой разработки приложений Microsoft Visual C++, находятся определения констант для файла описания ресурсов.
Листинг 4.3. Файл setlocal\resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by setlocal.rc
//
#define IDR_MENU1 101
#define IDR_APPMENU 101
#define IDI_APPICON 102
#define IDI_APPICONSM 103
#define IDD_DIALOG_SETLAYOUT 104
#define IDC_COMBO1 1003
#define IDC_EDIT1 1004
#define IDC_BUTTON1 1005
#define ID_FILE_EXIT 40001
#define ID_HELP_ABOUT 40002
#define ID_KEYBOARD_SETLAYOUT 40004
#define ID_KEYBOARD_GETLAYOUTID 40005
#define ID_LOCALINFO_GETLOCALINFO 40006
#define ID_LOCALINFO_SETENGLISH 40007
#define ID_LOCALINFO_SETRUSSIAN 40008
#define ID_LOCALINFO_GETDATE 40009
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40010
#define _APS_NEXT_CONTROL_VALUE 1006
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Файл описания ресурсов приложения setlocal.rc приведен в листинге 4.4. В нем определены меню, диалоговая панель, пиктограммы и текстовые строки.
Листинг 4.4. Файл setlocal\setlocal.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 "E&xit", ID_FILE_EXIT
END
POPUP "&Keyboard"
BEGIN
MENUITEM "&Set Layout", ID_KEYBOARD_SETLAYOUT
MENUITEM "&Get Layout ID", ID_KEYBOARD_GETLAYOUTID
END
POPUP "Local Info"
BEGIN
MENUITEM "&Get Local Info", ID_LOCALINFO_GETLOCALINFO
MENUITEM "Get &Date and Time", ID_LOCALINFO_GETDATE
MENUITEM SEPARATOR
MENUITEM "Set &English", ID_LOCALINFO_SETENGLISH
MENUITEM "Set &Russian", ID_LOCALINFO_SETRUSSIAN
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 "setlocal.ico"
IDI_APPICONSM ICON DISCARDABLE "setlocsm.ico"
//////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_DIALOG_SETLAYOUT DIALOG DISCARDABLE 0, 0, 248, 143
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Set Keyboard Layout"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,191,7,50,14
PUSHBUTTON "Cancel",IDCANCEL,191,24,50,14
COMBOBOX IDC_COMBO1,14,65,102,64,CBS_DROPDOWN |
CBS_AUTOHSCROLL | CBS_SORT | WS_VSCROLL |
WS_TABSTOP
EDITTEXT IDC_EDIT1,14,28,150,14,ES_AUTOHSCROLL
LTEXT "Keyboard layouts:",IDC_STATIC,14,51,57,8
LTEXT "Enter text to test layout:",
IDC_STATIC,14,14,75,8
PUSHBUTTON "Set layout",IDC_BUTTON1,191,64,50,14
END
//////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
IDD_DIALOG_SETLAYOUT, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 241
TOPMARGIN, 7
BOTTOMMARGIN, 136
END
END
#endif // APSTUDIO_INVOKED
#endif // Russian resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Исходные тексты приложения SRVCTRL
Главный файл исходных текстов приложения SRVCTRL, предназначенного для управления сервисом Sample of simple service приведен в листинге 5.3.
Листинг 5.3. Файл service/srvctrl.c
// ==================================================
// Приложение SRVCTRL
// Работа с сервисом "Sample of simple service"
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include "resource.h"
#include "afxres.h"
#include "srvctrl.h"
HINSTANCE hInst;
char szAppName[] = "ServiceCtlApp";
char szAppTitle[] = "Simple Service Control";
// Состояние сервиса
SERVICE_STATUS ss;
// -----------------------------------------------------
// Функция 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)
{
// Идентификатор сервиса
SC_HANDLE schService;
// Идентификатор системы управления сервисами
SC_HANDLE schSCManager;
LPQUERY_SERVICE_CONFIG lpBuf;
DWORD dwBytesNeeded;
char szBuf[1024];
switch (id)
{
case ID_FILE_EXIT:
{
// Завершаем работу приложения
PostQuitMessage(0);
return 0L;
break;
}
// Установка сервиса в систему
case ID_SERVICE_INSTALL:
{
// Открываем систему управления сервисами
schSCManager = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!schSCManager)
break;
// Создаем сервис с именем MYServiceName
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);
break;
}
// Удаление сервиса из системы
case ID_SERVICE_REMOVE:
{
// Открываем систему управления сервисами
schSCManager = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!schSCManager)
break;
// Открываем сервис с именем MYServiceName
schService = OpenService(
schSCManager, MYServiceName,
SERVICE_ALL_ACCESS);
if(!schService)
break;
// Останавливаем сервис
ControlService(schService,
SERVICE_CONTROL_STOP, &ss);
// Вызываем функцию удаления сервиса из системы
DeleteService(schService);
// Закрываем идентификатор системы управления
// сервисами
CloseServiceHandle(schSCManager);
break;
}
case ID_SERVICE_START:
{
// Открываем систему управления сервисами
schSCManager = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!schSCManager)
break;
// Открываем сервис с именем MYServiceName
schService = OpenService(
schSCManager, MYServiceName,
SERVICE_ALL_ACCESS);
if(!schService)
break;
// Запускаем сервис
StartService(schService, 0, NULL);
// Закрываем идентификатор системы управления
// сервисами
CloseServiceHandle(schSCManager);
break;
}
case ID_SERVICE_STOP:
{
// Открываем систему управления сервисами
schSCManager = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!schSCManager)
break;
// Открываем сервис с именем MYServiceName
schService = OpenService(
schSCManager, MYServiceName,
SERVICE_ALL_ACCESS);
// Останавливаем сервис
ControlService(schService,
SERVICE_CONTROL_STOP, &ss);
// Закрываем идентификатор системы управления
// сервисами
CloseServiceHandle(schSCManager);
break;
}
case ID_SERVICE_GETCONFIGURATION:
{
// Открываем систему управления сервисами
schSCManager = OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!schSCManager)
break;
// Открываем сервис с именем MYServiceName
schService = OpenService(
schSCManager, MYServiceName,
SERVICE_ALL_ACCESS);
if(!schService)
break;
// Получаем буфер для сохранения конфигурации
lpBuf = (LPQUERY_SERVICE_CONFIG)malloc(4096);
if(lpBuf != NULL)
{
// Сохраняем конфигурацию в буфере
QueryServiceConfig(schService,
lpBuf, 4096, &dwBytesNeeded);
// Отображаем некоторые поля конфигурации
wsprintf(szBuf, "Binary path: %s\n"
"Start Name: %s\n"
"Display Name: %s\n",
lpBuf->lpBinaryPathName,
lpBuf->lpServiceStartName,
lpBuf->lpDisplayName);
MessageBox(hWnd, szBuf, szAppTitle,
MB_OK | MB_ICONINFORMATION);
// Освобождаем буфер
free(lpBuf);
}
// Закрываем идентификатор системы управления
// сервисами
CloseServiceHandle(schSCManager);
break;
}
case ID_HELP_ABOUT:
{
MessageBox(hWnd,
"Simple Service Control\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);
}
В файле srvctrl.h (листинг 5.4) определено имя сервиса и прототипы функций.
Листинг 5.4. Файл service/srvctrl.h
// Имя сервиса
#define MYServiceName "Sample of simple service"
// -----------------------------------------------------
// Описание функций
// -----------------------------------------------------
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);
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);
Файл resource.h (листинг 5.5) содержит описания констант, которые используются в файле определения ресурсов приложения.
Листинг 5.5. Файл service/resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by srvctrl.rc
//
#define IDR_MENU1 101
#define IDR_APPMENU 101
#define IDI_APPICON 102
#define IDI_APPICONSM 103
#define ID_FILE_EXIT 40001
#define ID_HELP_ABOUT 40002
#define ID_SERVICE_INSTALL 40010
#define ID_SERVICE_REMOVE 40011
#define ID_SERVICE_START 40012
#define ID_SERVICE_STOP 40013
#define ID_SERVICE_GETCONFIGURATION 40014
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40015
#define _APS_NEXT_CONTROL_VALUE 1006
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Файл определения ресурсов приложения приведен в листинге 5.6.
Листинг 5.6. Файл service/srvctrl.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 "E&xit", ID_FILE_EXIT
END
POPUP "&Service"
BEGIN
MENUITEM "&Install", ID_SERVICE_INSTALL
MENUITEM "&Remove", ID_SERVICE_REMOVE
MENUITEM SEPARATOR
MENUITEM "&Start", ID_SERVICE_START
MENUITEM "Sto&p", ID_SERVICE_STOP
MENUITEM SEPARATOR
MENUITEM "&Get configuration",
ID_SERVICE_GETCONFIGURATION
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 "srvctrl.ico"
IDI_APPICONSM ICON DISCARDABLE "srvctrsm.ico"
#endif // Russian resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Исходные тексты приложения STIME
Главный файл исходных текстов приложения STIME представлен в листинге 2.7.
Листинг 2.7. Файл rclock/stime/stime.c
// ==================================================
// Приложение STIME (работает вместе с приложением RTIME)
// Демонстрация использования сообщения WM_COPYDATA
// для передачи данных между процессами
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <time.h>
#include "resource.h"
#include "afxres.h"
#include "stime.h"
HINSTANCE hInst;
char szAppName[] = "StimeApp";
char szAppTitle[] = "Time Sender";
// Имя приложения RTIME
char szServerAppName[] = "RclockApp";
// Идентификатор главного окна приложения RTIME
HWND hWndServer;
// Структура для передачи данных между процессами
// при помощи сообщения WM_COPYDATA
COPYDATASTRUCT cd;
// Буферы для передаваемых данных
char szBuf[80];
char szTerminated[] = "<Terminated>";
// -----------------------------------------------------
// Функция 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;
}
// Ищем окно серверного приложения RCLOCK и сохраняем
// его идентификатор
hWndServer = FindWindow(szServerAppName, NULL);
if(hWndServer == NULL)
{
// Если окно серверного приложения не найдено,
// выводим сообщение об ошибке и завершаем работу
// приложения
MessageBox(NULL, "Server RCLOCK not found", szAppName,
MB_ICONEXCLAMATION | MB_OK);
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_TIMER, WndProc_OnTimer);
HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
default:
return(DefWindowProc(hWnd, msg, wParam, lParam));
}
}
// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct)
{
// Создаем таймер с периодом работы 1 сек
SetTimer(hWnd, CLOCK_TIMER, 1000, NULL);
return TRUE;
}
// -----------------------------------------------------
// Функция WndProc_OnTimer
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnTimer(HWND hWnd, UINT id)
{
time_t t;
struct tm *ltime;
// Определяем текущее время
time(&t);
ltime = localtime(&t);
// Формируем текстовую строку времени
wsprintf(szBuf, "%02d:%02d:%02d",
ltime->tm_hour, ltime->tm_min,ltime->tm_sec);
// Записываем адрес и размер строки в структуру
// типа COPYDATASTRUCT
cd.lpData = szBuf;
cd.cbData = strlen(szBuf) + 1;
// Посылаем сообщение серверному приложению RCLOCK
SendMessage(hWndServer, WM_COPYDATA,
(WPARAM)hWnd, (LPARAM)&cd);
return 0;
}
// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
// Перед завершением работы приложения передаем
// серверу строку <Terminated>
cd.lpData = szTerminated;
cd.cbData = strlen(szTerminated) + 1;
SendMessage(hWndServer, WM_COPYDATA,
(WPARAM)hWnd, (LPARAM)&cd);
// Удаляем таймер
KillTimer(hWnd, CLOCK_TIMER);
PostQuitMessage(0);
return 0L;
}
Исходный текст сервиса
Исходный текст сервиса представлен в листинге 5.1. Так как ранее мы уже подробно описывали структуру этого сервиса, то мы оставим вам этот листинг и листинг приложения SRVCTRL на самостоятельное изучение.
Листинг 5.1. Файл service/small/small.c
// ==================================================
// Сервис "Sample of simple service"
// Шаблон простейшего сервиса Windows NT
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include "small.h"
// -----------------------------------------------------
// Глобальные переменные
// -----------------------------------------------------
// Код ошибки
DWORD dwErrCode;
// Текущее состояние сервиса
SERVICE_STATUS ss;
// Идентификатор сервиса
SERVICE_STATUS_HANDLE ssHandle;
// -----------------------------------------------------
// Функция main
// Точка входа процесса
// -----------------------------------------------------
void main(int agrc, char *argv[])
{
// Таблица точек входа
SERVICE_TABLE_ENTRY DispatcherTable[] =
{
{
// Имя сервиса
MYServiceName,
// Функция main сервиса
(LPSERVICE_MAIN_FUNCTION)ServiceMain
},
{
NULL,
NULL
}
};
printf("Sample of simple service\n"
"(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");
// Запуск диспетчера
if(!StartServiceCtrlDispatcher(DispatcherTable))
{
fprintf(stdout,
"StartServiceCtrlDispatcher: Error %ld\n",
GetLastError());
getch();
return;
}
}
// -----------------------------------------------------
// Функция ServiceMain
// Точка входа сервиса
// -----------------------------------------------------
void WINAPI ServiceMain(DWORD argc, LPSTR *argv)
{
// Регистрируем управляющую функцию сервиса
ssHandle =
RegisterServiceCtrlHandler(MYServiceName, ServiceControl);
if(!ssHandle)
return;
// Устанавливаем состояние сервиса
// Сервис работает как отдельный процесс
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
// Код ошибки при инициализации и завершения сервиса
// не используется
ss.dwServiceSpecificExitCode = 0;
// Начинаем запуск сервиса.
// Прежде всего устанавливаем состояние ожидания
// запуска сервиса
ReportStatus(SERVICE_START_PENDING, NO_ERROR, 4000);
// Вызываем функцию, которая выполняет все
// необходимые инициализирующие действия
// ServiceStart(argc, argv);
// После завершения инициализации устанавливаем
// состояние работающего сервиса
ReportStatus(SERVICE_RUNNING, NOERROR, 0);
return;
}
// -----------------------------------------------------
// Функция ServiceControl
// Точка входа функции обработки команд
// -----------------------------------------------------
void WINAPI ServiceControl(DWORD dwControlCode)
{
// Анализируем код команды и выполняем эту команду
switch(dwControlCode)
{
// Команда остановки сервиса
case SERVICE_CONTROL_STOP:
{
// Устанавливаем состояние ожидания остановки
ss.dwCurrentState = SERVICE_STOP_PENDING;
ReportStatus(ss.dwCurrentState, NOERROR, 0);
// Выполняем остановку сервиса, вызывая функцию,
// которая выполняет все необходимые для этого действия
// ServiceStop();
// Отмечаем состояние как остановленный сервис
ReportStatus(SERVICE_STOPPED, NOERROR, 0);
break;
}
// Определение текущего состояния сервиса
case SERVICE_CONTROL_INTERROGATE:
{
// Возвращаем текущее состояние сервиса
ReportStatus(ss.dwCurrentState, NOERROR, 0);
break;
}
// В ответ на другие команды просто возвращаем
// текущее состояние сервиса
default:
{
ReportStatus(ss.dwCurrentState, NOERROR, 0);
break;
}
}
}
// -----------------------------------------------------
// Функция 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);
}
В файле small.h (листинг 5.2) определено имя сервиса MYServiceName и прототипы функций.
Листинг 5.2. Файл service/small/small.h
#define MYServiceName "Sample of simple service"
void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArv);
void WINAPI ServiceControl(DWORD dwControlCode);
void ReportStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode, DWORD dwWaitHint);
Использование фукции CreateFile
Функция CreateFile, предназначенная для работы с файлами, может также быть использована для создания новых каналов и открытия существующих. При этом вместо имени файла вы должны указать этой функции имя канала Pipe.
Изменение раскладки клавиатуры
Помимо изменения текущего набора национальных параметров приложение может изменить раскладку клавиатуры. При этом изменится расположение символов на клавиатуре и генерируемые при нажатии на клавиши символьные коды.
Для изменения раскладки клавиатуры в программном интерфейсе операционной системы Microsoft Windows NT предусмотрено несколько специальных функций.
Изменение состояния канала Mailslot
С помощью функции SetMailslotInfo серверный процесс может изменить время ожидания для канала Mailslot уеж после его создания.
Прототип функции SetMailslotInfo приведен ниже:
BOOL SetMailslotInfo(
HANDLE hMailslot, // идентификатор канала Mailslot
DWORD dwReadTimeout); // время ожидания
Через параметр hMailslot функции SetMailslotInfo передается идентификатор канала Mailslot, для которого нужно изменить время ожидания.
Новое значение времени ожидания в миллисекундах задается через параметр dwReadTimeout. Вы также можете указать здесь константы 0 или MAILSLOT_WAIT_FOREVER. В первом случае функции, работающие с каналом, вернут управление немедленно, во втором - будут находиться в состоянии ожидания до тех пор, пока не завершится выполняемая операция.
Экспортирование функций и глобальных переменных
Хотя существуют DLL-библиотеки без исполнимого кода и предназначенные для хранения ресурсов, подавляющее большинство DLL-библиотек экспортируют функции для их совместного использования несколькими приложениям.
Кроме функций LibMain и WEP в 16-разрядных DLL-библиотеках операционной системы Microsoft Windows версии 3.1 и функции DLLEntryPoint в 32-разрядных библиотеках операционных систем Microsoft Windows NT и Microsoft Windows 95 могут быть определены экспортируемые
и неэкспортируемые функции.
Экспортируемые функции доступны для вызова приложениям Windows. Неэкспортируемые являются локальными для DLL-библиотеки, они доступны только для функций библиотеки.
При необходимости вы можете экспортировать из 32-разрядных DLL-библиотек не только функции, но и глобальные переменные.
Самый простой способ сделать функцию экспортируемой - перечислить все экспортируемые функции в файле определения модуля при помощи оператора EXPORTS:
EXPORTS
ИмяТочкиВхода [=ВнутрИмя] [@Номер] [NONAME] [CONSTANT]
. . .
Здесь ИмяТочкиВхода задает имя, под которым экспортируемая из DLL-библиотеки функция будет доступна для вызова.
Внутри DLL-библиотеки эта функция может иметь другое имя. В этом случае необходимо указать ее внутреннее имя ВнутрИмя.
С помощью параметра @Номер вы можете задать порядковый номер экспортируемой функции.
Если вы не укажите порядковые номера экспортируемых функций, при компоновке загрузочного файла DLL-библиотеки редактор связи создаст свою собственную нумерацию, которая может изменяться при внесении изменений в исходные тексты функций и последующей повторной компоновке.
Заметим, что ссылка на экспортируемую функцию может выполняться двумя различными способами - по имени функции и по ее порядковому номеру. Если функция вызывается по имени, ее порядковый номер не имеет значения. Однако вызов функции по порядковому номеру выполняется быстрее, поэтому использование порядковых номеров предпочтительнее.
Если при помощи файла определения модуля DLL-библиотеки вы задаете фиксированное распределение порядковых номеров экспортируемых функций, при внесении изменений в исходные тексты DLL-библиотеки это распределение не изменится. В этом случае все приложения, ссылающиеся на функции из этой библиотеки по их порядковым номерам, будут работать правильно. Если же вы не определили порядковые номера функций, у приложений могут возникнуть проблемы с правильной адресацией функции из-за возможного изменения этих номеров.
Указав флаг NONAME и порядковый номер, вы сделаете имя экспортируемой функции невидимым. При этом экспортируемую функцию можно будет вызвать только по порядковому номеру, так как имя такой функции не попадет в таблицу экспортируемых имен DLL-библиотеки.
Флаг CONSTANT позволяет экспортировать из DLL-библиотеки не только функции, но и данные. При этом параметр ИмяТочкиВхода задает имя экспортируемой глобальной переменной, определенной в DLL-библиотеке.
Приведем пример экспортирования функций и глобальных переменных из DLL-библиотеки:
EXPORTS
DrawBitmap=MyDraw @4
ShowAll
HideAll
MyPoolPtr @5 CONSTANT
GetMyPool @8 NONAME
FreeMyPool @9 NONAME
В приведенном выше примере в разделе EXPORTS перечислены имена нескольких экспортируемых функций DrawBitmap, ShowAll, HideAll, GetMyPool, FreeMyPool и глобальной переменной MyPoolPtr.
Функция MyDraw, определенная в DLL-библиотеке, экспортируется под именем DrawBitmap. Она также доступна под номером 4.
Функции ShowAll и HideAll экспортируются под своими “настоящими” именами, с которыми они определены в DLL-библиотеке. Для них не заданы порядковые номера.
Функции GetMyPool и FreeMyPool экспортируются с флагом NONAME, поэтому к ним можно обращаться только по их порядковым номерам, которые равны, соответственно, 8 и 9.
Имя MyPoolPtr экспортируется с флагом CONSTANT, поэтому оно является именем глобальной переменной, определенной в DLL-библиотеке, и доступной для приложений, загружающих эту библиотеку.
Как работает DLL-библиотека
В операционной системе Microsoft Windows версии 3.1 16-разрядная DLL-библиотека состоит из нескольких специфических функций и произвольного набора функций, выполняющих ту работу, для которой разрабатывалась данная библиотека. В заголовке загрузочного модуля DLL-библиотеки описаны экспортируемые точки входа, соответствующие всем или некоторым определенным в ней функциям. Приложения могут вызывать только те функции DLL-библиотеки, которые ей экспортируются.
В процессе инициализации после загрузки 16-разрядной DLL-библиотеки в память Windows версии 3.1 вызывает функцию LibEntry, которая должна быть определена в каждой DLL-библиотеке. Задачей функции LibEntry является инициализация локальной области памяти, если она определена для DLL-библиотеки.
Функция LibEntry должна быть дальней функцией, составленной на языке ассемблера, так как она получает параметры через регистры процессора. Мы подробно описали функцию LibEntry и ее параметры в 13 томе “Библиотеки системного программиста”. Заметим, что использование языка ассемблера затрудняет создание мультиплатформных приложенй, поэтому в мультиплатформной операционной системе Microsoft Windows NT используется другой способ инициализации.
Создавая 16-разрядную DLL-библиотеку, вам не надо определять функцию LibEntry самостоятельно, так как при создании файла DLL-библиотеки редактор связей, входящий в систему разработки, включит уже имеющийся в стандартной библиотеке модуль. Этот стандартный модуль выполняет всю необходимую работу по инициализации локальной области памяти DLL-библиотеки (с помощью функции LocalInit) и затем вызывает функцию LibMain.
Функция LibMain должна присутствовать в каждой 16-разрядной DLL-библиотеке. Эту функцию надо определить самостоятельно, причем вы можете воспользоваться языком программирования С.
По своему назначению функция LibMain напоминает функцию WinMain обычного приложения Windows. Функция WinMain получает управление при запуске приложения, а функция LibMain - при первой загрузке DLL-библиотеки в память. Так же как и функция WinMain, функция LibMain имеет параметры, которые можно использовать для инициализации библиотеки.
Прототип функции LibMain и ее параметры были описаны в 13 томе “Библиотеки системного программиста”.
Другая функция, которая присутствует в каждой 16-разрядной DLL-библиотеке, это функция WEP.
В среде Microsoft Windows версии 3.1 DLL-библиотека в любой момент времени может быть выгружена из памяти. В этом случае Windows перед выгрузкой вызывает функцию WEP. Эта функция, как и функция LibMain, вызывается только один раз. Она может быть использована для уничтожения структур данных и освобождения блоков памяти, заказанных при инициализации DLL-библиотеки.
Вам не обязательно самостоятельно определять функцию WEP. Так же как и функция LibEntry, функция WEP добавляется в 16-разрядную DLL-библиотеку транслятором.
Что же касается 32-разрядных DLL-библиотек операционной системы Microsoft Windows NT, то их инициализация и выгрузка из памяти происходит иначе.
Каналы передачи данных Mailslot
В завершении этой главы мы рассмотрим еще один простой способ организации передачи данных между различными процессами, основанный на использовании датаграммных каналов Mailslot.
Каналы Mailslot позволяют выполнять одностороннюю передачу данных от одного или нескольких клиентов к одному или нескольким серверам. Главная особенность каналов Mailslot заключается в том, что они, в отличие от других средств, рассмотренных нами в этой главе, позволяют передавать данные в широковещательном режиме.
Это означает, что на компьютере или в сети могут работать несколько серверных процессов, способных получать сообщения через каналы Mailslot. При этом один клиентский процесс может посылать сообщения сразу всем этим серверным процессам.
С помощью каналов Pipe вы не сможете передавать данные в широковещательном режиме, так как только два процесса могут создать канал типа Pipe.
Каналы передачи данных Pipe
В среде операционной системы Microsoft Windows NT вам доступно такое удобное средство передачи данных между параллельно работающими процессами, как каналы типа Pipe. Это средство позволяет организовать передачу данных между локальными процессами, а также между процессами, запущенными на различных рабочих станциях в сети.
Каналы типа Pipe больше всего похожи на файлы, поэтому они достаточно просты в использовании.
Через канал можно передавать данные только между двумя процессами. Один из процессов создает канал, другой открывает его. После этого оба процесса могут передавать данные через канал в одну или обе стороны, используя для этого хорошо знакомые вам функции, предназначенные для работы с файлами, такие как ReadFile и WriteFile. Заметим, что приложения могут выполнять над каналами Pipe синхронные или асинхронные операции, аналогично тому, как это можно делать с файлами. В случае использования асинхронных операций необходимо отдельно побеспокоиться об организации синхронизации.
Наборы национальных параметров
При установке Microsoft Windows NT пользователь может указать, какие наборы национальных параметров должны быть установлены (для каких стран). Российские пользователи в своем большинстве пожелают установить по крайней мере английский и русский набор параметров, для того чтобы было можно работать в английской и русской среде. Некоторым дополнительно могут потребоваться национальные параметры для работы с французским, немецким или каким-либо другим языком.
С точки зрения разработчика приложений наборы параметров обозначаются при помощи так называемого идентификатора наборов национальных параметров LCID (в документации к SDK этот термин звучит как locale identifier).
Многозадачная операционная система Microsoft Windows NT допускает установку отдельного набора национальных параметров для каждой задачи. Для этого предназначена функция с именем SetThreadLocale. Что же касается Microsoft Windows 95, то в среде этой операционной системы соответствующих средств нет - пользователь должен выбрать только один набор при помощи приложения Control Panel (пиктограмма Regional Settings). Новый набор параметров будет использован после перезагрузки.
Рассмотрим кратко средства, предназначенные для работы с наборами национальных параметров.
Обмен через файлы, отображаемые на память
Изучение средств обмена данными между процессами мы начнем с файлов, отображенных на память. Этот способ обладает высоким быстродействием, так как данные передаются между процессами непосредственно через виртуальную память.
Методика работы с файлами, отображаемыми на память, была описана в первой главе. Эта методика может быть использована без изменений для организации передачи данных между процессами, однако мы все же сделаем некоторые замечания.
Напомним, что отображение создается функцией CreateFileMapping.
Вот фрагмент кода из приложения Oem2Char, в котором создается отображение файла, а затем выполняется отображение этого файла в память:
hFileMapping = CreateFileMapping(hSrcFile,
NULL, PAGE_READWRITE, 0, dwFileSize, NULL);
if(hFileMapping == NULL)
return;
lpFileMap = MapViewOfFile(hFileMapping,
FILE_MAP_WRITE, 0, 0, 0);
if(lpFileMap == 0)
return;
Здесь в качестве первого параметра для функции CreateFileMapping мы передаем идентификатор файла, открытого функцией CreateFile. Последний параметр указан как NULL, поэтому отображение не имеет имени.
Если отображение будет использоваться для передачи данных между процессами, удобно указать для него имя. Пользуясь этим именем, другие процессы смогут открыть отображение функцией OpenFileMapping.
Другое замечание касается идентификатора файла, передаваемого функции CreateFileMapping через первый параметр. Если вы создаете отображение только для того чтобы обеспечить передачу данных между процессами, вам не нужно создавать файл на диске компьютера. Указав в качестве идентификатора файла значение (HANDLE)0xFFFFFFFF, вы создадите отображение непосредственно в виртуальной памяти без использования дополнительного файла.
Ниже мы привели фрагмент кода, в котором создается отображение с именем $MyVerySpecialFileShareName$, причем это отображение создается в виртуальной памяти:
CHAR lpFileShareName[] =
"$MyVerySpecialFileShareName$";
hFileMapping = CreateFileMapping((HANDLE)0xFFFFFFFF,
NULL, PAGE_READWRITE, 0, 100, lpFileShareName);
После того как вы создали объект-отображение, следует выполнить отображение файла в память при помощи функции MapViewOfFile, как это было показано выше. В случае успеха эта функция вернет указатель на отображенную область памяти.
Итак, первый процесс создал отображение. Второй процесс, который будет выполнять обмен данными с первым процессом, должен открыть это отображение по имени при помощи функции OpenFileMapping, например, так:
hFileMapping = OpenFileMapping(
FILE_MAP_READ | FILE_MAP_WRITE, FALSE, lpFileShareName);
Далее второе приложение выполняет отображение, вызывая функцию MapViewOfFile:
lpFileMap = MapViewOfFile(hFileMapping,
FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
Пользуясь значением, полученным от функции MapViewOfFile, второе приложение получает указатель на отображенную область памяти. Физически эта область находится в тех же страницах виртуальной памяти, что и область, созданная первым процессом. Таким образом, два процесса получили указатели на общие страницы памяти.
Перед завершением своей работы процессы должны отменить отображение файла и освободить идентификатор созданного объекта-отображения:
UnmapViewOfFile(lpFileMap);
CloseHandle(hFileMapping);
Обмен данными между приложениями через DLL-библиотеку
В среде операционной системы Microsoft Windows версии 3.1 существовала возможность обмена данными между различными работающими параллельно приложениями через область локальной кучи (local heap) DLL-библиотеки. Эта область находилась в сегменте данных DLL-библиотеки и потому была доступна всем приложениям.
Что же касается операционной системы Microsoft Windows NT, то в ее среде DLL-библиотеки не имеют собственных областей данных, а отображаются в адресные пространства приложений, загружающих эти библиотеки. Как результат, приложения не могут получать адреса статических и глобальных переменных и использовать эти переменные для обмена данными - ведь адрес, верный в контексте одного приложения, не будет иметь никакого смысла для другого приложения.
Приложение также может сделать попытку изменить содержимое статической или глобальной переменной, с тем чтобы другие приложения могли прочитать новое значение. Однако этот способ передачи данных между приложениями не будет работать. Попытка изменения данных будет зафиксирована операционной сиситемой, которая создаст для этого приложения копию страницы памяти, в которой находится изменившиеся данные, с использованием механизма копирования при записи (copy-on-write). Мы рассказывали об этом механизме в предыдущем томе “Библиотеки системного программиста”.
Если по какой-либо причине вы не можете использовать для обмена данными между приложениями файлы, отображаемые на память, можно создать DLL-библиотеку с данными, имеющими атрибут SHARED.
Для этого прежде всего вы должны задать имя секции данных, которая будет использована для передачи данных между процессами. Это можно сделать с помощью прагмы транслятора data_seg:
#pragma data_seg (“.shar”)
После этого в файле определения модуля для DLL-библиотеки необходимо указать атрибуты секции данных, как это показано ниже:
SECTIONS .shar READ WRITE SHARED
Можно также указать ключ редактору связей:
-SECTION:.shar,RWS
Строка RWS определяет атрибуты секции: R - READ, W - WRITE, S - SHARED.
При обращении к глобальным переменным, расположенным в секции с атрибутом SHARED, процессы должны выполнять взаимную синхронизацию с использованием таких средств, как критические секции, объекты-события, семафоры.
Описание функций
Приведем описание функций, определенных в нашем приложении.
В этом разделе мы кратко расскажем о функциях, определенных в приложении DiskInfo.
Приведем краткое описание наиболее важных функций приложения SETLOCAL.
Определение идентификатора раскладки клавиатуры для задачи
С помощью функции GetKeyboardLayout приложение может определить идентификатор раскладки клавиатуры, назначенный для данной задачи:
HKL GetKeyboardLayout(
DWORD dwLayout); // идентификатор задачи
Единственный параметр этой функции определяет идентификатор задачи, для которой нужно определить идентификатор раскладки клавиатуры. Если вы укажите нулевое значение, функция возвратит идентификатор раскладки для текущей задачи.
Определение конфигурации сервиса
Приложение или сервис может определить конфигурацию заданного сервиса, вызвав для этого функцию QueryServiceConfig:
BOOL QueryServiceConfig(
SC_HANDLE schService, // идентификатор сервиса
LPQUERY_SERVICE_CONFIG lpqscServConfig, // адрес структуры
// QUERY_SERVICE_CONFIG, в которую будет
// записана конфигурация сервиса
DWORD cbBufSize, // размер буфера для записи конфигурации
LPDWORD lpcbBytesNeeded); // адрес переменной, в котоую будет
// записан размер буфера, необходимый для
// сохранения всей информации о конфигурации
Формат структуры QUERY_SERVICE_CONFIG приведен ниже:
typedef struct _QUERY_SERVICE_CONFIG
{
DWORD dwServiceType;
DWORD dwStartType;
DWORD dwErrorControl;
LPTSTR lpBinaryPathName;
LPTSTR lpLoadOrderGroup;
DWORD dwTagId;
LPTSTR lpDependencies;
LPTSTR lpServiceStartName;
LPTSTR lpDisplayName;
} QUERY_SERVICE_CONFIG, LPQUERY_SERVICE_CONFIG;
Содержимое полей этой структуры соответствует содержимому параметров функции CreateService, описанной ранее.
Ниже расположен фрагмент кода, в котором определяется текущая конфигурация сервиса:
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
schService = OpenService(
schSCManager, MYServiceName, SERVICE_ALL_ACCESS);
lpBuf = (LPQUERY_SERVICE_CONFIG)malloc(4096);
if(lpBuf != NULL)
{
QueryServiceConfig(schService, lpBuf, 4096, &dwBytesNeeded);
. . .
free(lpBuf);
}
Определение набора национальных параметров по умолчанию
Если приложению нужно использовать наборы национальных параметров, которые используются операционной системой по умолчанию или которые установлены для текущего пользователя по умолчанию, оно также может использовать специальные функции.
Функция GetSystemDefaultLCID не имеет параметров и возвращает идентификатор набора национальных параметров, которые используются операционной системой по умолчанию:
LCID GetSystemDefaultLCID(VOID);
Для определения идентификатора набора национальных параметров, установленных по умолчанию для текущего пользователя Microsoft Windows NT вы должны вызвать функцию GetUserDefaultLCID, которая также не имеет параметров:
LCID GetUserDefaultLCID(VOID);
Функция GetSystemDefaultLangID позволяет определить идентификатор национального языка, который используется операционной системой по умолчанию:
LANGID GetSystemDefaultLangID(VOID);
С помощью функции GetUserDefaultLangID приложение может определить идентификатор национального языка, который установлен по умолчанию для текущего пользователя Microsoft Windows NT:
LANGID GetUserDefaultLangID(VOID);
Определение национального набора параметров
Помимо установки нового набора национальных параметров, часто возникает и обратная задача - определение текущего набора национальных параметров, или определение набора, который используется по умолчанию операционной системой или текущим пользователем. Для решения этой задачи в программном интерфейсе операционной системы Microsoft Windows NT предусмотрен набор функций и макрокоманд, к рассмотрению которых мы сейчас переходим.
Определение названия текущей раскладки клавиатуры
Для определения названия текущей (активной) расклдаки клавиатуры вы можете воспользоваться функцией GetKeyboardLayoutName:
BOOL GetKeyboardLayoutName(
LPTSTR pwszKLID); // адрес буфера для имени раскладки
Через единственный параметр вы должны передать этой функции адрес буфера, в который будет записано название раскладки. Размер буфера должен быть не меньше чем KL_NAMELENGTH байт.
Определение отдельных национальных параметров
Теперь, когда мы научились устанавливать и определять идентификатор национальных параметров, пора перейти к самим национальным параметрам.
Для определения значений отдельных параметров для заданного идентификатора национальных параметров вы должны использовать функцию GetLocaleInfo:
int GetLocaleInfo(
LCID Locale, // идентификатор набора параметров
LCTYPE LCType, // тип информации
LPTSTR lpLCData, // адрес буфера для информации
int cchData); // размер буфера для информации
Параметр Locale этой функции задает идентификатор национальных параметров, для которого нужно определить один из конкретных параметров.
Нужный национальный параметр задается параметром LCType функции GetLocaleInfo. Немного позже мы приведем сокращенный список допустимых значений для этого параметра.
Полученная информация будет записана в буфер, адрес которого задается параметром lpLCData, а размер - параметром cchData. Информация будет записана в буфер в виде текстовой строки.
Обычно буфер заказывается динамически, причем для определения требуемого размера буфера достаточно указать значение параметра lpLCData, равное NULL, - в этом случае функция GetLocaleInfo вернет нужный размер буфера в байтах.
В случае успешного выполнения функция GetLocaleInfo возвращает размер текстовой строки с информацией, записанной в буфер lpLCData. При ошибке возвращается нулевое значение.
Для типа информации LCType можно задавать очень много значений. Все допустимые значения описаны в документации SDK. Из-за ограниченного объема книги мы не имеем возможности все их перечислить, поэтому ограничимся только самыми интересными:
LOCALE_ILANGUAGE
Идентификатор национального языка (длиной не более 5 символов)
LOCALE_SLANGUAGE
Полное название национального языка
LOCALE_SENGLANGUAGE
Полное английское название языка
LOCALE_SABBREVLANGNAME
Сокращенное трехсимвольное название языка
LOCALE_SNATIVELANGNAME
Естественное названия языка
LOCALE_ICOUNTRY
Код страны (длиной не более 6 символов)
LOCALE_SCOUNTRY
Полное локализованное название страныLOCALE_SENGCOUNTRY
Полное английское название страны
LOCALE_SABBREVCTRYNAME
Сокращенное название страны
LOCALE_SNATIVECTRYNAME
Естественное название страны
LOCALE_IDEFAULTLANGUAGE
Идентификатор основного языка, который используется в данной стране
LOCALE_IDEFAULTCOUNTRY
Основной код страны
LOCALE_IDEFAULTCODEPAGE
Номер кодовой страницы OEM
LOCALE_IDEFAULTANSICODEPAGE
Номер кодовой страницы ANSI
LOCALE_SLIST
Символ, который используется для разделения элементов списка
LOCALE_IMEASURE
Система измерений (0 - метрическая, 1 - американская)
LOCALE_SDECIMAL
Символ, который используется в качестве десятичного разделителя в числах
LOCALE_STHOUSAND
Символ, который используется в качестве разделителя групп цифр в многозначных числах
LOCALE_SDATE
Символ-разделитель в строке даты
LOCALE_STIME
Символ-разделитель в строке времени
LOCALE_IDATE
Порядок, в котором располагаются компоненты даты:
0: Месяц-День-Год,
1: День-Месяц-Год,
2: Год-Месяц-День
LOCALE_SDAYNAME1
Естественное длинное название для понедельника
LOCALE_SDAYNAME2 - LOCALE_SDAYNAME7
Естественное длинное название для дней недели от вторника до воскресения
LOCALE_SABBREVDAYNAME1
Естественное сокращенное название для понедельника
LOCALE_SABBREVDAYNAME2 - LOCALE_SABBREVDAYNAME7
Естественное сокращенное название для дней недели от вторника до воскресения
LOCALE_SMONTHNAME1
Естественное длинное название для января
LOCALE_SMONTHNAME2 - LOCALE_SMONTHNAME12
Естественное длинное название для месяцев от февраля до декабря
Помимо перечисленных, предусмотрены многочисленные константы для определения формата отображения даты, времени и денежных единиц, положительных и отрицательных чисел и так далее.
В качестве примера использования функции GetLocaleInfo приведем следующий фрагмент кода, в котором мы определяем полное название национального языка для текущей задачи:
GetLocaleInfo(
GetThreadLocale(), LOCALE_SLANGUAGE, szBuf, 512);
Здесь полученное название языка будет записано в виде текстовой строки в буфер szBuf.
Определение состояния канала Mailslot
Серверный процесс может определить текущее состояние канала Mailslot по его идентификатору с помощью функции GetMailslotInfo. Прототип этой функции мы привели ниже:
BOOL GetMailslotInfo(
HANDLE hMailslot, // идентификатор канала Mailslot
LPDWORD lpMaxMessageSize, // адрес максимального размера
// сообщения
LPDWORD lpNextSize, // адрес размера следующего сообщения
LPDWORD lpMessageCount, // адрес количества сообщений
LPDWORD lpReadTimeout); // адрес времени ожидания
Через параметр hMailslot функции передается идентификатор канала Mailslot, состояние которого необходимо определить.
Остальные параметры задаются как указатели на переменные типа DWORD, в которые будут записаны параметры состояния канала Mailslot.
В переменную, адрес которой передается через параметр lpMaxMessageSize, после возвращения из функции GetMailslotInfo будет записан максимальный размер сообщения. Вы можете использовать это значение для динамического получения буфера памяти, в который это сообщение будет прочитано функцией ReadFile.
В переменную, адрес которой указан через параметр lpNextSize, записывается размер следующего сообщения, если оно есть в канале. Если же в канале больше нет сообщений, в эту переменную будет записана константа MAILSLOT_NO_MESSAGE.
С помощью параметра lpMessageCount вы можете определить количество сообщений, записанных в канал клиентскими процессами. Если это количество равно нулю, вам не следует вызывать функцию ReadFile для чтения несуществующего сообщения.
И, наконец, в переменную, адрес которой задается в параметре lpReadTimeout, записывается текущее время ожидания, установленное для канала (в миллисекундах).
Если вам не нужна вся информация, которую можно получить с помощью функции GetMailslotInfo, некоторые из ее параметров (кроме, разумеется, первого) можно указать как NULL.
В случае успешного завершения функция GetMailslotInfo возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Ниже мы привели пример использоания функции GetMailslotInfo:
BOOL fReturnCode;
DWORD cbMessages;
DWORD cbMsgNumber;
fReturnCode = GetMailslotInfo(hMailslot, NULL, &cbMessages,
&cbMsgNumber, NULL);
Определение текущего набора национальных параметров для задачи
В любой момент времени задача может получить установленный для нее идентификатор текущего набора установленных параметров, если воспользуется функцией GetThreadLocals:
LCID GetThreadLocale(VOID);
Эта функция не имеет параметров. Она возвращает 32-разрядный идентификатор национального набора параметров, из которого при помощи различных макрокоманд можно выделить различные компоненты.
С помощью макрокоманды LANGIDFROMLCID вы можете выделить из идентификатора набора национальных параметров идентификатор национального языка:
WORD LANGIDFROMLCID(
LCID lcid); // идентификатор набора национальных параметров
#define LANGIDFROMLCID(lcid) ((WORD)(lcid))
Далее, с помощью макрокоманды PRIMARYLANGID нетрудно выделить из идентификатора национального языка первичный идентификатор языка:
WORD PRIMARYLANGID(
WORD lgid); // идентификатор национального языка
#define PRIMARYLANGID(lgid) ((WORD)(lgid) & 0x3ff)
Аналогично, макрокоманда SUBLANGID позволяет выделить из идентификатора национального языка вторичный идентификатор языка (диалект):
WORD SUBLANGID(
WORD lgid); // идентификатор национального языка
#define SUBLANGID(lgid) ((WORD)(lgid) >> 10)
Определения и глобальные переменные
Так как объем нашей книги ограничен, мы решили не приводить по одному примеру приложений на каждый метод работы с файлами (синхронный, асинхронный, отображение файла на память), а совместить все в одном приложении.
Для этого мы сделали три определения, показанных ниже:
#define SYNCHRONOUS_IO 1 // синхронные операции
#define ASYNCHRONOUS_IO 2 // асинхроные операции
#define MEMORYMAPPED_IO 3 // отображение на память
Каждая из этих строк соответствует одному из режимов работы с файлами.
Для того чтобы исходные тексты были оттранслированы для использования нужного вам режима работы с файлами, необходимо определить значение макропеременной FILEOP. Это можно сделать одним из трех способов:
//#define FILEOP SYNCHRONOUS_IO
//#define FILEOP ASYNCHRONOUS_IO
#define FILEOP MEMORYMAPPED_IO
Перед трансляцией исходных текстов приложения вы должны выбрать одну из этих строк, закрыв две другие символами комментария. При этом с помощью операторов условной трансляции, расположенных в исходных текстах приложения, будет выбран нужный вариант исходного текста.
Строка включения файла определений oem2char.h должна расплагаться после строки определения макропеременной FILEOP, так как в этом файле тоже имеются операторы условной трансляции.
В глобальной переменной fConversionType хранится текущий режим преобразования, который можно изменить при помощи диалоговой панели Conversion Options, показанной на рис. 1.3. Сразу после запуска приложения в этой переменной хранится значение OEM_TO_ANSI, в результате чего приложение будет выполнять перекодировку из OEM в ANSI. Функция диалога диалоговой панели Conversion Options может записать в переменную fConversionType значение ANSI_TO_OEM. В результате приложение будет перекодировать текстовые файлы из ANSI в OEM.
Далее в области глобальных переменных определены переменные для хранения идентификаторов файлов hSrcFile, hDstFile, а также буфер cBuf размером 2048 байт:
HANDLE hSrcFile;
#if FILEOP != MEMORYMAPPED_IO
HANDLE hDstFile;
CHAR cBuf[2048];
#endif
В переменной hSrcFile хранится идентификатор исходного файла. Что же касается переменной hDstFile, то она предназначена для хранения идентификатора файла, в который будет записан результат перекодировки. Эта переменная, а также буфер для временного хранения перекодируемых данных cBuf не нужны при работе с использованием отображения файла в память. Поэтому если значение макропеременной FILEOP не равно константе MEMORYMAPPED_IO, строки определения переменной hDstFile и буфера cBuf пропускаются при трансляции исходных текстов приложения.
В начале своей работы приложение DiskInfo получает список имен дисков в виде текстовых строк. Каждая такая строка закрыта двоичным нулем, а последняя - двумя нулевыми байтами. Адрес списка приложение записывает в глобальную переменную lpLogicalDriveStrings.
После получения списка имен приложение сканирует его с целью подсчета количества дисковых устройств. Это количество сохраняется в глобальной переменной nNumDirves.
Далее приложение заказывает память для массива структур типа DISKINFO:
typedef struct tagDISKINFO
{
char szDriveName[10]; // имя диска
UINT nDriveType; // тип диска
char szVolumeName[30]; // имя тома
DWORD dwVolumeSerialNumber; // серийный номер
DWORD dwMaxFileNameLength; // длина имени
DWORD dwFileSystemFlags; // системные флаги
char szFileSystemName[10]; // имя файловой системы
int iImage; // номер пиктограммы
DWORD dwFreeSpace; // свободное пространство
DWORD dwTotalSpace; // общий объем диска
} DISKINFO;
В каждой структуре этого массива будет храниться информация о соответствующем логическом диске, такая, например, как имя и тип диска, имя тома и так далее. В процессе работы приложение будет отображать и обновлять информацию, записанную в массиве.
Адрес массива структур DISKINFO хранится в глобальной переменной pdi.
В глобальном массиве szAppName хранится текстовая строка с названием приложения. Это название будет использовано приложением STIME для поиска главного окна приложения RCLOCK.
В глобальных переменных cxChar и cyChar хранятся метрики шрифта с фиксированной шириной символов, которые будут определены на этапе создания главного окна приложения при обработке сообщения WM_CREATE.
Структура rc типа RECT предназначена для хранения размеров окна рабочего стола.
Буфер szBuf используется для хранения данных, передаваемых из приложения STIME при помощи сообщения WM_COPYDATA.
Помимо всего прочего в области глобальных переменных определен массив szServerAppName, в котором хранится имя приложения RCLOCK. Это имя будет использовано в функции WinMain для поиска главного окна серверного приложения.
Идентификатор главного окна найденного приложения RCLOCK хранится в глобальной переменной hWndServer. Этот идентификатор используется для посылки сообщения WM_COPYDATA функцией SendMessage.
В области глобальных переменных определена структура cd типа COPYDATASTRUCT, которая совместно с глобальным буфером szBuf используется для передачи текстовой строки в приложение RCLOCK.
Кроме того, определен буфер szTerminated, в котором находится строка символов Terminated. Эта строка передается в приложение RCLOCK перед завершением работы приложения STIME.
Остановка сервиса
Остановка сервиса выполняется посылкой сервису команды SERVICE_CONTROL_STOP, для чего эта комнда передается функции ControlService:
ControlService(schService, SERVICE_CONTROL_STOP, &ss);
Отключение серверного процесса от клиентского процесса
Если сервер работает с несколькими клиентскими процессами, то он может использовать для этого несколько реализаций канала, причем одни и те же реализации могут применяться по очереди.
Установив канал с клиентским процессом при помощи функции ConnectNamedPipe, серверный процесс может затем разорвать канал, вызвав для этого функцию DisconnectNamedPipe. После этого реализация канала может быть вновь использована для соединения с другим клиентским процессом.
Прототип функции DisconnectNamedPipe мы привели ниже:
BOOL DisconnectNamedPipe(HANDLE hNamedPipe);
Через параметр hNamedPipe функции передается идентификатор реализации канала Pipe, полученный от функции CreateNamedPipe.
В случае успеха функция возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить от функции GetLastError.
Открытие канала Mailslot
Прежде чем приступить к работе с каналом Mailslot, клиентский процесс должен его открыть. Для выполнения этой операции следует использовать функцию CreateFile, например, так:
LPSTR lpszMailslotName = "\\\\.\\mailslot\\$MailslotName$";
hMailslot = CreateFile(
lpszMailslotName, GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
Здесь в качестве первого параметра функции CreateFile передается имя канала Mailslot. Заметим, что вы можете открыть канал Mailslot, созданный на другой рабочей станции в сети. Для этого строка имени канала, передаваемая функции CreateFile, должна иметь следующий вид:
\\ИмяРабочейСтанции\mailslot\[Путь]ИмяКанала
Можно открыть канал для передачи сообщений всем рабочим станциям заданного домена. Для этого необходимо задать имя по следующему образцу:
\\ИмяДомена\mailslot\[Путь]ИмяКанала
Для передачи сообщений одновременно всем рабочим станциям сети первичного домена имя задается следующим образом:
\\*\mailslot\[Путь]ИмяКанала
В качестве второго параметра функции CreateFile мы передаем константу GENERIC_WRITE. Эта константа определяет, что над открываемым каналом будет выполняться операция записи. Напомним, что клиентский процесс может только посылать сообщения в канал Mailslot, но не читать их оттуда. Чтение сообщений из канала Mailslot - задача для серверного процесса.
Третий параметр указан как FILE_SHARE_READ, и это тоже необходимо, так как сервер может читать сообщения, посылаемые одновременно несколькими клиентскими процессами.
Обратите также внимание на константу OPEN_EXISTING. Она используется потому, что функция CreateFile открывает существующий канал, а не создает новый.
Открывание отображения
Если несколько процессов используют совместно одно и то же отображение, первый процесс создает это отображение с помощью функции CreateFileMapping, указав имя отображения, а остальные должны открыть его, вызвав функцию OpenFileMapping:
HANDLE OpenFileMapping(
DWORD dwDesiredAccess, // режим доступа
BOOL bInheritHandle, // флаг наследования
LPCTSTR lpName); // адрес имени отображения файла
Через параметр lpName этой функции следует передать имя открываемого отображения. Имя должно быть задано точно также, как при создании отображения функцией CreateFileMapping.
Параметр dwDesiredAccess определяет требуемый режим доступа к отображению и указывается точно также, как и для описанной выше функции MapViewOfFile.
Параметр bInheritHandle определяет возможность наследования идентификатора отображения. Если он равен TRUE, порожденные процессы могут наследовать идентификатор, если FALSE - то нет.
Отмена отображения файла
Если созданное отображение больше не нужно, его следует отменить с помощью функции UnmapViewOfFile:
BOOL UnmapViewOfFile(LPVOID lpBaseAddress);
Через единственный параметр этой функции необходимо передать адрес области отображения, полученный от функций MapViewOfFile или MapViewOfFileEx.
В случае успеха функция возвращает значение TRUE. При этом гарантируется, что все измененные страницы оперативной памяти, расположенные в отменяемой области отображения, будут записаны на диск в отображаемый файл. При ошибке функция возвращает значение FALSE.
Если приложение создало несколько отображений для файла, перед завершением работы с файлом все они должны быть отменены с помощью функции UnmapViewOfFile. Далее с помощью функции CloseHandle следует закрыть идентификаторы отображения, полученный от функции CreateFileMapping и CreateFile.
Отображение страниц DLL-библиотеки
В среде Microsoft Windows NT DLL-библиотека загружается в страницы виртуальной памяти, которые отображаются в адресные пространства всех “заинтересованных” приложений, которым нужны функции из этой библиотеки. При этом используется механизм, аналогичный отображению файлов на память, рассмотренный в первой главе нашей книги.
На рис. 3.4 схематически показано отображение кода и данных DLL-библиотеки в адресные пространства двух приложений.
Рис. 3.4. Отображение DLL-библиотеки в адресные пространства двух процессов
На этом рисунке показано, что в глобальном пуле памяти находится один экземпляр кода DLL-библиотеки, который отображается в адресные пространства приложений (процессов). Что же касается данных DLL-библиотеки, то для каждого приложения в глобальном пуле создается отдельная область. Таким образом, различные приложения не могут в этом случае передавать друг другу данные через общую область данных DLL-библиотеки, как это можно было делать в среде операционной системы Microsoft Windows версии 3.1.
Тем не менее, принципиальная возможность создания глобальных областей памяти DLL-библиотеки, доступных разным процессам, существует. Для этого необходимо при редактировании описать область данных DLL-библиотеки как SHARED.
Заметим, что одна и та же функция DLL-библиотеки может отображаться на разные адреса в различные адресные пространства приложений. Это же относится к глобальным и статическим переменным DLL-библиотеки - они будут отображаться на разные адреса для различных приложений.
Когда первое приложение загрузит DLL-библиотеку в память (явно или неявно), эта библиотека (точнее говоря, страницы памяти, в которые она загружена) будет отображена в адресное прстранство этого приложения. Если теперь другое приложение попытается загрузить ту же самую библиотеку еще раз, то для него будет создано новое отображение тех же самых страниц. На этот раз страницы могут быть отображены уже на другие адреса.
Кроме того, для каждой DLL?библиотеки система ведет счетчик использования (usage count). Содержимое этого счетчика увеличивается при очередной загрузке библиотеки в память и уменьшается при освобождении библиотеки.
Когда содержимое счетчика использования DLL-библиотеки станет равным нулю, библиотека будет выгружена из памяти.
Передача сообщений между приложениями
Метод передачи данных между процессами, основанный на использовании файлов, отображенных на виртуальную память, работает достаточно быстро, так как процессы имеют прямой доступ к общим страницам памяти. Тем не менее, при использовании этого метода необходимо заботиться о синхронизации процессов, для чего следует использовать объекты синхронизации.
Если скорость передачи данных не является критичной, можно воспользоваться удобным способом передачи данных, не требующим синхронизации. Этот метод основан на передаче сообщения WM_COPYDATA из одного приложения в другое при помощи функции SendMessage (функцию PostMessage для передачи этого сообщения использовать нельзя).
Сообщение WM_COPYDATA использует оба параметра - wParam и lParam. Через параметр wParam необходимо передать идентификатор окна, посылающего сообщение. Параметр lParam используется для передачи указателя на предварительно заполненную структуру COPYDATASTRUCT, в которой находится ссылка на передаваемые данные.
Если приложение обрабатывает сообщение WM_COPYDATA, то соответствующий обработчик должен вернуть значение TRUE, а если нет - FALSE.
Ниже мы привели формат структуры COPYDATASTRUCT:
typedef struct tagCOPYDATASTRUCT
{
DWORD dwData; // 32-разрядные данные
DWORD cbData; // размер передаваемого буфера с данными
PVOID lpData; // указатель на буфер с данными
} COPYDATASTRUCT;
Перед посылкой сообщения WM_COPYDATA приложение должно заполнить структуру COPYDATASTRUCT.
В поле dwData можно записать произвольное 32-разрядное значение, которое будет передано вместе с сообщением.
В поле lpData вы дополнительно можете записать указатель на область данных, полученную, например, при помощи функции HeapAlloc. Размер этой области следует записать в поле cbData.
Когда функция окна принимающего приложения получит сообщение WM_COPYDATA, она должна скопировать в свой локальный буфер данные, которые были переданы вместе с сообщением. И это единственное, что можно с этими данными сделать. Их, например, нельзя изменять. Не следует также надеятся, что данные сохранятся до обработки другого сообщения, поэтому копирование следует выполнить во время обработки сообщения WM_COPYDATA.
Если вам достаточно передать из одного приложения в другое 32-разрядное значение, в поле lpData можно записать константу NULL.