Снова о файлах
В последней главе 26 тома “Библиотеки системного программиста”, который называется “Программирование для Windows NT. Часть первая”, мы рассказали вам о том, как приложения Microsoft Windows NT работают с файлами. При этом мы привели краткое описание функций программного интерфейса операционной системы, выполняющие “классические” файловые операции, такие как открывание и закрывание файла, чтение блока данных из файла в буфер, расположенный в оперативной памяти, запись содержимого такого буфера в файл и так далее. Все эти операции знакомы вам по операционным системам MS-DOS и Microsoft Windows версии 3.1.
Что же касается операционных систем Microsoft Windows NT и Microsoft Windows 95, то в них появилось новое мощное средство, предназначенное для работы с файлами - файлы, отображаемые в память. Это средство, которое, кстати, может использоваться еще и для обмена данными между параллельно работающими процессами, значительно упрощает программирование файловых операций, сводя их к работе с оперативной памятью.
Например, вы можете создать файл, содержащий записи реляционной базы данных. Открыв затем такой файл с использованием отображения на память, приложение может адресоваться к записям как к элементам массива, расположенного в оперативной памяти. При этом операционная система при необходимотси будет самостоятельно выполнять чтение данных из файла и запись данных в файл без специальных усилий со стороны приложения.
После того как мы изучим методику работы с файлами, отображаемыми на память, мы приведем примеры приложений, работающих с файловой системой обычными средствами.
Передача данных между процессами
Когда вы создавали приложения для операционной системы Microsoft Windows версии 3.1, вы могли организовать передачу данных между параллельно работающими приложениями либо через общую область памяти, либо с использованием механизма динамического обмена данными DDE, либо при помощи средств привязки и вставки объектов OLE, либо через универсальный буфер обмена Clipboard.
Что же касается Microsoft Windows NT, то в среде этой операционной системы вы по-прежнему можете пользоваться DDE, OLE и Clipboard, однако прямое создание глобальных областей памяти, доступных всем приложениям, невозможно. Причина этого лежит в том, что адресные пространства 32-разрядных приложений, работающих под управлением операционных систем Microsoft Windows NT и Microsoft Windows 95, полностью изолированы.
Что же предлагается взамен?
В программном интерфейсе Microsoft Windows NT предусмотрен достаточно широкий набор средств организации передачи данных между процессами, который вовсе не ограничивается относительно медленными механизмами DDE и OLE.
Прежде всего, вы можете организовать передачу данных между процессами, работающими в разных адресных пространствах, с использованием файлов, отображенных на память. О том, как работать с такими файлами, вы узнали из предыдущей главы.
Методика использования файлов, отображенных на память, для передачи данных между процессами заключается в следующем.
Один из процессов создает такой файл, задавая при этом имя отображения. Это имя является глобальным и доступно для всех процессов, запущенных в системе. Другие процессы могут воспользоваться именем отображения, открыв созданный ранее файл. В результате оба процесса могут получить указатели на область памяти, для которой выполнено отображение, и эти указатели будут ссылаться на одни и те же страницы виртуальной памяти. Обмениваясь данными через эту область, процессы должны обеспечить синхронизацию своей работы, например, с помощью критических секций, событий, объектов Mutex или семафоров (в зависимости от логики процесса обмена данными).
Если вы привыкли передавать данные вместе с сообщениями (что является общепринятой практикой в программировании для Microsoft Windows), то мы советуем вам обратить внимание на сообщение WM_COPYDATA. Это сообщение позволяет передавать данные между различными процессами за счет копирования передаваемых данных из адресного пространства одного процесса в адресное пространство другого процесса.
Еще один способ организации обмена данными между процессами заключается в организации специально предназначенных для этого каналов Pipe. Такие каналы напоминают файлы и достаточно удобны в работе. После их создания для обмена данными вы можете вызывать хорошо знакомые вам функции ReadFile и WriteFile, предназначенные для работы с файлами.
В том случае, когда требуется обеспечить передачу данных только в одном направлении, можно использовать так называемые каналы Mailslot.
Каналы Mailslot удобны тем, что их можно использовать для организации широковещательной передачи данных между процессами, запущенными на различных рабочих станциях сети. Что же касается каналов Pipe, то с их помощью можно организовать взаимодействие только двух процессов, но не широковещательную передачу данных.
Библиотеки динамической компоновки
Библиотеки динамической компоновки DLL (Dynamic Link Libraries) являются стержневым компонентом операционной системы Windows NT и многих приложений Windows. Без преувеличения можно сказать, что вся операционная система Windows, все ее драйверы, а также другие расширения есть ни что иное, как набор библиотек динамической компоновки. Редкое крупное приложение Windows не имеет собственных библиотек динамической компоновки, и ни одно приложение не может обойтись без вызова функций, расположенных в таких библиотеках. В частности, все функции программного интерфейса Windows NT находятся именно в библиотеках динамической компоновки DLL.
В 13 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть третья” мы уже рассказывали о создании и использовании библиотек DLL в среде операционной системы Microsoft Windows версии 3.1. Что же касается операционных систем Microsoft Windows NT и Microsoft Windows 95, то в них библиотеки DLL создаются и работают по-другому.
Национальные Параметры
Операционная система Microsoft Windows NT разрабатывалась таким образом, чтобы максимально облегчить разработку приложений, адаптированных для использования в различных странах (как сейчас модно говорить, локализованных приложений).
В чем здесь проблема?
В каждой стране используется свой национальный язык, свои символы, своя система обозначения времени и даты и так далее. Если бы разработчики пытались учесть в своих приложениях все национальные особенности, им бы пришлось затратить немало времени на создание приложений. К счастью, в операционной системе Microsoft Windows NT имеется специальный программный интерфейс, значительно облегчающий процедуру создания “интернациональных” приложений.
Сервисные процессы
Помимо обычных процессов, в операционной системе Microsoft Windows NT создаются так называемые сервисные процессы или сервисы (services). Эти процессы могут стартовать автоматически при загрузке операционной системы, по запросу приложений или других сервисов, а также в ручном режиме.
Функции, выполняемые сервисами, могут быть самыми разнообразными: от обслуживания аппаратуры и программных интерфейсов до серверов приложений, таких, например, как серверы баз данных или серверы World Wide Web (WWW).
Информация о всех серверах, установленных в системе, хранится в регистрационной базе данных. Ниже мы привели путь к этой информации:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Servicies
Для просмотра и редактирования регистрационной базы данных вы можете воспользоваться приложением regedt32.exe, которое находится в каталоге winnt\system32. Однако в программном интерфейсе WIN32 имеется набор функций, специально предназначенных для работы с записями регистрационной базы данных, имеющих отношение к сервисным процессам.
Чтобы просмотреть список установленных сервисов, вы можете запустить приложение Services из папки Control Panel. В диалоговой панели Services (рис. 5.1) отображается список установленных сервисов, их текущее состояние и режим запуска (ручной или автоматический).
Рис. 5.1. Диалоговая панель приложения Services, предназначенная для управления сервисными процессами
Если сделать двойной щелчок левой клавишей мыши по названию сервиса, на экране появится диалоговая панель, с помощью которой можно настроить параметры сервиса (рис. 5.2).
Рис. 5.2. Диалоговая панель, предназначенная для настройки параметров сервиса
С помощью группы переключателей Startup Type вы можете выбрать один из трех способов запуска сервиса.
Если включен переключатель Automatic, сервис будет запускаться автоматически при загрузке операционной системы. Этот режим удобен для тех сервисов, которые нужны постоянно, например для сервера базы данных. При этом сервер базы данных будет запускаться автоматически без участия оператора, что очень удобно.
При включении переключателя Manual сервис будет запускаться в ручном режиме. Пользователь может запустить сервис, нажав кнопку Start в диалоговой панели Services, показанной на рис. 5.1. Другое приложение или другой сервис также может запустить этот сервис при помощи специальной функции программного интерфейса WIN32. Эту функцию мы рассмотрим позже.
И, наконец, если включить переключатель Disabled, работа сервиса будет заблокирована.
Сервис может работать с привилегиями выбранных пользователей или с привилегиями системы LocalSystem. Для выбора имени пользователя в ы должны включить переключатель This Account и выбрать из списка, расположенного справа от этого переключателя, имя пользователя. Дополнительно в полях Password и Confirm Password необходимо ввести пароль пользователя.
Если включить переключатель System Account, сервис будет работать с привилегиями системы. Если сервис будет взаимодействовать с программным интерфейсом рабочего стола Desktop, следует включить переключатель Allow Service to Interact with Desktop.
Сервисы могут быть двух типов: стандартные сервисы и сервисы, соответствующие протоколам драйверов устройств Microsoft Windows NT. Последние описаны в документации DDK и не рассмотрены в нашей книге. Нажав в диалоговой панели Services, показанной на рис. 5.1, кнопку HW Profiles, вы можете выбрать один из установленных файлов конфигурации аппаратуры, которая обслуживается данным сервисом (рис. 5.3), разрешить или запретить использование выбранного файла конфигурации.
Рис. 5.3. Выбор файла конфигурации аппаратуры
При запуске операционной системы Microsoft Windows NT автоматически стартует специальный процесс, который называется процессом управления сервисами (Service Control Manager). В программном интерфейсе WIN32 имеются функции, с помощью которых приложения и сервисы могут управлять работой сервисов, обращаясь к процессу управления сервисами. Некоторые из этих функций будут рассмотрены в нашей книге.
Анализ DLL-библиотек при помощи программы dumpbin.exe
В комплекте системы разработки Microsoft Visual C++ входит программа dumpbin.exe, предназначенная для запуска из командной строки. С помощью этой утилиты вы сможете проанализировать содержимое любого загрузочного файла в формате COFF, в том числе DLL-библиотеки, определив имена экспортируемых функций, их порядковые номера, имена DLL-библиотек и номера функций, импортируемых из этих библиотек и т. д. Можно даже дизассемблировать секции кода с использованием таблицы символов, если такая имеется в файле.
Выберем для исследования DLL-библиотеку comdlg32.dll, в которой находятся функции для работы со стандартными диалоговыми панелями.
Вначале запустим программу dumpbin.exe, передав ей в качестве параметра имя DLL-библиотеки:
c:\msdev\bin>dumpbin comdg32.dll > lst.txt
Перед запуском программы dumpbin.exe мы скопировали файл comdg32.dll в каталог c:\msdev\bin.
Программа запишет в файл lst.txt информацию о типе файла (DLL-библиотека) и перечислит названия секций и их размер, как это показано ниже:
Microsoft (R) COFF Binary File Dumper Version 3.10.6038
Copyright (C) Microsoft Corp 1992-1996. All rights reserved.
Dump of file comdlg32.dll
File Type: DLL
Summary
4000 .data
1000 .edata
2000 .rdata
2000 .reloc
9000 .rsrc
17000 .text
Ниже мы перечислили названия некоторых стандартных секций (полное описание вы найдете в документации, которая поставляется в составе использованного вами средства разработки приложений для Microsoft Windows NT):
Название | Описание | ||
.data | Секция инициализированных данных | ||
.text | Секция кода | ||
.rdata | Данные, которые можно только читать во время выполнения | ||
.edata | Таблица экспортируемых имен | ||
.reloc | Таблица перемещений | ||
.rsrc | Ресурсы | ||
.bss | Секция неинициализированных данных | ||
.xdata | Таблица обработки исключений | ||
.CRT | Данные библиотеки C, которые можно только читать во время выполнения | ||
.debug | Отладочная информация | ||
.tls | Локальная память задач |
Для просмотра более подробной информации о секции следует воспользоваться параметром /SECTION:
c:\msdev\bin>dumpbin comdg32.dll /SECTION:.data > lst.txt
Результат выполнения этой команды показан ниже:
Microsoft (R) COFF Binary File Dumper Version 3.10.6038
Copyright (C) Microsoft Corp 1992-1996. All rights reserved.
Dump of file comdlg32.dll
File Type: DLL
SECTION HEADER #3
.data name
35E4 virtual size
1A000 virtual address
E00 size of raw data
18C00 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C8000040 flags
Initialized Data
Not Paged
(no align specified)
Read Write
Summary
4000 .data
Аналогичная информация о секции .text приведена ниже:
Microsoft (R) COFF Binary File Dumper Version 3.10.6038
Copyright (C) Microsoft Corp 1992-1996. All rights reserved.
Dump of file comdlg32.dll
File Type: DLL
SECTION HEADER #1
.text name
16FF9 virtual size
1000 virtual address
17000 size of raw data
400 file pointer to raw data
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
68000020 flags
Code
Not Paged
(no align specified)
Execute Read
Summary
17000 .text
Для просмотра списка имен экспортируемых функций и глобальных переменных запустите программу dumpbin.exe с параметром /EXPORTS:
c:\msdev\bin>dumpbin comdg32.dll /EXPORTS > lst.txt
Список появится в следующем виде:
Microsoft (R) COFF Binary File Dumper Version 3.10.6038
Copyright (C) Microsoft Corp 1992-1996. All rights reserved.
Dump of file comdlg32.dll
File Type: DLL
Section contains the following Exports for comdlg32.dll
0 characteristics
30D0D8ED time date stamp Fri Dec 15 05:09:49 1995
0.00 version
1 ordinal base
23 number of functions
23 number of names
ordinal hint name
1 0 ChooseColorA (00012442)
2 1 ChooseColorW (0001164F)
3 2 ChooseFontA (00009235)
4 3 ChooseFontW (00014028)
5 4 CommDlgExtendedError (0000F92A)
6 5 FindTextA (0001373A)
7 6 FindTextW (0001372A)
8 7 GetFileTitleA (0000FB57)
9 8 GetFileTitleW (0000FAF1)
10 9 GetOpenFileNameA (00004952)
11 A GetOpenFileNameW (0000F9EE)
12 B GetSaveFileNameA (0000493B)
13 C GetSaveFileNameW (0000FA37)
14 D LoadAlterBitmap (0000A450)
15 E PageSetupDlgA (00014277)
16 F PageSetupDlgW (00014373)
17 10 PrintDlgA (0000738E)
18 11 PrintDlgW (00014238)
19 12 ReplaceTextA (0001375A)
20 13 ReplaceTextW (0001374A)
21 14 WantArrows (000122D5)
22 15 dwLBSubclass (000018F7)
23 16 dwOKSubclass (000018C4)
Summary
4000 .data
1000 .edata
2000 .rdata
2000 .reloc
9000 .rsrc
17000 .text
Наряду с именами функций здесь отображаются порядковые номера функций и их адреса (в скобках).
Указав параметр /DISASM, вы можете дизассемблировать секцию кода, однако полученный в результате листинг может оказаться слишком большим и трудным для анализа. Вот фрагмент такого листинга:
Microsoft (R) COFF Binary File Dumper Version 3.10.6038
Copyright (C) Microsoft Corp 1992-1996. All rights reserved.
Dump of file comdlg32.dll
File Type: DLL
77DF1000: 83 3D EC C4 E0 77 cmp dword ptr ds:[77E0C4ECh],0
00
77DF1007: 56 push esi
77DF1008: 75 2D jne 77DF1037
77DF100A: 83 3D 8C C7 E0 77 cmp dword ptr ds:[77E0C78Ch],0
00
77DF1011: 8B 74 24 08 mov esi,dword ptr [esp+8]
77DF1015: 0F 84 93 97 00 00 je 77DFA7AE
77DF101B: 83 FE 01 cmp esi,1
77DF101E: 1B C0 sbb eax,eax
77DF1020: 24 FE and al,0FEh
77DF1022: 05 02 7F 00 00 add eax,7F02h
77DF1027: 50 push eax
77DF1028: 6A 00 push 0
В заключение этого раздела приведем список параметров программы dumpbin.exe.
Параметр | Описание |
/ALL | Просмотр всей доступной информации, исключая листинг дизассемблирования |
/ARCHIVEMEMBERS | Просмотр минимальной информации об объектах библиотеки |
/DISASM | Дизассемблирование секции кода |
/EXPORTS | Просмотр экспортируемых имен |
/FPO | Просмотр записи FPO (Frame Pointer Optimization) |
/HEADERS | Просмотр заголовка файла и заголовков каждой секции, либо заголовка каждого объекта, расположенного в библиотеке |
/IMPORTS | Просмотр импортированных имен |
/LINENUMBERS | Просмотр номеров строк формата COFF |
/LINKERMEMBER | Просмотр доступных символов, опередленных в библиотеке как public |
/OUT:ИмяФайла | Запись выходной информации не на стандартное устройство вывода (консоль), а в файл с именем ИмяФайла |
/RAWDATA | Просмотр дампа каждой секции |
/RELOCATIONS | Просмотр таблицы перемещений |
/SECTION:Секция | Просмотр информации только о секции, имеющей имя Секция |
/SUMMARY | Просмотр минимальной информации о секциях |
/SYMBOLS | Просмотр таблицы символов формата COFF |
Аннотация
В этой книге продолжается рассказ о программировании для операционной системы Microsoft Windows NT, начатый в предыдущем томе “Библиотеки системного программиста”. Мы продолжим изучение файловой системы, в частности, рассмотрим работу с файлами, отображаемыми на память. Отдельная глава книги будет посвящена организации передачи данных между параллельно работающими процессами. Мы также расскажем вам о библиотеках динамической загрузки DLL, программном интерфейсе, предназначенном для создания многоязычных приложений и сервисных процессах.
Парктически все сведения, изложенные в книге, пригодятся вам и при создании приложений для операционной системы Windows 95.
В книге вы найдете много исходных текстов приложений, которые можно приобрести отдельно на дискете.
Библиотека импорта
Для того чтобы редактор связей мог создать ссылку, в файл проекта приложения вы должны включить так называемую библиотеку импорта (import library). Эта библиотека создается автоматически системой разработки Microsoft Visual C++.
Следует заметить, что стандартные библиотеки систем разработки приложений Windows содержат как обычные объектные модули, предназначенные для статической компоновки, так и ссылки на различные стандартные DLL-библиотеки, экспортирующие функции программного интерфейса операционной системы Windows.
Чтение данных из канала
Как и следовало ожидать, для чтения данных из канала можно воспользоваться функцией ReadFile, например, так:
HANDLE hNamedPipe;
DWORD cbRead;
char szBuf[256];
ReadFile(hNamedPipe, szBuf, 512, &cbRead, NULL);
Данные, прочитанные из канала hNamedPipe, будут записаны в буфер szBuf, имеющий размер 512 байт. Количество действительно прочитанных байт данных будет сохранено функцией ReadFile в переменной cbRead. Так как последний параметр функции указан как NULL, используется синхронный режим работы без перекрытия.
Чтение сообщений из канала Mailslot
Серверный процесс может читать сообщения из созданного им канала Mailslot при помощи функции ReadFile, как это показано ниже:
HANDLE hMailslot;
char szBuf[512];
DWORD cbRead;
ReadFile(hMailslot, szBuf, 512, &cbRead, NULL);
Через первый параметр функции ReadFile передается идентификатор созданного ранее канала Mailslot, полученный от функции CreateMailslot. Второй и третий параметры задают, соответственно, адрес буфера для сообщения и его размер.
Заметим, что перед выполнением операции чтения следует проверить состояние канала Mailslot. Если в нем нет сообщений, то функцию ReadFile вызывать не следует. Для проверки состояния канала вы должны воспользоваться функцией GetMailslotInfo, описанной ниже.
Динамический импорт функций во время выполнения приложения
В некоторых случаях невозможно выполнить динамическую компоновку на этапе редактирования. Вы можете, например, создать приложение, которое состоит из основного модуля и дополнительных, реализованных в виде DLL-библиотек. Состав этих дополнительных модулей и имена файлов, содержащих DLL-библиотеки, может изменяться, при этом в приложение могут добавляться новые возможности.
Если вы, например, разрабатываете систему распознавания речи, то можете сделать ее в виде основного приложения и набора DLL-библиотек, по одной библиотеке для каждого национального языка. В продажу система может поступить в комплекте с одной или двумя библиотеками, но в дальнейшем пользователь сможет купить дополнительные библиотеки и, просто переписав новые библиотеки на диск, получить возможность работы с другими языками. При этом основной модуль приложения не может "знать" заранее имена файлов дополнительных DLL-библиотек, поэтому статическая компоновка с использованием библиотеки импорта невозможна.
Однако приложение может в любой момент времени загрузить любую DLL-библиотеку, вызвав специально предназначенную для этого функцию программного интерфейса Windows с именем LoadLibrary. Приведем ее прототип:
HINSTANCE WINAPI LoadLibrary(LPCSTR lpszLibFileName);
Параметр функции является указателем на текстовую строку, закрытую двоичным нулем. В эту строку перед вызовом функции следует записать путь к файлу DLL-библиотеки или имя этого файла. Если путь к файлу не указан, при поиске выполняется последовательный просмотр следующих каталогов:
каталог, из которого запущено приложение;
текущий каталог;
32-разрядный системный каталог Microsoft Windows NT;
16-разрядный системный каталог;
каталог в котором находится операционная система Windows NT;
каталоги, перечисленные в переменной описания среды PATH
Если файл DLL-библиотеки найден, функция LoadLibrary возвращает идентификатор модуля библиотеки. В противном случае возвращается значение NULL. При этом код ошибки можно получить при помощи функции GetLastError.
Функция LoadLibrary может быть вызвана разными приложениями для одной и той же DLL-библиотеки несколько раз. В этом случае в среде операционной системы Microsoft Windows версии 3.1 загрузка DLL-библиотеки выполняется только один раз. Последующие вызовы функции LoadLibrary приводят только к увеличению счетчика использования DLL-библиотеки. Что же касается Microsoft Windows NT, то при многократном вызове функции LoadLibrary различными процессами функция инициализации DLL-библиотеки получает несколько раз управление с кодом причины вызова, равным значению DLL_PROCESS_ATTACH.
В качестве примера приведем фрагмент исходного текста приложения, загружающего DLL-библиотеку из файла DLLDEMO.DLL:
typedef HWND (WINAPI *MYDLLPROC)(LPSTR);
MYDLLPROC GetAppWindow;
HANDLE hDLL;
hDLL = LoadLibrary("DLLDEMO.DLL");
if(hDLL != NULL)
{
GetAppWindow = (MYDLLPROC)GetProcAddress(hDLL,
"FindApplicationWindow");
if(GetAppWindow != NULL)
{
if(GetAppWindow(szWindowTitle) != NULL)
MessageBox(NULL, "Application window was found",
szAppTitle, MB_OK | MB_ICONINFORMATION);
else
MessageBox(NULL, "Application window was not found",
szAppTitle, MB_OK | MB_ICONINFORMATION);
}
FreeLibrary(hDLL);
}
Здесь вначале с помощью функции LoadLibrary выполняется попытка загрузки DLL-библиотеки DLLDEMO.DLL. В случае успеха приложение получает адрес точки входа для функции с именем FindApplicationWindow, для чего используется функция GetProcAddress. Этой функцией мы займемся немного позже.
Если точка входа получена, функция вызывается через указатель GetAppWindow.
После использования DLL-библиотека освобождается при помощи функции FreeLibrary, прототип который показан ниже:
void WINAPI FreeLibrary(HINSTANCE hLibrary);
В качестве параметра этой функции следует передать идентификатор освобождаемой библиотеки.
При освобождении DLL-библиотеки ее счетчик использования уменьшается. Если этот счетчик становится равным нулю (что происходит, когда все приложения, работавшие с библиотекой, освободили ее или завершили свою работу), DLL-библиотека выгружается из памяти.
Каждый раз при освобождении DLL-библиотеки вызывается функция DLLEntryPoint с параметрами DLL_PROCESS_DETACH или DLL_THREAD_DETACH, выполняющая все необходимые завершающие действия.
Теперь о функции GetProcAddress.
Для того чтобы вызвать функцию из библиотеки, зная ее идентификатор, необходимо получить значение дальнего указателя на эту функцию, вызвав функцию GetProcAddress:
FARPROC WINAPI GetProcAddress(HINSTANCE hLibrary,
LPCSTR lpszProcName);
Через параметр hLibrary вы должны передать функции идентификатор DLL-библиотеки, полученный ранее от функции LoadLibrary.
Параметр lpszProcName является дальним указателем на строку, содержащую имя функции или ее порядковый номер, преобразованный макрокомандой MAKEINTRESOURCE.
Приведем фрагмент кода, в котором определяются адреса двух функций. В первом случае используется имя функции, а во втором - ее порядковый номер:
FARPROC lpMsg;
FARPROC lpTellMe;
lpMsg = GetProcAddress(hLib, "Msg");
lpTellMe = GetProcAddress(hLib, MAKEINTRESOURCE(8));
Перед тем как передать управление функции по полученному адресу, следует убедиться в том, что этот адрес не равен NULL:
if(lpMsg != (FARPROC)NULL)
{
(*lpMsg)((LPSTR)"My message");
}
Для того чтобы включить механизм проверки типов передаваемых параметров, вы можете определить свой тип - указатель на функцию, и затем использовать его для преобразования типа адреса, полученного от функции GetProcAddress:
typedef int (PASCAL *LPGETZ)(int x, int y);
LPGETZ lpGetZ;
lpGetZ = (LPGETZ)GetProcAddress(hLib, "GetZ");
А что произойдет, если приложение при помощи функции LoadLibrary попытается загрузить DLL-библиотеку, которой нет на диске?
В этом случае операционная система Microsoft Windows NT выведет на экран диалоговую панель с сообщением о том, что она не может найти нужную DLL-библиотеку. В некоторых случаях появление такого сообщения нежелательно, так как либо вас не устраивает внешний вид этой диалоговой панели, либо по логике работы вашего приложения описанная ситуация является нормальной.
Для того чтобы отключить режим вывода диалоговой панели с сообщением о невозможности загрузки DLL-библиотеки, вы можете использовать функцию SetErrorMode, передав ей в качестве параметра значение SEM_FAILCRITICALERRORS:
UINT nPrevErrorMode;
nPrevErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
hDLL = LoadLibrary("DLLDEMO.DLL");
if(hDLL != NULL)
{
// Работа с DLL-библиотекой
. . .
}
SetErrorMode(nPrevErrorMode);
Приведем прототип функции SetErrorMode:
UINT WINAPI SetErrorMode(UINT fuErrorMode);
Эта функция позволяет отключать встроенный в Windows обработчик критических ошибок. В качестве параметра этой функции можно указывать комбинацию следующих значений:
Значение |
Описание |
SEM_FAILCRITICALERRORS |
Операционная система Microsoft Windows NT не выводит на экран сообщения обработчика критических ошибок, возвращая приложению соответствующий код ошибки |
SEM_NOGPFAULTERRORBOX |
На экран не выводится сообщение об ошибке защиты памяти. Этот флаг может использоваться только при отладке приложений, если они имеют собственный обработчик такой ошибки |
SEM_NOOPENFILEERRORBOX |
Если Microsoft Windows NT не может открыть файл, на экран не выводится диалоговая панель с сообщением об ошибке |
DLL-библиотеки в операционной системе Windows NT
В операционной системе Microsoft Windows версии 3.1 после загрузки DLL-библиотека становилась как бы частью операционной системы. DLL-библиотека является модулем и находится в памяти в единственном экземпляре, содержит сегменты кода и ресурсы, а так же один сегмент данных (рис. 3.3). Можно сказать, что для DLL-библиотеки создается одна копия (instance), состоящая только из сегмента данных, и один модуль, состоящий из кода и ресурсов.
Рис. 3.3. Структура DLL-библиотеки в памяти
DLL-библиотека, в отличие от приложения, не имеет стека и очереди сообщения. Функции, расположенные в модуле DLL-библиотеки, выполняются в контексте вызвавшей их задачи. При этом они пользуются стеком копии приложения, так как собственного стека в DLL-библиотеке не предусмотрено. Тем не менее, в среде операционной системы Microsoft Windows версии 3.1 функции, расположенные в 16-разрядной DLL-библиотеке, пользуются сегментом данных, принадлежащей этой библиотеке, а не копии приложения.
Создавая приложения для операционной системы Microsoft Windows версии 3.1, вы делали DLL-библиотеки для коллективного использования ресурсов или данных, расположенных в сегменте данных библиотеки. Функции, входящие в состав 16-разрядной DLL-библиотеки, могут заказывать блоки памяти с атрибутом GMEM_SHARE. Такой блок памяти не принадлежит ни одному приложению и поэтому не освобождается автоматически при завершении работы приложения. Так как в Windows версии 3.1 все приложения используют общую глобальную память, блоки памяти с атрибутом GMEM_SHARE можно использовать для обмена данными между приложениями. Управлять таким обменом могут, например, функции, расположенные в соответствующей DLL-библиотеке.
Когда разные приложения, запущенные в среде операционной системы Microsoft Windows версии 3.1, обращались к одной и той же функции DLL-библиотеки, то они все использовали для этого один и тот же адрес. Это и понятно - так как все приложения работали на одной виртуальной машине, то все они находились в одном адресном пространстве.
В операционной системе Microsoft Windows NT каждое приложение работает в рамках отдельного адресного пространства. Поэтому для того чтобы приложение могло вызывать функции из DLL-библиотеки, эта библиотека должна находиться в адресном пространстве приложения.
Здесь мы обращаем ваше внимание на первое отличие механизма динамической компоновки в среде Microsoft Windows NT от аналогичного механизма для Microsoft Windows версии 3.1.
Другие функции
Среди других функций, предназначенных для работы с каналами Pipe, мы рассмотрим функции CallNamedPipe, TransactNamedPipe, PeekNamedPipe, WaitNamedPipe, SetNamedPipeHandleState, GetNamedPipeInfo, GetNamedPipeHandleState.
Файл определения модуля для DLL-библиотеки
Файл определения модуля для DLL-библиотеки отличается от соответствующего файла обычного приложения Windows. В качестве примера приведем образец такого файла:
LIBRARY DLLNAME
DESCRIPTION 'DLL-библиотека DLLNAME'
EXPORTS
DrawBitmap=MyDraw @4
ShowAll
HideAll
MyPoolPtr @5 CONSTANT
GetMyPool @8 NONAME
FreeMyPool @9 NONAME
В файле определения модуля DLL-библиотеки вместо оператора NAME должен находиться оператор LIBRARY, определяющий имя модуля DLL-библиотеки, под которым она будет известна Windows. Однако формат строк файла описания модуля зависит от используемой системы разработки. Например, если вы создаете DLL-библиотеку при помощи Microsoft Visual C++ версии 4.0, оператор LIBRARY можно не указывать.
Файл rclock.h
В файле rclock.h (листинг 2.4) находятся прототипы функций, определенных в приложении RCLOCK.
Листинг 2.4. Файл rclock/rclock.h
// -----------------------------------------------------
// Описание функций
// -----------------------------------------------------
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void WndProc_OnDestroy(HWND hWnd);
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct);
void WndProc_OnPaint(HWND hWnd);
Файл rclock.rc
В файле rclock.rc (листинг 2.6) определены ресурсы приложения RCLOCK.
Листинг 2.6. Файл rclock/rclock.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
//////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
#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 "RCLOCK.ICO"
IDI_APPICONSM ICON DISCARDABLE "RCLOCKSM.ICO"
//////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
IDD_DIALOG1, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 162
TOPMARGIN, 7
BOTTOMMARGIN, 52
END
END
#endif // APSTUDIO_INVOKED
#endif // English (U.S.) resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Файл resource.h
Файл resource.h (листинг 2.5) создается автоматически и содержит определения для файла описания ресурсов приложения RCLOCK.
Листинг 2.5. Файл rclock/resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by RCLOCK.RC
//
#define IDR_APPMENU 102
#define IDI_APPICON 103
#define IDI_APPICONSM 104
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 106
#define _APS_NEXT_COMMAND_VALUE 40006
#define _APS_NEXT_CONTROL_VALUE 1010
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Файл resource.h (листинг 2.9) создается автоматически и содержит определения для файла описания ресурсов приложения STIME.
Листинг 2.9. Файл rclock/stime/resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by STIME.RC
//
#define IDR_APPMENU 102
#define IDI_APPICON 103
#define IDI_APPICONSM 104
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 106
#define _APS_NEXT_COMMAND_VALUE 40006
#define _APS_NEXT_CONTROL_VALUE 1010
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Файл stime.h
В файле stime.h (листинг 2.8) определен идентификатор таймера CLOCK_TIMER, а также прототипы функций.
Листинг 2.8. Файл rclock/stime/stime.h
#define CLOCK_TIMER 100
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void WndProc_OnDestroy(HWND hWnd);
BOOL WndProc_OnCreate(HWND hWnd,
LPCREATESTRUCT lpCreateStruct);
void WndProc_OnPaint(HWND hWnd);
void WndProc_OnTimer(HWND hWnd, UINT id);
Файл stime.rc
В файле stime.rc (листинг 2.10) определены ресурсы приложения STIME.
Листинг 2.10. Файл rclock/stime/stime.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
//////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
#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 "STIME.ICO"
IDI_APPICONSM ICON DISCARDABLE "STIMESM.ICO"
//////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
IDD_DIALOG1, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 162
TOPMARGIN, 7
BOTTOMMARGIN, 52
END
END
#endif // APSTUDIO_INVOKED
#endif // English (U.S.) resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Файлы, отображаемые на память
В операционную систему Microsoft Windows NT встроен эффективный механизм виртуальной памяти, описанный нами в предыдущем томе “Библиотеки системного программиста”. При использовании этого механизма приложениям доступно больше виртуальной оперативной памяти, чем объем физической оперативной памяти, установленной в компьютере.
Как вы знаете, виртуальная память реализована с использованием обычной оперативной памяти и дисковой памяти. Когда приложение обращается к странице виртуальной памяти, отсутствующей в физической памяти, операционная система автоматически читает ее из файла виртуальной памяти в физическую память и предоставляет приложению. Если же приложение изменяет содержимое страницы памяти в физической оперативной памяти, операционная система сохраняет такую страницу в файле виртуальной памяти.
Почему мы вспомнили о виртуальной памяти в главе, посвященной файлам?
Потому что механизм работы с файлами, отображаемыми на память, напоминает механизм работы вирутальной памяти.
Напомним, что в операционной системе Microsoft Windows NT каждому процессу выделяется 2 Гбайта адресного пространства. Любой фрагмент этого пространства может быть отображен на фрагмент файла соответствующего размера.
На рис. 1.1 показано отображение фрагмента адресного пространства приложения, размером в 1 Гбайт, на фрагмент файла, имеющего размер 5 Гбайт.
Рис. 1.1. Отображение фрагмента адресного пространства приложения на файл
С помощью соответсвующей функции программного интерфейса, которую мы скоро рассмотрим, приложение Microsoft Windows NT может выбрать любой фрагмент большого файла для отображения в адресное пространство. Поэтому, несмотря на ограничение адресного пространства величиной 2 Гбайт, вы можете отображать (по частям) в это пространство файлы любой длины, возможной в Microsoft Windows NT. В простейшем случае при работе с относительно небольшими файлами вы можете выбрать в адресном пространстве фрагмент подходящего размера и отобразить его на начало файла.
Как выполняется отображение фрагмента адресного пространства на фрагмент файла?
Если установлено такое отображение, то операционная система обеспечивает тождественность содержимого отображаемого фрагмента памяти и фрагмента файла, выполняя при необходимости операции чтения и записи в файл (с буферизацией и кешированием).
В процессе отображения адресного пространства память не выделяется, а только резервируется. Поэтому отображение фрагмента размером 1 Гбайт не вызовет переполнение файлов виртуальной памяти. Более того, при отображении файлы виртуальной памяти вообще не используются, так как страницы фрагмента связываются с отображаемым файлом.
Если приложение обращается в отображенный фрагмент для чтения, возникает исключение. Обработчик этого исключения загружает в физическую оперативную память соответствующую страницу из отображенного файла, а затем возобновляет выполнение прерванной команды. В результате в физическую оперативную память загружаются только те страницы, которые нужны приложению.
При записи происходит аналогичный процесс. Если нужной страницы нет в памяти, она подгружается из отображенного файла, затем в нее выполняется запись. Загруженная страница остается в памяти до тех пор, пока не будет вытеснена другой страницей при нехватке физической оперативной памяти. Что же касается записи измененной страницы в файл, то эта запись будет выполнена при закрытии файла, по явному запросу приложения или при выгрузке страницы из физической памяти для загрузки в нее другой страницы.
Заметим, что операционная система Microsoft Windows NT активно работает с файлами, отобажаемыми в память. В частности, при загрузке исполнимого модуля приложения соответствующий exe- или dll-файл отображается на память, а затем ему передается управление. Когда пользователь запускает вторую копию приложения, для работы используется файл, который уже отображается в память. В этом случае соответствующие страницы виртуальной памяти отображаются в адресные пространства обоих приложений. При этом возникает одна интересная проблема, связанная с использованием глобальных переменных.
Представьте себе, что в приложении определены глобальные переменные. При запуске приложения область глобальных переменных загружается в память наряду с исполнимым кодом. Если запущены две копии приложения, то страницы памяти, отведенные для кода и глобальных данных, будут отображаться в адресные пространства двух разных процессов. При этом существует потенциальная возможность конфликта, когда разные работающие копии одного и того же приложения попытаются установить разные значения для одних и тех же глобальных переменных.
Операционная система Microsoft Windows NT выходит из этой ситуации следующим способом. Если она обнаружит, что одна из копий приложения пытается изменить страницу, в которой хранятся глобальные переменные, она создает для нее еще одну копию страницы. Таким образом, между приложениями никогда не возникает интерференции.
Аналогичная методика используется и для страниц, содержащих программный код. Если приложение (например, отладчик) пытается изменить страницу, содержащую исполнимый код, операционная система Microsoft Windows NT создает еще одну копию страницы и отображает ее в адресное пространство соответствующего процесса. Подробнее об этом мы расскажем в разделе, посвященном использованию файлов, отображаемых на память, для передачи данных между различными процессами.
Форматное преобразование даты и времени
В этом разделе мы рассмотрим две функции, предназначенные для получения текстовых строк времени и даты. Формат этих строк зависит от указанного идентификатора набора национальных параметров.
Функции для работы с каналами
В этом разделе мы опишем наиболее важные функции программного интерфейса Microsoft Windows NT, предназначенные для работы с каналами Pipes. Более подробную информацию вы найдете в документации, которая поставляется в составе библиотеки разработчика Microsoft Development Library (MSDN).
Функция CallNamedPipe
Обычно сценарий взаимодействия клиентского процесса с серверным заключается в выполнении следующих операций:
подключение к каналу с помощью функции CreateFile;
выполнение операций чтения или записи такими функциями как ReadFile или WriteFile;
отключение от канала функцией CloseHandle.
Функция CallNamedPipe позволяет выполнить эти операции за один прием, при условии что канал открыт в режиме передачи сообщений и что клиент посылает одно сообщение серверу и в ответ также получает от сервера одно сообщение.
Ниже мы привели прототип функции CallNamedPipe:
BOOL CallNamedPipe(
LPCTSTR lpNamedPipeName, // адрес имени канала
LPVOID lpOutBuffer, // адрес буфера для записи
DWORD nOutBufferSize, // размер буфера для записи
LPVOID lpInBuffer, // адрес буфера для чтения
DWORD nInBufferSize, // размер буфера для чтения
LPDWORD lpBytesRead, // адрес переменной для записи
// количества прочитанных байт данных
DWORD nTimeOut); // время ожидания в миллисекундах
Перед вызовом функции CallNamedPipe вы должны записать в параметр lpNamedPipeName указатель на текстовую строку, содержащую имя канала Pipe. При формировании этой строки вы должны руководствоваться теми же правилами, что и при использовании функции CreateFile.
Кроме имени канала, вы также должны подготовить буфер, содержащий передаваемые через канал данные. Адрес и размер этого буфера следует указать функции CallNamedPipe, соответственно, через параметры lpOutBuffer и nOutBufferSize.
Данные, полученные от сервера в ответ на посланное ему сообщение, будут записаны в буфер, который вы тоже должны подготовить заранее. Адрес этого буфера необходимо указать через параметр lpInBuffer, а размер буфера - через параметр nInBufferSize.
В переменную, адрес которой указан через параметр lpBytesRead, записывается количество байт, полученных через канал от сервера.
Параметр nTimeOut определяет, в течении какого времени функция CallNamedPipe будет ожидать доступности канала Pipe, прежде чем она вернет код ошибки. Помимо численного значения в миллисекундах, вы можете указать в этом параметре одну из следующих констант:
Константа | Описание | ||
NMPWAIT_NOWAIT | Ожидание канала Pipe не выполняется. Если канал не доступен, функция CallNamedPipe сразу возвращает код ошибки | ||
NMPWAIT_WAIT_FOREVER | Ожидание выполняется бесконечно долго | ||
NMPWAIT_USE_DEFAULT_WAIT | Ожидание выполняется в течении периода времени, указанного при вызове функции CreateNamedPipe |
В случае успешного завершения функция CallNamedPipe возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Заметим, что может возникнуть ситуация, при которой длина сообщения, полученного от сервера, превосходит размер буфера, предусмотренного процессом. В этом случае функция CallNamedPipe завершится с ошибкой, а функция GetLastError вернет значение ERROR_MORE_DATA. Не существует никакого способа получить через канал оставшуюся часть сообщения, так как перед возвращением управления функция CallNamedPipe закрывает канал с сервером.
Функция CreateNamedPipe
Для создания именованного канала Pipe вы должны использовать функцию CreateNamedPipe. Вот прототип этой функции:
HANDLE CreateNamedPipe(
LPCTSTR lpName, // адрес строки имени канала
DWORD dwOpenMode, // режим открытия канала
DWORD dwPipeMode, // режим работы канала
DWORD nMaxInstances, // максимальное количество
// реализаций канала
DWORD nOutBufferSize, // размер выходного буфера в байтах
DWORD nInBufferSize, // размер входного буфера в байтах
DWORD nDefaultTimeOut, // время ожидания в миллисекундах
LPSECURITY_ATTRIBUTES lpSecurityAttributes); // адрес
// переменной для атрибутов защиты
Через параметр lpName передается адрес строки имени канала в форме \\.\pipe\ИмяКанала (напомним, что при создании канала имя сервера не указывается, так как канал можно создать только на той рабочей станции, где запущен процесс, создающий канал).
Параметр dwOpenMode задает режим, в котором открывается канал. Остановимся на этом параметре подробнее.
Канал Pipe может быть ориентирован либо на передачу потока байт, либо на передачу сообщений. В первом случае данные через канал передаются по байтам, во втором - отдельными блоками заданной длины.
Режим работы канала (ориентированный на передачу байт или сообщений) задается, соответственно, константами PIPE_TYPE_BYTE или PIPE_TYPE_MESSAGE, которые указываются в параметре dwOpenMode. По умолчанию используется режим PIPE_TYPE_BYTE.
Помимо способа передачи данных через канал, с помощью параметра dwOpenMode можно указать, будет ли данный канал использован только для чтения данных, только для записи или одновременно для чтения и записи. Способ использования канала задается указанием одной из следующих констант:
Константа | Использование канала | ||
PIPE_ACCESS_INBOUND | Только для чтения | ||
PIPE_ACCESS_OUTBOUND | Только для записи | ||
PIPE_ACCESS_DUPLEX | Для чтения и записи |
Перечисленные выше параметры должны быть одинаковы для всех реализаций канала (о реализациях канала мы говорили выше). Далее мы перечислим параметры, которые могут отличаться для разных реализаций канала:
Константа | Использование канала | ||
PIPE_READMODE_BYTE | Канал открывается на чтение в режиме последовательной передачи отдельных байт | ||
PIPE_READMODE_MESSAGE | Канал открывается на чтение в режиме передачи отдельных сообщений указанной длины | ||
PIPE_WAIT | Канал будет работать в блокирующем режиме, когда процесс переводится в состояние ожидания до завершения операций в канале | ||
PIPE_NOWAIT | Неблокирующий режим работы канала. Если операция не может быть выполнена немедленно, в неблокирующем режиме функция завершается с ошибкой | ||
FILE_FLAG_OVERLAPPED | Использование асинхронных операций (ввод и вывод с перекрытием). Данный режим позволяет процессу выполнять полезную работу параллельно с проведением операций в канале | ||
FILE_FLAG_WRITE_THROUGH | В этом режиме функции, работающие с каналом, не возвращают управление до тех пор, пока не будет полностью завершена операция на удаленном компьютере. Используется только с каналом, ориентированном на передачу отдельных байт и только в том случае, когда канал создан между процессами, запущенными на различных станциях сети |
Дополнительно к перечисленным выше флагам, через параметр dwOpenMode можно передавать следующие флаги защиты:
Флаг |
Описание |
WRITE_DAC |
Вызывающий процесс должен иметь права доступа на запись к произвольному управляющему списку доступа именованного канала access control list (ACL) |
WRITE_OWNER |
Вызывающий процесс должен иметь права доступа на запись к процессу, владеющему именованным каналом Pipe |
ACCESS_SYSTEM_SECURITY |
Вызывающий процесс должен иметь права доступа на запись к управляющему списку доступа именованного канала access control list (ACL) |
Теперь перейдем к параметру dwPipeMode, определяющему режим работы канала. В этом параметре вы можете указать перечисленные выше константы PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE, PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE, PIPE_WAIT и PIPE_NOWAIT. Для всех реализаций канала необходимо указывать один и тот же набор констант.
Параметр nMaxInstances определяет максимальное количество реализаций, которые могут быть созданы для канала. Вы можете указывать здесь значения от 1 до PIPE_UNLIMITED_INSTANCES. В последнем случае максимальное количество реализаций ограничивается только наличием свободных системных ресурсов.
Заметим, что если один серверный процесс использует несколько реализаций канала для связи с несколькими клиенсткими, то общее количество реализаций может быть меньше, чем потенциальное максимальное количество клиентов. Это связано с тем, что клиенты могут использовать реализации по очереди, если только они не пожелают связаться с серверным процессом все одновременно.
Параметры nOutBufferSize и nInBufferSize определяют, соответственно, размер буферов, используемых для записи в канал и чтения из канала. При необходимости система может использовать буферы других, по сравнению с указанными, размеров.
Параметр nDefaultTimeOut определяет время ожидания для реализации канала. Для всех реализаций необходимо указывать одинаковое значение этого параметра.
Через параметр lpPipeAttributes передается адрес переменной, содержащей атрибуты защиты для создаваемого канала. В наших приложениях мы будем указывать этот параметр как NULL. В результате канал будет иметь атрибуты защиты, принятые по умолчанию.
В случае успеха функция CreateNamedPipe возвращает идентификатор созданной реализации канала, который можно использовать в операциях чтения и записи, выполняемых с помощью таких функций, как ReadFile и WriteFile.
При ошибке функция CreateNamedPipe возвращает значение INVALID_HANDLE_VALUE. Код ошибки вы можете уточнить, вызвав функцию GetLastError.
Функция CreatePipe
Анонимный канал создается функцией CreatePipe, имеющей следующий прототип:
BOOL CreatePipe(
PHANDLE hReadPipe, // адрес переменной, в которую будет
// записан идентификатор канала для
// чтения данных
PHANDLE hWritePipe, // адрес переменной, в которую будет
// записан идентификатор канала для
// записи данных
LPSECURITY_ATTRIBUTES lpPipeAttributes, // адрес переменной
// для атрибутов защиты
DWORD nSize); // количество байт памяти,
// зарезервированной для канала
Канал может использоваться как для записи в него данных, так и для чтения. Поэтому при создании канала функция CreatePipe возвращает два идентификатора, записывая их по адресу, заданному в параметрах hReadPipe и hWritePipe.
Идентификатор, записанный по адресу hReadPipe, можно передавать в качестве параметра функции ReadFile или ReadFileEx для выполнения операции чтения. Идентификатор, записанный по адресу hWritePipe, передается функции WriteFile или WriteFileEx для выполнения операции записи.
Через параметр lpPipeAttributes передается адрес переменной, содержащей атрибуты защиты для создаваемого канала. В наших приложениях мы будем указывать этот параметр как NULL. В результате канал будет иметь атрибуты защиты, принятые по умолчанию.
И, наконец, параметр nSize определяет размер буфера для создаваемого канала. Если этот размер указан как нуль, будет создан буфер с размером, принятым по умолчанию. Заметим, что при необходимости система может изменить указанный вами размер буфера.
В случае успеха функция CreatePipe возвращает значение TRUE, при ошибке - FALSE. В последнем случае для уточнения причины возникновения ошибки вы можете воспользоваться функцией GetLastError.
Функция DlgProc
Функция диалога DlgProc обрабатывает сообщения WM_INITDIALOG и WM_COMMAND, поступающие от диалоговой панели Conversion Options. Для обработки этих сообщений вызываются, соответственно, функции DlgProc_OnInitDialog и DlgProc_OnCommand.
Функция DlgProc обрабатывает сообщения WM_INITDIALOG и WM_COMMAND, поступающие в функцию диалога диалоговой панели, предназначенной для ввода заголовка окна. Эти сообщения обрабатываются, соответственно, функциями DlgProc_OnInitDialog и DlgProc_OnCommand.
Функция DlgProc_OnCommand
Когда в диалоговой панели Conversion Options пользователь нажимает одну из кнопок или клавиши <Esc> и <Enter>, в функцию диалога поступает сообщение WM_COMMAND. Обработчик этого сообщения, расположенный в функции DlgProc_OnCommand, определяет текущее состояние переключателей режима перекодировки, расположенных на посверхности диалоговой панели, и записывает соответствующее значение в глобальную переменную fConversionType:
if(IsDlgButtonChecked(hdlg, IDC_OEMANSI))
{
fConversionType = OEM_TO_ANSI;
}
else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM))
{
fConversionType = ANSI_TO_OEM;
}
Если при работе с диалоговой панелью пользователь нажимает кнопку Cancel или клавишу <Esc>, содержимое глобальной переменной fConversionType не изменяется.
Функция DlgProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее в функцию диалога от органов управления, расположенных в диалоговой панели.
Если пользователь нажимает кнопку OK, функция DlgProc_OnCommand извлекает содержимое однострочного текстового редактора (введенное имя заголовока окна), вызывая для этого макрокоманду GetDlgItemText, и сохраняет это содержимое в глобальном буфере szWindowTitle. Затем функция завершает работу диалоговой панели с кодом 1, в результате чего приложение приступит к поиску окна с заданным заголовком.
В том случае, когда пользователь отказался от поиска, нажав в диалоговой панели кнопку Cancel, работа диалоговой панели завершается с нулевым кодом. Поиск окна в этом случае не выполняется.
Функция DlgProc_OnCommand обрабатывает сообщения от органов управления, расположенных в диалоговой панели выбора новой раскладки клавиатуры.
После определения выбранной раскладки она активизируется при помощи функции ActivateKeyboardLayout, как это показано ниже:
uSelectedItem = SendMessage(
GetDlgItem(hdlg, IDC_COMBO1), CB_GETCURSEL, 0, 0);
ActivateKeyboardLayout(*(lpList + uSelectedItem), 0);
Функция DlgProc_OnInitDialog
Сообщение WM_INITDIALOG поступает в функцию диалога при инициализации диалоговой панели. Функция DlgProc_OnInitDialog, обрабатывающая это сообщение, выполняет единственную задачу: она включает переключатель OEM -> ANSI, устанавливая таким образом режим перекодировки из OEM в ANSI:
CheckDlgButton(hdlg, IDC_OEMANSI, 1);
Эта функция возаращает значение TRUE, разрешая отображение диалоговой панели. При создании собственного приложения вы можете добавить в эту функцию код инициализации органов управления диалоговой панели или связанных с этой панелью данных.
Эта функция выполняет инициализацию списка типа COMBOBOX, расположенного в диалоговой панели выбора новой раскладки клавиатуры. В процессе инициализации в список записываются называния национальных языков, соответствующие установленным раскладкам клавиатуры.
Функция GetDiskInfo
Функция GetDiskInfo вызывается обработчиком сообщения WM_CREATE при создании главного окна приложения. Она получает и сохраняет информацию о всех логических дисках, имеющихся в системе.
Из предыдущего тома “Библиотеки системного программиста” вы знаете, что с помощью функции GetLogicalDriveStrings можно получить список имен всех логических дисковых устройств. Через второй параметр этой функции необходимо передать адрес блока памяти, в который будет записан указанный выше список, а через первый - размер этого блока.
Однако заранее приложение не знает, сколько логических дисков имеется в системе и, соответственно, не знает, сколько места потребуется ему для сохранения списка логических устройств. Поэтому вначале мы вызываем функцию GetLogicalDriveStrings, передав ей значение 0 в качестве первого параметра и значение NULL - в качестве второго:
dwDriveStringsSpace = GetLogicalDriveStrings(0, NULL);
В результате функция GetLogicalDriveStrings возвращает размер блока памяти, необходимый для записи всех имен логических дисков.
На следующем шаге наше приложение получает блок памяти необходимого размера, вызывая для этого функцию malloc. Адрес полученного блока и его размер затем передаются функции GetLogicalDriveStrings, которая в этом случае заполняет блок необходимой информацией:
GetLogicalDriveStrings(dwDriveStringsSpace,
lpLogicalDriveStrings);
Если в компьютере имеются, например, логические диски A:, C: и D:, в блок памяти с адресом lpLogicalDriveStrings будут записаны следующие три строки:
A:\<0>
C:\<0>
D:\<0><0>
В конце каждой строки записывается байт с нулевым значением, а в конце последней строки - два нулевых байта.
Получив список имен устройств, наше приложение подсчитывает количество устройств. Для этого оно с помощью функции strchr сканирует список в цикле до тех пор, пока не будет найден его конец, подсчитывая количество проходов в глобальной переменной nNumDirves:
nNumDirves = 0;
for(lpTemp = lpLogicalDriveStrings; *lpTemp != 0;
nNumDirves++)
{
lpTemp = strchr(lpTemp, 0) + 1;
}
Определив таким образом общее количество логических дисков, приложение заказывает память для массива структур типа DISKINFO, в котором будут хранится параметры логических дисков:
pdi = malloc(nNumDirves * sizeof(DISKINFO));
Заполнение массива структур DISKINFO выполняется в цикле.
Для каждого диска прежде всего выполняется копирование имени диска из соответствующей строки списка, полученного ранее при помощи функции GetLogicalDriveStrings.
Далее приложение определяет тип диска, вызывая для этого функцию GetDriveType (в локальной переменной lpTemp хранится имя диска):
(pdi + i)->nDriveType = GetDriveType(lpTemp);
Заполнение полей структуры DISKINFO выполняется по-разному в зависимости от типа диска.
Если устройство со сменным носителем данных, то в поле iImage, предназначенное для хранения номера пиктограммы диска, записывается нулевое значение. Именно под этим номером мы занесли пиктограмму диска со сменным носителем в список пиктограмм для органа управления List View.
В поле szVolumeName мы записываем строку <Unknown>, так как определение фактических параметров устройств со сменным носителем выполняется при обработке извещения NM_DBLCLK. Аналогичным образом заполняются и остальные поля структуры.
Заполнение структуры DISKINFO для устройств чтения CD-ROM выполняется точно так же, как и устройств со сменным носителем данных, за исключением того что в поле номера пиктограммы iImage записывается значение 2. Это номер пиктограммы с изображением накопителя CD-ROM в списке пиктограмм органа управления List View.
Если текущим устройством, для которого мы определяем параметры, является диск с несменным носителем данных, функция GetDiskInfo получает большинство этих параметров при помощи функции GetVolumeInformation, как это показано ниже:
GetVolumeInformation(lpTemp, (pdi + i)->szVolumeName, 30,
&((pdi + i)->dwVolumeSerialNumber),
&((pdi + i)->dwMaxFileNameLength),
&((pdi + i)->dwFileSystemFlags),
(pdi + i)->szFileSystemName, 10);
Для определения общего объема диска и объема свободного пространства дополнительно вызывается функция GetDiskFreeSpace:
GetDiskFreeSpace(lpTemp, &dwSectors, &dwBytes, &dwFreeClusters,
&dwClusters);
Значения, полученные от этой функции, обрабатываются следующим образом:
(pdi + i)->dwFreeSpace = dwSectors * dwBytes*dwFreeClusters;
(pdi + i)->dwTotalSpace = dwSectors * dwBytes*dwClusters;
Объем свободного пространства в байтах записывается в поле dwFreeSpace. Он вычисляется как произведение сделующих величин: количества свободных кластеров на диске dwFreeClusters, количество секторов в одном кластере dwSectors и количества байт в одном сеткоре dwBytes.
Общий объем диска записывается в поле dwTotalSpace и подсчитывается аналогично, за исключением того что вместо количества свобдных кластеров используется общее количество кластеров на диске dwClusters.
В поле iImage записывается значение 1. Это номер пиктограммы с изображением диска с несменным носителем данных.
Получение и заполнение информации об удаленных (сетевых) дисковых устройствах выполняется аналогично, однако в поле iImage записывается значение 3.
Если тип устройства не поддается классификации, наше приложение получает информацию о его параметрах, а в поле iImage записывает значение 1.
Функция GetNamedPipeHandleState
С помощью функции GetNamedPipeHandleState процесс может определить состояние канала Pipe, зная его идентификатор.
Прототип функции GetNamedPipeHandleState мы привели ниже:
BOOL GetNamedPipeHandleState(
HANDLE hNamedPipe, // идентификатор именованного канала
LPDWORD lpState, // адрес флагов состояния канала
LPDWORD lpCurInstances, // адрес количества реализаций
LPDWORD lpMaxCollectionCount, // адрес размера пакета
// передаваемых данных
LPDWORD lpCollectDataTimeout, // адрес максимального
// времени ожидания
LPTSTR lpUserName, // адрес имени пользователя
// клиентского процесса
DWORD nMaxUserNameSize); // размер буфера для
// имени пользователя клиентского процесса
Идентификатор канала, для которого нужно определить состояние, передается функции GetNamedPipeHandleState через параметр hNamedPipe.
Через параметр lpState нужно передать указатель на переменную типа DWORD, в которую будет записан один из флагов состояния канала:
Флаги состояния | Описание | ||
PIPE_WAIT | Канал будет работать в блокирующем режиме, когда процесс переводится в состояние ожидания до завершения операций в канале | ||
PIPE_NOWAIT | Неблокирующий режим работы канала. Если операция не может быть выполнена немедленно, в неблокирующем режиме функция завершается с ошибкой |
Если информация о состоянии канала не требуется, в качестве значения для параметра lpState следует использовать константу NULL.
В переменную, адрес которой передается через параметр lpCurInstances, записывается текущее количество реализаций канала. Если эта информация вам не нужна, передайте через параметр lpCurInstances значение NULL.
Параметры lpMaxCollectionCount и lpCollectDataTimeout позволяют определить, соответственно, размер пакета передаваемых данных и максимальное время ожидания между передачами.
Через параметр lpUserName вы должны передать указатель на буфер, в который функция GetNamedPipeHandleState запишет имя пользователя клиентского процесса. Размер этого буфера задается в параметре nMaxUserNameSize.
При необходимости вы можете задать значения параметров lpMaxCollectionCount, lpCollectDataTimeout и lpUserName как NULL. В этом случае соответствующая информация не будет извлекаться.
В случае успешного завершения функция GetNamedPipeHandleState возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Функция GetNamedPipeInfo
Еще одна функция, позволяющая получить информацию об именованном канале по его идентификатору, называется GetNamedPipeInfo:
BOOL GetNamedPipeInfo(
HANDLE hNamedPipe, // идентификатор канала
LPDWORD lpFlags, // адрес флагов типа канала
LPDWORD lpOutBufferSize, // адрес размера выходного буфера
LPDWORD lpInBufferSize, // адрес размера входного буфера
LPDWORD lpMaxInstances); // адрес максимального количества
// реализаций канала
Параметр hNamedPipe задает идентфикатор именованного канала Pipe, для которого требуется получить информацию.
Через параметр lpFlags функции GetNamedPipeInfo необходимо передать адрес переменной типа DWORD или NULL, если флаги определять не требуется. Ниже мы привели возможные значения флагов:
Флаг | Описание | ||
PIPE_CLIENT_END | Идентификатор ссылается на клиентскую часть канала | ||
PIPE_SERVER_END | Идентификатор ссылается на серверную часть канала | ||
PIPE_TYPE_MESSAGE | Канал работает в режиме передачи сообщений |
В переменные, адреса которых задаются через параметры lpOutBufferSize и lpInBufferSize, функция GetNamedPipeInfo заносит размеры входного и выходного буфера, соответственно. Если эта информация не нужна, передайте через параметры lpOutBufferSize и lpInBufferSize значение NULL.
И, наконец, через параметр lpMaxInstances передается адрес переменной, в которую будет записано максимальное значение реализаций, которое можно создать для данного канала. Если после вызова функции GetNamedPipeInfo в этой переменной записано значение PIPE_UNLIMITED_INSTANCES, количество реализаций ограничивается только свободными системными ресурсами.
В случае успешного завершения функция GetNamedPipeInfo возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Функция main сервисного процесса
В простейшем случае функция main вызывает функцию StartServiceCtrlDispatcher, что необходимо для подключения главной задачи сервисного процесса к процессу управления сервисами. Ниже мы привели пример функции main сервисного процесса:
#define MYServiceName "Sample of simple service"
void main(int agrc, char *argv[])
{
SERVICE_TABLE_ENTRY DispatcherTable[] =
{
{
MYServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain
},
{
NULL, NULL
}
};
if(!StartServiceCtrlDispatcher(DispatcherTable))
{
fprintf(stdout,"StartServiceCtrlDispatcher: Error %ld\n",
GetLastError());
getch();
return;
}
}
Функции StartServiceCtrlDispatcher передается указатель на массив структур типа SERVICE_TABLE_ENTRY. В этом массиве описываются точки входа всех сервисов, определенных в данном файле. Таким образом, в одном файле можно определить сразу несколько сервисов. Последняя строка таблицы всегда должна содержать значения NULL - это признак конца таблицы.
Тип SERVICE_TABLE_ENTRY и соответствующий указатель определены следующим образом:
typedef struct _SERVICE_TABLE_ENTRY
{
LPTSTR lpServiceName;
LPSERVICE_MAIN_FUNCTION lpServiceProc;
} SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY;
В поле lpServiceName записывается указатель на текстовую строку имени сервиса, а в поле lpServiceProc - указатель на точку входа сервиса.
Заметим, что функция main должна вызвать функцию StartServiceCtrlDispatcher достаточно быстро - не позднее чем через 30 секунд после запуска.
Получив управление, функция StartServiceCtrlDispatcher не возвращает его до тех пор, пока все сервисы, запущенные в рамках данного процесса, не завершат свою работу.
При успешном завершении функция StartServiceCtrlDispatcher возвращает значение TRUE. Если же произойдет ошибка, возвращается значение FALSE. Код ошибки можно определить при помощи функции GetLastError.
Функция обработки команд
Как следует из названия, функция обработки команд, зарегистрированная функцией RegisterServiceCtrlHandler, обрабатывает команды, передаваемые сервису операционной системой, другими сервисами или приложениями. Эта функция может иметь любое имя и выглядит следующим образом:
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. Эта функция будет описана в следующем разделе.
Через единственный параметр функция обработки команд получает код команды, который может принимать одно из перечисленных ниже значений.
Значение | Описание | ||
SERVICE_CONTROL_STOP | Остановка сервиса | ||
SERVICE_CONTROL_PAUSE | Временная остановка сервиса | ||
SERVICE_CONTROL_CONTINUE | Продолжение работы сервиса после временной остановки | ||
SERVICE_CONTROL_INTERROGATE | Когда поступает эта команда, сервис должен немедленно сообщить процессу управления сервисами свое состояние | ||
SERVICE_CONTROL_SHUTDOWN | Сервис должен прекратить работу в течении 20 секунд, так как завершается работа операционной системы |
Функция Oem2Char (асинхронные операции с файлами)
Вариант функции Oem2Char, предназначенный для использования асинхронных операций с файлами, выглядит сложнее, однако он позволяет приложению выполнять дополнительную работу в то время когда происходит чтение или запись блока данных.
В асинхронном режиме для чтения и записи необходимо подготовить структуры типа OVERLAPPED. Мы делаем это следующим образом:
OVERLAPPED ovRead;
OVERLAPPED ovWrite;
ovRead.Offset = 0;
ovRead.OffsetHigh = 0;
ovRead.hEvent = NULL;
ovWrite.Offset = 0;
ovWrite.OffsetHigh = 0;
ovWrite.hEvent = NULL;
Структура ovRead используется для выполнения операций чтения. В поля OffsetHigh и Offset этой структуры мы записываем нулевые значения, поэтому чтение будет выполняться с самого начала файла.
Что же касается поля hEvent, то в него мы записываем значение NULL. При этом мы не будем создавать для синхронизации отдельный объект-событие, а воспользуемся идентификатором файла.
Структура ovWrite, которая предназначена для выполнения операций записи, используется аналогичным образом.
После подготовки структур ovRead и ovWrite функция Oem2Char начинает цикл перекодировки.
Прежде всего, в этом цикле запускается операция асинхронного чтения, для чего вызывается функция ReadFile:
bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf),
&dwBytesRead, &ovRead);
В качестве последнего параметра мы передаем этой функции адрес заранее подготовленной структуры ovRead.
Заметим, что в данном случае функция ReadFile вернет управление еще до завершения операции чтения, поэтому в переменную dwBytesRead не будет записано количествао прочитанных байт (так как пока неизвестно, сколько их удастся прочитать).
Далее функция Oem2Char проверяет код возврата фукнции ReadFile. При этом дополнительно вызывается функция GetLastError.
Если при запуске процедуры чтения был достигнут конец файла, эта функция вернет значение ERROR_HANDLE_EOF. В этом случае функция Oem2Char просто возвращает управление.
Если же функция GetLastError вернула значение ERROR_IO_PENDING, то это означает, что в настоящий момент происходит выполнение операции чтения. Приложение может вызвать функцию, например, IdleWork, для выполнения какой-либо фоновой работы.
Перед тем как продолжить свою работу, функция дожидается завершение операции чтения, вызывая для этого функцию WaitForSingleObject, описанную в предыдущем томе “Библиотеки системного программиста”:
WaitForSingleObject(hSrcFile, INFINITE);
При этом главная задача приложения перейдет в состояние ожидания до тех пор, пока не закончится операция чтения. Напомним, что в состоянии ожидания задача не отнимает циклы процессорного времени и не снижает производительность системы.
Далее функция проверяет результат выполнения асинхронной операции чтения, вызывая функцию GetOverlappedResult:
bResult = GetOverlappedResult(hSrcFile, &ovRead,
&dwBytesRead, FALSE);
Помимо всего прочего, эта функция записывает в локальную переменную dwBytesRead количество байт, прочитанных из исходного файла.
После дополнительных проверок ошибок функция Oem2Char выполняет преобразование содержимого буфера, заполненного прочитанными данными.
Вслед за этим в структуре ovRead изменяется содержимое поля Offset:
ovRead.Offset += dwBytesRead;
Значение, которое находится в этом поле, увеличивается на количество прочитанных байт. В результате при очередном вызове функции ReadFile будет запущено чтение для следующего блока данных. Так как мы не изменяем поле OffsetHigh, наше приложение способно работать с файлами, имеющими размер не более 4 Гбайт (что, однако, вполне достаточно).
Асинхронная операция записи прочитанных данных запускается при помощи функции WriteFile:
bResult = WriteFile(hDstFile, cBuf, dwBytesRead,
&dwBytesWritten, &ovWrite);
В качестве последнего параметра этой функции передается адрес заранее подготовленной структуры ovWrite.
После анализа кода возврата функции WriteFile вызывается функция GetOverlappedResult, с помощью которой определяется результат завершения операции записи:
GetOverlappedResult(hDstFile, &ovWrite,
&dwBytesWritten, TRUE);
Так как через последний параметр этой функции передается значение TRUE, функция GetOverlappedResult выполняет ожидание завершения операции записи. Кроме того, в локальную переменную dwBytesWritten эта функция заносит количество байт данных, записанных в выходной файл.
Так как асинхронные операци чтения и записи не изменяют текущую позицию в файле, после выполнения записи мы изменяем соответствующим образом содержимое поля Offset в структуре ovWrite:
ovWrite.Offset += dwBytesWritten;
Далее цикл перекодировки продолжает свою работу.
Функция Oem2Char (отображение файла на память)
Третий вариант функции Oem2Char, который используется для работы через отображение файла на память, выглядит очень просто. В нем даже нет цикла, а перекодировка выполняется за один прием:
void Oem2Char(HANDLE hSrcFile)
{
DWORD dwFileSize;
HANDLE hFileMapping;
LPVOID lpFileMap;
dwFileSize = GetFileSize(hSrcFile, NULL);
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;
if(fConversionType == OEM_TO_ANSI)
OemToCharBuff(lpFileMap, lpFileMap, dwFileSize);
else if(fConversionType == ANSI_TO_OEM)
CharToOemBuff(lpFileMap, lpFileMap, dwFileSize);
UnmapViewOfFile(lpFileMap);
CloseHandle(hFileMapping);
}
Вначале функция Oem2Char определяет размер исходного файла, идентификатор которого передается ей через параметр hSrcFile. Для определения размера файла используется функция GetFileSize.
На следующем шаге с помощью функции CreateFileMapping создается объект-отображение. Страницы этого объекта будут доступны и на чтение, и на запись, так как указан режим PAGE_READWRITE.
Для того чтобы получить доступ к памяти, отображенной на файл, наше приложение вызывает функцию MapViewOfFile. Отображение выполняется для чтения и записи, поэтому мы указали флаг FILE_MAP_WRITE. В случае успешного завершения функции адрес отображенной области записывается в локальную переменную lpFileMap.
Что же касается перекодировки файла, то она выполняется исключительно просто: мы передаем через первые два параметра функции перекодировки адрес, на который отображен файл, а через третий параметр - размер файла.
После того как перекодировка будет выполнена, необходимо вначале отменить отображение, а затем освободить идентификатор объекта-отображения. Первая задача решается в нашем приложении спомощью функции UnmapViewOfFile, а вторая - с помощью функции CloseHandle, которой в качестве единственного параметра передается идентификатор объекта-отображения.
Функция Oem2Char (синхронные операции с файлами)
Если приложение подготовлено таким образом, что оно работает с файлами при помощи синхронных операций, используется следующий вариант исходного текста функции Oem2Char:
void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile)
{
DWORD dwBytesRead;
DWORD dwBytesWritten;
BOOL bResult;
while(TRUE)
{
bResult = ReadFile(hSrcFile, cBuf, 2048,
&dwBytesRead, NULL);
if(bResult && dwBytesRead == 0)
break;
if(fConversionType == OEM_TO_ANSI)
OemToCharBuff(cBuf, cBuf, dwBytesRead);
else if(fConversionType == ANSI_TO_OEM)
CharToOemBuff(cBuf, cBuf, dwBytesRead);
WriteFile(hDstFile, cBuf, dwBytesRead,
&dwBytesWritten, NULL);
}
}
Здесь мы работаем с файлом обычным образом.
Перекодировка файла выполняется в цикле, который прерывается при достижении конца исходного файла.
Функция Oem2Char при помощи функции ReadFile читает фрагмент исходного файла в буфер cBuf, расположенный в области глобальных переменных. Количество прочитанных байт записывается при этом в локальную переменную dwBytesRead.
При достижении конца исходного файла количество байт, прочитанных функцией ReadFile из файла, а также значение, возвращенное этой функцией, равно нулю. Этот факт мы используем для завершения цикла перекодировки.
После прочтения блока данных из исходного файла функция Oem2Char анализирует содержимое глобальной переменной fConversionType, определяя тип преобразования, который нужно выполнить. В зависимости от содержимого этой переменной вызывается либо функция OemToCharBuff, выполняющая преобразование из кодировки OEM в кодировку ANSI, либо функция CharToOemBuff, которая выполняет обратное преобразование.
На следующем этапе преобразованное содержимое буфера cBuf записывается в выходной файл при помощи функции WriteFile. При этом количество действительно записанных байт сохраняется в локальной переменной dwBytesWritten, однако в нашем приложении оно никак не используется.
Сделаем важное замечание относительно функций OemToCharBuff и CharToOemBuff.
Для преобразования текстовых строк из кодировки OEM в кодировку ANSI и обратно в программном интерфейсе операционной системы Microsoft Windows версии 3.1 имелся набор функций, описанный нами в 12 томе “Библиотеки системного программиста”, который называется “Операционная система Microsoft Windows 3.1 для программиста. Часть вторая”. Это такие функции как OemToAnsi, AnsiToOem, OemToAnsiBuff, AnsiToOemBuff.
В программном интерфейсе операционной системы Microsoft Windows NT эти функции оставлены для совместимости, однако ими не рекомендуется пользоваться. Причина заключается в том, что в Microsoft Windows NT можно работать с символами в кодировке Unicode, когда для представления каждого символа используется не один, а два байта.
Соответственно, вместо перечисленных выше функций необходимо использовать функции OemToChar, CharToOem, OemToCharBuff, CharToOemBuff, которые имеют такие же параметры, что и их 16-разрядные прототипы (за исключением того, что функциям OemToCharBuff и CharToOemBuff можно передавать 32-разрядную длину преобразуемой текстовой строки). В зависимости от того, используется ли приложением кодировка Unicode, эти функции могут преобразовывать строки из OEM в строки ANSI или Unicode (и обратно).
Функция PeekNamedPipe
Чтение данных из канала функцией ReadFile вызывает удаление прочитанных данных. В противоположность этому, функция PeekNamedPipe позволяет получить данные из именованного или анонимного канала без удаления, так что при последующих вызовах этой функции или функции ReadFile будут получены все те же данные, что и при первом вызове функции PeekNamedPipe.
Еще одно отличие заключается в том, что функция PeekNamedPipe никогда не переходит в состояние ожидания, сразу возвращая управление вне зависимости от того, есть данные в канале или нет.
Прототип функции PeekNamedPipe представлен ниже:
BOOL PeekNamedPipe(
HANDLE hPipe, // идентификатор канала Pipe
LPVOID lpvBuffer, // адрес буфера для прочитанных данных
DWORD cbBuffer, // размер буфера прочитанных данных
LPDWORD lpcbRead, // адрес переменной, в которую будет
// записано количество действительно
// прочитанных байт данных
LPDWORD lpcbAvail, // адрес переменной, в которую будет
// записано общее количество байт данных,
// доступных в канале для чтения
LPDWORD lpcbMessage); // адрес переменной, в которую будет
// записано количество непрочитанных
// байт в данном сообщении
Через параметр hPipe функции PeekNamedPipe нужно передать идентификатор открытого анонимного или именованного канала Pipe.
Данные, полученные из канала, будут записаны в буфер lpvBuffer, имеющий размер cbBuffer байт. При этом количество действительно прочитанных байт будет сохранено в переменной, адрес которой передается функции PeekNamedPipe через параметр lpcbRead.
В случае успешного завершения функция PeekNamedPipe возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Функция SetNamedPipeHandleState
При необходимости вы можете изменить режимы работы для уже созданного канала Pipe. Для этого предназначена функция SetNamedPipeHandleState, прототип которой мы привели ниже:
BOOL SetNamedPipeHandleState(
HANDLE hNamedPipe, // идентификатор канала Pipe
LPDWORD lpdwMode, // адрес переменной, в которой указан
// новый режим канала
LPDWORD lpcbMaxCollect, // адрес переменной, в которой
// указывается максимальный размер
// пакета, передаваемого в канал
LPDWORD lpdwCollectDataTimeout); // адрес максимальной
// задержки перед передачей данных
Параметр hNamedPipe задает идентификатор канала Pipe, режим работы которого будет изменен.
Новый режим работы записывается в переменную, адрес которой задан через параметр lpdwMode. Вы можете указать одну из следующих констант, определяющих режим работы канала:
Константа | Использование канала | ||
PIPE_READMODE_BYTE | Канал открывается на чтение в режиме последовательной передачи отдельных байт | ||
PIPE_READMODE_MESSAGE | Канал открывается на чтение в режиме передачи отдельных сообщений указанной длины | ||
PIPE_WAIT | Канал будет работать в блокирующем режиме, когда процесс переводится в состояние ожидания до завершения операций в канале | ||
PIPE_NOWAIT | Неблокирующий режим работы канала. Если операция не может быть выполнена немедленно, в неблокирующем режиме функция завершается с ошибкой |
Константы PIPE_WAIT и PIPE_NOWAIT, задающие блокирующий и неблокирующий режим соответственно, можно комбинировать при помощи логической операции ИЛИ с константами PIPE_READMODE_BYTE и PIPE_READMODE_MESSAGE.
Если текущий режим работы канала изменять не нужно, для параметра lpdwMode следует указать значение NULL.
Теперь рассмотрим назначение параметра lpcbMaxCollect.
Если при открытии канала клиентским процессом функцией CreateFile не была указана константа FILE_FLAG_WRITE_THROUGH, то данные передаются пакетами, которые собираются из отдельных сообщений. Размер такого пакета как раз и определяет параметр lpcbMaxCollect.
В том случае, когда вы не собираетесь изменять размер пакета, укажите для параметра lpcbMaxCollect значение NULL.
Параметр lpdwCollectDataTimeout задает максимальный интервал между передачами данных по сети. Если функция SetNamedPipeHandleState изменяет параметры канала со стороны сервера, или если сервер и клиент работают на одном и том же компьютере, параметр lpdwCollectDataTimeout должен быть задан как NULL.
В случае успешного завершения функция SetNamedPipeHandleState возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Функция StartConversion
В задачу функции StartConversion входит выбор и открывание исходного файла и файла, в который будет записан результат перекодировки. Когда приложение работает с файлом в режиме отображения на память, открывается только один файл - исходный.
Для выбора файла мы использовали функцию GetOpenFileName, хорошо знакомую вам по предыдущим томам “Библиотеки системного программиста”, посвященным программированию для операционной системы Microsoft Windows версии 3.1.
Выбранные файлы открываются при помощи функции CreateFile. Однако способ открывания зависит от режима работы с файлами. Ниже мы привели фрагмент исходного текста приложения, в котором открывается исходный файл:
#if FILEOP == SYNCHRONOUS_IO
hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#elif FILEOP == ASYNCHRONOUS_IO
hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, NULL);
#elif FILEOP == MEMORYMAPPED_IO
hSrcFile = CreateFile(ofn.lpstrFile,
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
#endif
В синхронном режиме исходный файл будет читаться последовательно, поэтому мы указали режим GENERIC_READ (только чтение) и флаг FILE_FLAG_SEQUENTIAL_SCAN. Так как в процессе перекодировки исходный файл не будет изменяться, нет причин запрещать чтение этого файла для других процессов. Чтобы предоставить доступ другим процессам на чтение исходного файла, мы указали режим совместного использования файла FILE_SHARE_READ.
В асинхронном режиме необходимо указывать флаг FILE_FLAG_OVERLAPPED, что мы и сделали в нашем примере.
Что же касается режима отображения файла на память, то сдесь при открывании файла мы указали режимы GENERIC_READ и GENERIC_WRITE. В результате файл открывается и для чтения, и для записи.
После того как в режиме отображения файла на память исходный файл будет открыт, функция StartConversion вызывает функцию Oem2Char, передавая ей в качестве единственного параметра идентификатор исходного файла:
Oem2Char(hSrcFile);
CloseHandle(hSrcFile);
Функция Oem2Char выполняет перекодировку файла “по месту”. Далее идентификатор исходного файла закрывается функцией CloseHandle, после чего функция StartConversion возвращает управление.
В синхронном и асинхронном режиме функция StartConversion после открывания исходного файла дополнительно открывает выходной файл, в который будет записан результат перекодировки. Для выбора выходного файла вызывается функция GetSaveFileName.
Так же как и исходный файл, выходной файл открывается при помощи функции CreateFile, причем в синхронном и асинхронном режиме этот файл открывается по-разному:
#if FILEOP == SYNCHRONOUS_IO
hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE,
0, NULL, CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
#elif FILEOP == ASYNCHRONOUS_IO
hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE,
0, NULL, CREATE_ALWAYS,
FILE_FLAG_OVERLAPPED, NULL);
#endif
В синхронном режиме мы указываем режим доступа на запись GENERIC_WRITE и флаг FILE_FLAG_SEQUENTIAL_SCAN (так как запись в выходной файл будет выполняться последовательно от начала до конца).
В асинхронном режиме необходимо указать флаг FILE_FLAG_OVERLAPPED.
Кроме того, в обоих случаях мы указали режим открывания файла CREATE_ALWAYS. В результате выходной файл будет создан заново даже в том случае, если в выбранном каталоге уже есть файл с таким именем. При этом содержимое старого файла будет уничтожено.
После открывания исходного и выходного файла вызывается функция Oem2Char, выполняющая перекодировку, а затем оба файла закрываются при помощи функции CloseHandle:
Oem2Char(hSrcFile, hDstFile);
CloseHandle(hSrcFile);
CloseHandle(hDstFile);
Обратите внимание, что в синхронном и асинхронном режиме работы с файлами в нашем приложении используется другой вариант фукнции Oem2Char - вариант с двумя параметрами. Как вы сейчас увидите, в нашем приложении используются три варианта этой функции.
Функция TransactNamedPipe
Функция TransactNamedPipe, также как и функция CallNamedPipe, предназначена для выполнения передачи и приема данных от клиентского процесса серверному. Однако эта функция более гибкая в использовании, чем функция CallNamedPipe.
Прежде всего, перед использованием функции TransactNamedPipe клиентский процесс должен открыть канал с сервером, воспользовавашись для этого, например, функцией CreateFile.
Кроме того, клиентский процесс может выполнять обмен данными с сервером, вызывая функцию TransactNamedPipe много раз. При этом не будет происходить многократного открытия и закрытия канала.
Прототип функции TransactNamedPipe представлен ниже:
BOOL TransactNamedPipe(
HANDLE hNamedPipe, // идентификатор канала Pipe
LPVOID lpvWriteBuf, // адрес буфера для записи
DWORD cbWriteBuf, // размер буфера для записи
LPVOID lpvReadBuf, // адрес буфера для чтения
DWORD cbReadBuf, // размер буфера для чтения
LPDWORD lpcbRead, // адрес переменной, в которую будет
// записано количество действительно прочитанных байт
LPOVERLAPPED lpov); // адрес структуры типа OVERLAPPED
Через параметр hNamedPipe вы должны передать функции TransactNamedPipe идентификатор предварительно открытого канала. Канал следует открыть в режиме передачи сообщений.
Параметры lpvWriteBuf и cbWriteBuf задают, соответственно, адрес и размер буфера, содержимое которого будет передано через канал.
Ответное сообщение, полученное от сервера, будет записано в буфер с адресом lpvReadBuf. Размер этого буфера следует передать функции TransactNamedPipe через параметр cbReadBuf.
После того как функция TransactNamedPipe вернет управление, в переменную, адрес которой передавался через параметр lpcbRead, будет записан размер принятого от сервера сообщения в байтах.
Если операция передачи данных через канал должна выполняться с перекрытием (асинхронно), вы должны подготовить структуру типа OVERLAPPED и передать ее адрес через параметр lpov. В противном случае параметр lpov должен быть задан как NULL.
В случае успешного завершения функция TransactNamedPipe возвращает значение TRUE, а при ошибке - FALSE. Код ошибки можно получить, вызвав функцию GetLastError.
Если возникает ситуация, при которой длина сообщения, полученного от сервера, превосходит размер буфера, предусмотренного процессом, функция TransactNamedPipe завершится с ошибкой, а функция GetLastError вернет значение ERROR_MORE_DATA. Оставшуюся часть сообщения можно прочитать с помощью такой функции, как ReadFile.
Функция WaitNamedPipe
С помощью функции WaitNamedPipe процесс может выполнять ожидание момента, когда канал Pipe будет доступен для соединения:
BOOL WaitNamedPipe(
LPCTSTR lpszPipeName, // адрес имени канала Pipe
DWORD dwTimeout); // время ожидания в миллисекундах
Через параметр lpszPipeName задается имя канала, для которого выполняется ожидание готовности к соединению. Время ожидания в миллисекундах задается через параметр dwTimeout.
Помимо численного значения в миллисекундах, вы можете указать в этом параметре одну из следующих констант:
Константа | Описание | ||
NMPWAIT_WAIT_FOREVER | Ожидание выполняется бесконечно долго | ||
NMPWAIT_USE_DEFAULT_WAIT | Ожидание выполняется в течении периода времени, указанного при вызове функции CreateNamedPipe |
Если канал стал доступен до истечения периода времени, заданного параметром dwTimeout, функция WaitNamedPipe возвращает значение TRUE. В противном случае возвращается значение FALSE и вы можете воспользоваться функцией GetLastError.
Функция WinMain
Функция WinMain не имеет никаких особенностей. Сразу после запуска приложения она сохраняет идентификатор приложения в глобальной переменной hInst и проверяет, нет ли в памяти копии приложения, запущенной раньше. Для такой проверки используется методика, основанная на вызове функции FindWindow и описанная в предыдущем томе “Библиотеки системного программиста”, посвященного программированию для операционной системы Microsoft Windows NT. Если будет найдена копия работающего приложения, его окно выдвигается на передний план при помощи функций ShowWindow и SetForegroundWindow.
Далее функция WinMain регистрирует класс главного окна прилжения, создает и отображает это окно, а затем запускает цикл обработки сообщений.
Функция WinMain проверяет существование запущенной ранее копии приложения. Если такая копия будет обнаружена, окно работающей копии активизируется и выдвигается на передний план. Если нет - функция WinMain выполняет обычную инициализацию приложения, создавая его главное окно и запуская цикл обработки сообщений.
Функция WinMain приложения RCLOCK сразу после запуска приложения выполняет поиск своей копии, используя для этого функцию FindWindow. Если такая копия найдена, главное окно этой копии выдвигается на передний план функцией SetForegroundWindow, после чего работа функции WinMain завершается. Такая техника уже использовалась нами ранее.
В том случае, когда запускается первая копия приложения RCLOCK, функция WinMain выполняет обычные действия. Она регистрирует класс окна и создает главное окно приложения. Для того чтобы это окно имело вид, показанный на рис. 2.2, для него указываются стили WS_POPUPWINDOW и WS_THICKFRAME:
hWnd = CreateWindow(szAppName, szAppTitle,
WS_POPUPWINDOW | WS_THICKFRAME,
100, 100, 100, 100, NULL, NULL, hInst, NULL);
Для определения размеров и расположения главного окна приложения RCLOCK функция WinMain определяет размеры окна рабочего стола, сохраняя их в глобальной переменной rc:
GetWindowRect(GetDesktopWindow(), &rc);
Размещение главного окна приложения RCLOCK выполняется функцией MoveWindow, как это показано ниже:
MoveWindow(hWnd,
rc.right - cxChar * 25, rc.bottom - cyChar * 3,
cxChar * 10, cyChar * 2, TRUE);
Заметим, что метрики шрифта cxChar и cyChar определяются при обработке сообщения WM_CREATE, который получает управление при вызове функции CreateWindow. Поэтому после возвращения из функции CreateWindow содержимое глобальных переменных cxChar и cyChar будет отражать размеры рабочего стола.
После изменения размеров и расположения главного окна приложения RCLOCK выполняется отображение этого окна и запуск обычного цикла обработки сообщений.
После поиска своей собственной копии приложение STIME ищет окно серверного приложения RCLOCK:
hWndServer = FindWindow(szServerAppName, NULL);
Если это приложение не найдено, выдается сообщение об ошибке, вслед за чем работа приложения STIME завершается.
В случае успешного поиска идентификатор найденного окна приложения RCLOCK записывается в глобальную переменную hWndServer. Вслед за этим выполняется процедура регистрации класса главного окна приложения STIME, создается главное окно приложения и запускается обычный цикл обработки сообщений.
Функция WinMain сохраняет идентификатор прилождения в глобальной переменной hInst а затем проверяет, не было ли это приложение уже запущено. Если было, главное окно приложения выдвигается на передний план.
Далее функция WinMain регистрирует класс главного окна приложения, создает и отображает это окно. Затем запускается обычный цикл обработки сообщений.
Помимо выполнения обычных действий, необходимых для создания главного окна приложения, функция WinMain получает список установленных раскладок клавиатуры, сохраняя его в глобальном массиве lpList. Память для этого массива заказывается динамически, поэтому перед завершением работы приложения мы освобождаем эту память явным образом.
Для определения размера списка и для получения самого списка раскладок клавиатуры мы используем функцию GetKeyboardLayoutList:
UINT uLayouts;
HKL * lpList;
uLayouts = GetKeyboardLayoutList(0, NULL);
lpList = malloc(uLayouts * sizeof(HKL));
uLayouts = GetKeyboardLayoutList(uLayouts, lpList);
Функция WndProc
В задачу функции WndProc входит обработка сообщений, поступающих в главное окно приложения. Если от главного меню приложения приходит сообщение WM_COMMAND, вызывается функция WndProc_OnCommand. При уничтожении главного окна приложения в его функцию поступает сообщение WM_DESTROY, для обработки которого вызывается функция WndProc_OnDestroy.
Все остальные сообщения передаются функции DefWindowProc.
В задачу функции WndProc входит обработка сообщений WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY и WM_SIZE. Для обработки этих сообщений вызываются, соответственно, функции WndProc_OnCreate, WndProc_OnDestroy, WndProc_OnCommand, WndProc_OnNotify и WndProc_OnSize.
Все остальные сообщения передаются функции DefWindowProc, выполняющей обработку по умолчанию.
В задачу функции WndProc входит обработка сообщений WM_CREATE, WM_DESTROY, WM_PAINT и WM_COPYDATA. Для обработки первых трех сообщений при помощи макрокоманды HANDLE_MSG вызываются функции WndProc_OnCreate, WndProc_OnDestroy и WndProc_OnPaint, соответственно.
Для сообщения WM_COPYDATA в файле windowsx.h, к сожалению, не предусмотрены специальные макрокоманды. Мы могли бы подготовить такую макрокоманду самостоятельно, однако, так как обработка сообщения WM_COPYDATA очень проста, мы использовали классический способ:
case WM_COPYDATA:
{
strcpy(szBuf, ((PCOPYDATASTRUCT)lParam)->lpData);
InvalidateRect(hWnd, NULL, TRUE);
break;
}
Напомним, что вместе с параметром lParam сообщения WM_COPYDATA передается указатель на структуру COPYDATASTRUCT. Приложение, посылающее другому приложению сообщение WM_COPYDATA, подготавливает область данных, записывая ее адрес в поле lpData структуры типа COPYDATASTRUCT. Принимающее приложение должно скопировать эти данные в свой внутренний буфер.
В нашем случае в качестве данных передается строка символов, закрытая двоичным нулем, поэтому для копирования мы используем функцию strcpy.
После выполнения копирования обработчик сообщения WM_COPYDATA вызывает функцию InvalidateRect, что в результате приводит к перерисовке главного окна приложения. В этом окне обработчик сообщения WM_PAINT нарисует текстовую строку, полученную от другого приложения и скопированную только что в буфер szBuf.
Функция WndProc обрабатывает сообщения WM_CREATE, WM_DESTROY и WM_TIMER, вызывая для этого функции WndProc_OnCreate, WndProc_OnDestroy и WndProc_OnTimer, соответственно.
Функция WndProc обрабатывает сообщения WM_COMMAND и WM_DESTROY, для чего вызываются, соответственно, функции WndProc_OnCommand и WndProc_OnDestroy.
Функция WndProc_OnCommand
Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Для обработки мы использовали макрокоманду HANDLE_MSG, описанную в предыдущем томе “Библиотеки системного программиста”.
Если пользователь выберет из меню File строку Convert, будет выполняться преобразование файла. Для этого функция WndProc_OnCommand вызовет функцию StartConversion, передав ей в качестве единственного параметра идентификатор главного окна приложения.
При выборе из меню File строки Options приложение выведет на экран диалоговую панель Conversion Options, вызвав для этого функцию DialogBox:
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc);
Диалоговая панель имеет идентификатор IDD_DIALOG1 и определена в файле описания ресурсов приложения.
Эта функция обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения.
Выбирая строки меню Options, вы можете изменить внешний вид окна органа управления List View, выбрав один из четырех режимов отображения. Соответствующие процедуры мы описали в 22 томе “Библиотеки системного программиста”.
Эта функция обрабатывает сообщение WM_COMMAND, которое приходит от главного меню приложения.
Когда пользователь выбирает из меню File строку Find App Window, с помощью функции DialogBox на экран выводится диалоговая панель, предназначенная для ввода заголовка окна, которое нужно найти.
Если пользователь ввел заголовок и нажал в диалоговой панели кнопку OK, функция WndProc_OnCommand выполняет поиск окна, вызывая соответствующую функцию из DLL-библиотеки DLLDemo.DLL, исходные тексты которой мы только что рассмотрели.
В листинге мы подготовили два способа подключения DLL-библиотеки - прямой с использованием библиотеки экспорта и динамический.
Первый способ достаточно прост, однако предполагает, что в проект приложения DLLCALL будет включен файл библиотеки экспорта DLLDemo.LIB. Этот файл создается автоматически системой Microsoft Visual C++ при сборке проекта DLL-библиотеки.
Фрагмент кода, использующий прямое подключение, закрыт в листинге 3.4 символами комментария:
if(FindApplicationWindow(szWindowTitle) != NULL)
MessageBox(NULL, "Application window was found",
szAppTitle, MB_OK | MB_ICONINFORMATION);
else
MessageBox(NULL, "Application window was not found",
szAppTitle, MB_OK | MB_ICONINFORMATION);
В этом фрагменте мы выполняем простой вызов функции FindApplicationWindow, определенной в DLL-библиотеке DLLDemo.DLL. Прототип функции FindApplicationWindow мы поместили в файл dllcall.h.
Второй фрагмент загружает DLL-библиотеку при помощи функции LoadLibrary, а в случае успеха затем получает указатель на функцию FindApplicationWindow. Для получения указателя здесь применяется функция GetProcAddress:
hDLL = LoadLibrary("DLLDEMO.DLL");
if(hDLL != NULL)
{
GetAppWindow =
(MYDLLPROC)GetProcAddress(hDLL, "FindApplicationWindow");
if(GetAppWindow != NULL)
{
if(GetAppWindow(szWindowTitle) != NULL)
MessageBox(NULL, "Application window was found",
szAppTitle, MB_OK | MB_ICONINFORMATION);
Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее в функцию главного окна приложения от меню.
Для установки текущего набора национальных символов в этой функции используется описанная нами ранее функция SetThreadLocale, а также макрокоманды MAKELCID и MAKELANGID:
fRc = SetThreadLocale(MAKELCID(
MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL), SORT_DEFAULT));
Для получения значений отдельных национальных параметров мы вызываем функцию GetLocaleInfo:
GetLocaleInfo(GetThreadLocale(), LOCALE_SLANGUAGE,
szBuf1, 512);
В качестве идентификатора набора национальных параметров мы указываем идентификатор текущего набора параметров для основной задачи приложения, полученный от функции GetThreadLocale.
Имя текущей раскладки клавиатуры определяется при помощи функции GetKeyboardLayoutName:
GetKeyboardLayoutName(szKbLayoutName);
Для получения форматированной текстовой строки даты и времени мы вызываем функции GetDateFormat и GetTimeFormat:
GetDateFormat(GetThreadLocale(),
LOCALE_NOUSEROVERRIDE | DATE_LONGDATE,
NULL, NULL, szBuf1, 512);
GetTimeFormat(GetThreadLocale(),
LOCALE_NOUSEROVERRIDE, NULL, NULL, szBuf1, 512);