Главная > Методическое пособие

1

Смотреть полностью

ФЕДЕРАЛЬНОЕ АГЕНСТВО ПО ОБРАЗОВАНИЮ

Государственное образовательное учреждение высшего

профессионального образования

«МАТИ»- РОССИЙСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ

им. К.Э.ЦИОЛКОВСКОГО

Кафедра «Информационные технологии»

Методическое пособие по дисциплине

«Операционные системы»

и указания по выполнению лабораторных работ

Направление 230100 «Информатика и вычислительная техника»

Специальность 230102 «Автоматизированные системы обработки

информации и управления»

Составитель: Воинов С.Б.

Москва 2006

Оглавление

Оглавление 2

1. Общие положения 4

1.1. Классификация операционных систем 5

1.2. Характеристики операционных систем
фирмы Microsoft 8

1.3. Процесс начальной загрузки компьютера и
операционной системы 9

Контрольные вопросы 10

2. Лабораторная работа №1 11

2.1. Цель работы 11

2.2. Основные теоретические положения 11

2.2.1. Структура загрузочного сектора дискет и жёстких дисков 11

2.2.2. Структура главной загрузочной записи жёстких дисков 16

2.2.3. Типы разделов и таблица разделов 16

2.3. Задания и методические указания
к выполнению работы 21

2.3.1. Задание на лабораторную работу 21

2.3.2. Методические указания к выполнению работы 21

2.4. Контрольные вопросы 23

2.5. Варианты заданий на лабораторную работу 23

3. Лабораторная работа №2 25

3.1. Цель работы 25

3.2. Основные теоретические положения 25

3.2.1. Работа с дополнительной памятью по спецификации EMS 26

Функция 4000h 26

Функция 4100h 27

Функция 4200h 27

Функция 4300h 27

Функция 44h 28

Функция 4500h 28

Функция 4600h 29

3.2.2. Пример программы 29

3.3. Задания и методические указания
к выполнению работы 32

3.3.1. Задание на лабораторную работу 32

3.3.2. Методические указания к выполнению работы 32

3.4. Контрольные вопросы 32

3.5. Варианты заданий на лабораторную работу 34

4. Лабораторная работа №3 35

4.1. Цель работы 35

4.2. Основные теоретические положения 35

4.3. Задания и методические указания
к выполнению работы 40

4.3.1. Задание на лабораторную работу 40

4.3.2. Методические указания к выполнению работы 41

4.4. Контрольные вопросы 41

4.5. Варианты заданий на лабораторную работу 41

5. Лабораторная работа №4 44

5.1. Цель работы 44

5.2. Основные теоретические положения 44

5.2.1. Архитектура Windows 95 44

5.2.2. Основные компоненты Windows 95 46

5.2.3. Классификация API функций 49

5.3. Задания и методические указания
к выполнению работы 52

5.3.1. Задание на лабораторную работу 52

5.3.2. Методические указания к выполнению
лабораторной работы 52

5.4. Контрольные вопросы 54

5.5. Варианты заданий на лабораторную работу 54

6. Лабораторная работа №5 56

6.1. Цель работы 56

6.2. Основные теоретические положения 56

6.2.1. Краткие сведения о COM 56

6.2.2. COM объекты и интерфейсы 57

6.2.3. Интерфейс IUnknown и наследование инерфейсов 58

6.2.4. Назначение IUnknown::QueryInterface 59

6.2.5. Подсчет ссылок 61

6.2.6. Классы 62

6.2.7. Серверы СОМ объектов 62

6.2.8. СОМ и многопоточность 63

6.2.9. Создание СОМ объектов 64

6.2.10. Поиск COM серверов 65

6.2.11. Классы и экземпляры 65

6.2.12. Создание одного объекта 66

6.2.13. Создание нескольких объектов одного класса:
фабрики классов 67

6.2.14. Интерфейс IClassFactory 67

6.2.15. Использование фабрики классов 68

6.2.16. Эмуляция 68

6.2.17. Инициализация СОМ объектов 69

6.2.18. Повторное применение СОМ объектов 70

6.2.19. Включение 70

6.2.20. Агрегирование 71

6.3. Задания и методические указания
к выполнению работы 72

6.3.1. Задание на лабораторную работу 72

6.3.2. Методические указания к выполнению
лабораторной работы 73

6.4. Контрольные вопросы 80

6.5. Варианты заданий на лабораторную работу 81

6.6. Пример программы 81

Файл TXTView.h 81

Файл StdAfx.h 82

Файл TXTView.cpp 82

Файл StdAfx.cpp 84

Файл CtxMenu.cpp 85

Файл TXTView.def 86

Файл TXTView.reg 86

1. Общие положения

Операционная система (ОС) является неотъемлемой частью программного обеспечения практически любого компьютера. ОС выполняет такие важные функции, как управление ресурсами компьютера, обеспечение защиты информации, поддержка интерфейса пользователя и т. д. Помимо управления компьютером и работы с пользователем (или пользователями) ОС, как правило, обеспечивает и некоторый набор функций и объектов интерфейса прикладного программирования (API – Application Programming Interface). Это позволяет разрабатывать программы, функционирующие в среде конкретной ОС и наиболее эффективно использующие предоставляемые ОС возможности.

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

Тем не менее, для установки новой ОС на компьютер всё же требуется некоторое программное обеспечение. Как правило, новая ОС устанавливается либо из под старой ОС (уже установленной на данный компьютер), либо со специальной системной загрузочной дискеты. Во втором случае программа установки ОС запускается при загрузке с системной дискеты. Исключение не составляют и загрузочные компакт-диски, так как они по сути эмулируют загрузочную дискету.

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

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

  • оперативная память;

  • процессорное время;

  • периферийные устройства.

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

Таким образом, ещё одной важной функцией ОС является выделение ресурсов процессам и управление работой этих процессов.

1.1. Классификация операционных систем

На рис. 1.1 представлена обобщённая классификацию операционных систем.

Также ОС можно классифицировать по режиму работы, который может быть некоторым сочетанием пунктов классификации:

  • однопользовательские однозадачные;

  • однопользовательские многозадачные;

  • многопользовательские многозадачные.

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

Рассмотрим более подробно типы ОС.

Однопользовательские ОС предназначены для поддержки работы только одного пользователя в каждый конкретный момент времени. Несколько пользователей не могут одновременно разделять ресурсы вычислительной системы. Пример такой ОС – MS-DOS.

Многопользовательские ОС позволяют нескольким пользователям одновременно использовать разделяемые ресурсы вычислительной системы. В этом случае задачей такой ОС является распределение ресурсов между процессами пользователей и обеспечение синхронизации доступа к одному и тому же ресурсу. Пример такой ОС – Microsoft Windows 2000 Server.

Однозадачные ОС предполагают, что в каждый конкретный момент времени ресурсы вычислительной системы может использовать только один процесс. В качестве примера здесь также можно привести ОС MS-DOS. Однако в MS-DOS есть некоторое исключение – это резидентные программы. Подобные программы функционируют в фоновом режиме и перехватывают ресурсы компьютера при возникновении определённых событий – например, при возникновении аппаратного прерывания. Тем не менее по своей архитектуре и концепции MS-DOS является однопользовательской однозадачной ОС.

Многозадачные ОС позволяют одновременно нескольким процессам разделять ресурсы вычислительной системы. При этом такие ОС могут быть как однопользовательскими, так и многопользовательскими. Следует обратить внимание на то, каким образом обеспечивается одновременное разделение процессорного времени. Процессор позволяет выполнять одновременно только одну команду, но за счёт специальных архитектурных особенностей процессора ОС может очень быстро переключать процессор с одной задачи на другую, в результате чего у пользователя складывается впечатление, что программы работают одновременно. Пример такой ОС – Microsoft Windows 95.

Многозадачные ОС должны решать следующие проблемы, возникающие при совместной работе нескольких процессов:

  1. Взаимодействие процессов. Может потребоваться, чтобы один процесс каким-либо образом использовал результаты вычислений другого процесса. В этом случае многозадачная ОС должна обеспечивать соответствующий механизм общения процессов друг с другом. Примерами таких механизмов в Windows 9x/2000 являются: DDE, pipes, mailslots и отображение файла в памяти.

  2. Синхронизация процессов. Данная проблема возникает при попытке доступа нескольких процессов к одному и тому же ресурсу, например, к файлу. В этом случае многозадачная ОС должна решить, какому процессу распределить этот ресурс, а какому выдать отказ в выделении ресурса. Для файлов эта проблема решается назначением режима разделения при создании или открытии файла. Другими примерами механизмов синхронизации в Windows 9x/2000 являются: события (events), семафоры (semaphores), таймеры (timers) и мьютексы (mutexes).

  3. Квантование процессорного времени. Многозадачная ОС должна обеспечивать средства, позволяющие выделять задачам процессорное время исходя из приоритетов этих задач.

  4. Уровни доступа к ресурсам. Каждый процесс может иметь различные права доступа к разделяемым ресурсам. Например, в Windows NT/2000 права доступа процесса определяются правами доступа пользователя, запустившего этот процесс. Помимо этого процессоры Intel 80386 (и выше) в защищённом режиме работы определяют 4 уровня привилегий процессов.

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

Многозадачность также бывает следующих типов:

  1. Вытесняющая. В этом случае если задача не успела завершить какие-либо действия за отведённый ей квант времени процессора, управление в любом случае передаётся на следующую задачу. Такой тип многозадачности аппаратно поддерживается МП 80386 (и выше). ОС, использующие этот тип многозадачности: Unix, Windows 9x/2000.

  2. Согласующая. При этом типе многозадачности ОС ожидает возврата управления от задачи, если та не успела завершить необходимые действия в отведённый ей квант процессорного времени. ОС, использующие этот тип многозадачности: Windows 3.1, MacOS.

ОС с пакетной обработкой являются однозадачными и предполагают последовательную обработку нескольких задач. При этом список задач для обработки находится в так называемом пакетном файле. Для MS-DOS это файлы с расширением .BAT, в которых указывается последовательность задач или команд, выполняемых ОС последовательно.

ОС с разделением времени позволяют выделять каждой задаче определённую часть процессорного времени, после чего переключаться на следующую задачу. Примером такой ОС также может являться Microsoft Windows 95, поскольку она использует аппаратные средства процессора для отведения каждому процессу определённого количества процессорного времени.

К ОС реального времени предъявляется то требование, что поступающая на обработку задача должна быть выполнена за строго определённое время. ОС реального времени обычно используются в различных системах автоматизированного управления. К этим ОС предъявляются повышенные требования по надёжности. Если в процессе выполнения задачи из строя выходит какой либо компонент вычислительной системы, ОС должна оперативно переключиться на резервный компонент. Также ОС реального времени должна обеспечивать обработку очереди задач в соответствии с заданными приоритетами. Пример такой ОС – QNX.

Однопроцессорные ОС предназначены для функционирования на компьютере с одним центральным процессором. В этих системах отсутствует поддержка многопроцессорности. Пример таких ОС: MS-DOS, Windows 95.

В многопроцессорных ОС имеются встроенные средства, которые позволяют использовать преимущества нескольких центральных процессоров компьютера. К таким преимуществам можно отнести повышенную надёжность (когда один процессор может дублировать работу другого), а также параллельные вычисления (когда несколько задач могут выполняться на разных процессорах). Пример такой ОС – Microsoft Windows 2000 Data Center Server.

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

1.2. Характеристики операционных систем
фирмы Microsoft

В данном курсе изучаются только операционные системы фирмы Microsoft. Это связано с тем, что на настоящий момент эти системы являются наиболее популярными и установлены на большинстве компьютеров. Также, эти ОС предоставляют удобный пользовательский интерфейс и огромный набор функций API. В таблице 1.1 перечислены основные характеристики ОС фирмы Microsoft.

Таблица 1.1 – основные характеристики ОС фирмы Microsoft.

Название ОС

Год выпуска

Разрядность

Тип

MS-DOS 6.22

1992

16

Однопользовательская, однозадачная, с пакетной обработкой

Windows 3.1*

1993

16

Однопользовательская многозадачная, с разделением времени

Windows 3.11*

1993

16

Однопользовательская многозадачная, с разделением времени, сетевая

Windows NT 3.5

1994

16

Многопользовательская многозадачная, с разделением времени, сетевая

Windows 95

1995

32

Однопользовательская многозадачная, с разделением времени

Windows 95 OSR2

1996

32

Однопользовательская многозадачная, с разделением времени

Windows 98

1998

32

Однопользовательская многозадачная, с разделением времени

Windows 98 SE

2000

32

Однопользовательская многозадачная, с разделением времени

Windows Millennium

2000

32

Однопользовательская многозадачная, с разделением времени

Windows NT 4.0

1995

32

Многопользовательская многозадачная, с разделением времени, сетевая

Windows 2000 Professional

1999

32

Многопользовательская многозадачная, с разделением времени

Windows 2000 Server

2000

32

Многопользовательская многозадачная, с разделением времени, сетевая

Windows 2000 Advanced Server

2000

32

Многопользовательская многозадачная, с разделением времени, сетевая

Windows 2000 Data Center Server

2000

32

Многопользовательская многозадачная, с разделением времени, сетевая

Таблица 1.1 (продолжение).

Название ОС

Число поддерживаемых процессоров

Тип пользовательского интерфейса

Рекомендуемый объём памяти

Место, занимаемое на жёстком диске

MS-DOS 6.22

1

текстовый

4

8

Windows 3.1*

1

графический

8

16

Windows 3.11*

1

графический

8

20

Windows NT 3.5

1

графический

16

50

Windows 95

1

графический

32

100

Windows 95 OSR2

1

графический

32

140

Windows 98

1

графический

64

200

Windows 98 SE

1

графический

64

250

Windows Millennium

1

графический

64

250

Windows NT 4.0

1

графический

128

200

Windows 2000 Professional

2

графический

128

900

Windows 2000 Server

4

графический

256

1000

Windows 2000 Advanced Server

8

графический

256

1000

Windows 2000 Data Center Server

16

графический

512

1000

* - Windows 3.x строго говоря не является операционной системой, так как она работает только при установленной системе MS-DOS. Обычно Windows 3.x называют операционной оболочкой.

1.3. Процесс начальной загрузки компьютера и
операционной системы

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

  1. При включении питания запускается подпрограмма POST (Power On Self Test), которая определяет объём установленной оперативной памяти, её исправность, подключённые периферийные устройства и конфигурацию системы. Во время POST можно зайти подпрограмму BIOS Setup для задания конфигурации системы. В случае отсутствия оперативной памяти или видеоадаптера выдаётся звуковой сигнал, по которому можно определить причину неполадки. Если видеоадаптер и оперативная память присутствуют, дальнейшие сообщения выводятся на экран.

  2. После завершения POST BIOS определяет последовательность загрузки системы. Это включает в себя считывание из CMOS-памяти информации о том, с какого устройства выполнять загрузку (дисковод, жёсткий диск, CD-ROM и т. д.). В случае неуспешной загрузки с данного устройства BIOS переходит к загрузке со следующего по списку загрузки устройства (если эта опция поддерживается). В противном случае выдаётся сообщение о невозможности загрузки.

  3. В случае загрузки с дисковода BIOS считывается в память по адресу 0x7C00 (007C:0000) сектор 1 дорожки 0 головки 0 дискеты и вызывает команду дальнего перехода на адрес 007C:0000. После этого управление передаётся программе загрузочного сектора. Данная программа выполняет загрузку в память начального кода ОС, после чего передаёт ему управление. Поскольку в загрузочный сектор объёмом 512 байт помимо программы начальной загрузки включены и некоторые структуры данных, под код программы отводится только 426 байт и поэтому начальный код ОС должен размещаться в последовательных секторах для облегчения его поиска и сокращения объёма команд.

  4. В случае загрузки с жёсткого диска BIOS считывает главный загрузочный сектор (сектор 1 цилиндра 0 головки 0 для CHS или абсолютный сектор 0 для LBA) в память по адресу 007C:0000 и передаёт ему управление. Главный загрузочный сектор помимо программы загрузки содержит таблицу разделов, которая определяет характеристики разделов на жёстком диске. Размер таблицы разделов фиксирован и составляет 4 записи. Таким образом, на жёстком диске может быть не более 4 разделов (не путать с логическими дисками). Может быть до 3 основных разделов (те разделы, с которых выполняется загрузка ОС) и один дополнительный раздел (который включает в себя логические диски). Программа главного загрузочного сектора определяет, какой из основных разделов является активным и считывает в память его загрузочный сектор, а затем передаёт на него управление. Далее загрузка осуществляется, как в шаге 3.

  5. В случае невозможности загрузки ОС программа загрузочного сектора выдаёт соответствующее сообщение пользователю и далее, как правило, по нажатию клавиши выполняется перезагрузка.

Контрольные вопросы

  1. Дайте определение операционной системе.

  2. Назовите основные функции ОС.

  3. По каким признакам можно классифицировать ОС?

  4. Какие основные проблемы возникают в многозадачных ОС?

  5. Какие типы многозадачности вы знаете и чем они отличаются?

  6. Поясните процесс начальной загрузки компьютера и ОС.

2. Лабораторная работа №1

2.1. Цель работы

Изучить назначение и структуру загрузочного сектора дискет и жестких дисков. Получить практические навыки написания кода для загрузочного сектора.

2.2. Основные теоретические положения

Основным назначением загрузочного сектора дискет и жёстких дисков является загрузка начального кода ОС с диска в память и передача ему управления. Помимо кода, считывающего в память начальный код ОС, загрузочный сектор также содержит некоторую важную информацию о диске – тип ОС, тип файловой системы, количество секторов и т. д. Далее будет рассмотрена структура загрузочного сектора, используемая совместно с файловыми системами FAT. Файловая система NTFS использует другую структуру загрузочного сектора.

2.2.1. Структура загрузочного сектора дискет и жёстких дисков

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

Таблица 2.2.1 – структура загрузочного сектора дискет и жёстких дисков.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Пример
значения
в поле

Назначение поля

0x0000

3

EB 3C 90

Команда перехода (JMP)

0x0003

8

MSDOS5.0

Тип ОС

0x000B

25

Блок параметров BIOS

0x0024

26

Расширенный блок параметров BIOS

0x003E

448

Загрузочный код

0x01FE

2

0x55AA

Маркер конца сектора

Команда перехода практически всегда одинакова и передаёт управление на смещение 0x003E, где начинается загрузочный код. Выполнение команды перехода необходимо для того, чтобы «обойти» структуры данных, размещаемые перед загрузочным кодом.

Тип ОС – это текстовая строка размером 8 байт, идентифицирующая ту ОС, которая установлена на дискете или на разделе жёсткого диска. Эта строка для MS-DOS версии 5.0 и выше всегда равна ‘MSDOS5.0’ для ОС Windows 95 эта строка равна ‘MSWIN4.0’, а для Windows 98 – ‘MSWIN4.1’.

Блоки параметров BIOS содержат важную информацию о структуре диска – количество байт в секторе, количество секторов в кластере, количество копий FAT и т. д. Структура блока параметров BIOS приведена в таблице 2.2.2, а структура расширенного блока параметров BIOS – в таблице 2.2.3.

Загрузочный код выполняет операции по считыванию начального кода ОС в память и передачу ему управления. Для считывания данных с диска в загрузочном коде используются функции прерывания BIOS Int 13h (работа с диском на низком уровне). Поскольку для загрузочного кода отводится только 448 байт, начальный код ОС должен располагаться в последовательных секторах для сокращения числа команд, используемых для получения доступа к этому коду.

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

Таблица 2.2.2 – структура блока параметров BIOS.

Смещение поля
(в байтах)*

Размер поля
(в байтах)

Назначение поля

0x0B

2

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

0x0D

1

Число секторов в кластере. Первоначальный размер кластера зависит от размера тома и используемой файловой системы.

0x0E

2

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

0x10

1

Количество таблиц распределения файлов (File Allocation Tables – FATs), т. е. количество копий FAT на данном томе. Обычно значение этого поля равно 2.

0x11

2

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

0x13

2

Количество секторов в томе. Данное поле используется, если число секторов в томе не более 65535. Если же число секторов в томе больше 65535, то значение данного поля равно 0, и используется поле со смещением 0x20.

0x15

1

Тип носителя. Значение этого поля характеризует используемый тип носителя. Для жёстких дисков значение данного поля равно 0xF8.

0x16

2

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

0x18

2

Количество секторов на дорожке. Значение в это поле заносится при форматировании диска.

0x1A

2

Количество головок (сторон) диска. Значение в это поле заносится при форматировании диска.

0x1C

4

Количество скрытых секторов. Значение этого поля используется только для жёстких дисков. Значение поля характеризует смещение (в секторах) от начала диска до раздела, в котором размещается данный загрузочный сектор.

0x20

4

Данное поле используется, если количество секторов на томе превышает 65535. Если же количество секторов меньше или равно 65535, используется поле со смещением 0x13.

* - здесь и далее смещения приводятся относительно начала загрузочного сектора.

Таблица 2.2.3 – структура расширенного блока параметров BIOS.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Назначение поля

0x24

1

Физический номер диска. Этот параметр относится к физическому номеру диска BIOS. Гибкие диски нумеруются, начиная с 0x00 (диск A). Жёсткие диски нумеруются, начиная с 0x80 (диск C). Значение этого поля используется при вызове загрузочным кодом функций прерывания BIOS Int 13h.

0x25

1

Текущий номер головки. Это поле не используется для файловых систем FAT. Windows NT использует данной поле для хранения двух флагов:

  1. 0-й бит используется для определения необходимости запуска программы Chkdsk для данного диска при запуске Windows;

  2. установленный 1-й бит говорит о необходимости запуска теста поверхности данного диска; данный бит имеет значение только если бит 0 установлен в 1.

0x26

1

Сигнатура. Используется для обозначения корректного раздела или дискеты. Значение этого поля должно быть 0x28 или 0x29.

0x27

4

Серийный номер тома. Уникальное число, которое записывается в данное поле при форматировании диска.

0x2B

11

Метка тома. Раньше это поле использовалось для хранения метки тома, но в настоящее время для этой цели в корневом каталоге используется специальный файл с атрибутом ‘метка тома’.

0x36

8

Идентификатор файловой системы. Служит для определения файловой системы, установленной на данном томе. Значение этого поля может быть равно ‘FAT12’, ‘FAT16’ или ‘FAT32’.

В таблице 2.2.4 представлены коды значения поля типа носителя (блок параметров BIOS) для различных типов дисководов.

Таблица 2.2.4 – коды значения поля типа носителя.

Значение

Ёмкость

Размер и тип носителя

0xF0

2,88 Мб

3,5”, 2 стороны, 36 секторов на дорожку

0xF0

1,44 Мб

3,5”, 2 стороны, 18 секторов на дорожку

0xF9

720 Кб

3,5”, 2 стороны, 9 секторов на дорожку

0xF9

1,2 Мб

5,25”, 2 стороны, 15 секторов на дорожку

0xFD

360 Кб

5,25”, 2 стороны, 9 секторов на дорожку

0xFF

320 Кб

5,25”, 2 стороны, 8 секторов на дорожку

0xFC

180 Кб

5,25”, 1 сторона, 9 секторов на дорожку

0xFE

160 Кб

5,25”, 1 сторона, 8 секторов на дорожку

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

Таблица 2.2.5 – структура загрузочного сектора разделов с файловой системой NTFS.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Пример
значения
в поле

Назначение поля

0x0000

3

EB 5B 00

Команда перехода (JMP)

0x0003

8

NTFS

Тип файловой системы

0x000B

25

Блок параметров BIOS

0x0024

48

Расширенный блок параметров BIOS

0x003E

426

Загрузочный код

0x01FE

2

0x55AA

Маркер конца сектора

Из сравнения таблиц 1 и 5 видно, что размер загрузочного кода в загрузочном секторе NTFS меньше, чем у FAT. Однако, размер загрузочного кода Windows NT/2000 больше 426 байт и при форматировании диска под загрузочный код отводятся первые 16 секторов. В этом случае количество зарезервированных секторов (поле по смещению 0x0E в блоке параметров BIOS) будет равно 16.

Дополнительные поля расширенного блока параметров BIOS необходимы для локализации одной из основных структур NTFS – Master File Table (MFT). MFT в отличие от FAT не располагается в фиксированных секторах, что обеспечивает возможность перемещения MFT в другое место диска в случае возникновения повреждённых секторов в области MFT.

Структура блока параметров BIOS для томов NTFS представлена в таблице 2.2.6, а структура расширенного блока параметров – в таблице 2.2.7.

Таблица 2.2.6 – структура блока параметров BIOS для томов NTFS.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Назначение поля

0x0B

2

Количество байт в секторе.

0x0D

1

Число секторов в кластере.

0x0E

2

Количество зарезервированных секторов.

0x10

1

Значение этого поля всегда равно 0.

0x11

2

Значение этого поля всегда равно 0.

0x13

2

Не используется NTFS.

0x15

1

Тип носителя.

0x16

2

Значение этого поля всегда равно 0.

0x18

2

Количество секторов на дорожке.

0x1A

2

Количество головок (сторон) диска.

0x1C

4

Количество скрытых секторов.

0x20

4

Не используется NTFS.

Таблица 2.2.7 – структура расширенного блока параметров BIOS для томов NTFS.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Назначение поля

0x24

4

Не используется NTFS.

0x28

8

Общее количество секторов в разделе NTFS.

0x30

8

Логический номер кластера для файла MFT$.

0x38

8

Логический номер кластера для файла MFT$Mirr.

0x40

4

Количество кластеров в сегменте записи файла.

0x44

4

Количество кластеров в индексном блоке.

0x48

8

Серийный номер тома.

0x50

4

Контрольная сумма.

2.2.2. Структура главной загрузочной записи жёстких дисков

Жёсткие диски могут содержать несколько разделов, на каждом из которых может быть установлена ОС. В этом случае каждый раздел имеет свой собственный загрузочный сектор. Информация о разделах хранится в так называемой главной загрузочной записи (Master Boot Record – MBR) жёсткого диска. Помимо информации о разделах в MBR также хранится загрузочный код, который определяет, какой из разделов является активным, считывает его загрузочный сектор в память и передаёт ему управление. Главная загрузочная запись имеет размер 512 байт и занимает сектор 1 дорожки 0 стороны 0 жёсткого диска. Загрузочные сектора разделов размещаются в самых первых секторах соответствующих разделов.

MBR является основной структурой данных жёсткого диска. Существуют вирусы, которые модифицируют MBR, делая невозможным загрузку ОС. Рекомендуется устанавливать в BIOS опцию проверки MBR на вирусы. Эта опция доступна практически во всех современных BIOS.

Структура главной загрузочной записи жёсткого диска представлена в таблице 2.2.8.

Таблица 2.2.8 – структура главной загрузочной записи жёсткого диска.

Смещение поля
(в байтах)

Размер поля
(в байтах)

Назначение поля

0x0000

440

Загрузочный код. Данный код запускается BIOS при загрузке с жёсткого диска. Если на жёстком диске присутствуют разделы с установленной ОС, то загрузочный код определяет, какой из этих разделов является активным, считывает соответствующий загрузочный сектор и передаёт ему управление.

0x01B8

6

Сигнатура диска. Это уникальное число, которое используется Windows NT для хранения в реестре информации о диске (ветвь HKEY_LOCAL_MACHINE\SYSTEM\DISK).

0x1BE

64

Таблица разделов. Содержит всю необходимую информацию о разделах жёсткого диска (начало, конец, размер, тип и т. д.)

0x01FE

2

Маркер конца сектора

2.2.3. Типы разделов и таблица разделов

Всего жёсткий диск может содержать до 4 разделов. Поэтому таблица разделов хранит в себе 4 записи – по одной для каждого раздела. Разделы бывают основными и дополнительными. Дополнительный раздел может быть только один. Таким образом, на жёстком диске можно разместить максимум либо 3 основных и один дополнительный раздел, либо 4 основных раздела.

Каждый раздел может иметь свою индивидуальную файловую систему. Например, можно выделить раздел с файловой системой NTFS для Windows NT и раздел с файловой системой FAT32 для Windows 98.

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

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

Дополнительный раздел предназначен для организации логических дисков. С этого раздела не может осуществляться загрузка ОС. Сам по себе дополнительный раздел не добавляет логических дисков – они должны быть созданы в нём. Например, если жёсткий диск не является системным и содержит только один дополнительный раздел, то в этом дополнительном разделе можно разместить до 23 дисков (D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z). Здесь предполагается, что буква C зарезервирована для системного диска.

Жёсткий диск не обязательно полностью заполнять разделами. Какая-то часть жёсткого диска может оставаться не разбитой на разделы. Это справедливо и для дополнительного раздела, часть которого также может оставаться не разбитой.

Структура таблицы разделов представлена в таблице 2.2.9.

Таблица 2.2.9 – структура таблицы разделов.

Смещение поля в байтах

Название поля

0x01BE

Раздел 1

0x01CE

Раздел 2

0x01DE

Раздел 3

0x01EE

Раздел 4

Общий размер таблицы разделов составляет 64 байта. Каждая запись в таблице занимает 16 байт и определяет характеристики одного из 4-х разделов жёсткого диска. Структура записей таблицы разделов представлена в таблице 2.2.10.

Таблица 2.2.10 – структура записей таблицы разделов.

Смещение поля
(в байтах)*

Размер поля
(в байтах)

Назначение поля

0x00

1

Индикатор загрузки. Определяет, является ли данный раздел активным (значение 0x80) или неактивным (значение 0x00).

0x01

1

Начальная головка (сторона). Принимает значения от 0 до 255.

0x02

1

Начальный сектор (биты 0-5). Это поле также содержит старшие 2 бита номера цилиндра (биты 6-7). Номер сектора может быть в пределах от 1 до 63.

0x03

1

Начальный цилиндр. В данном поле содержатся младшие 8 бит номера цилиндра. Используя 2 бита из поля начального сектора, номер цилиндра занимает 10 бит и таки образом может быть в пределах от 0 до 1023.

0x04

1

Системный идентификатор.

0x05

1

Конечная головка.

0x06

1

Конечный сектор (биты 0-5) и старшие 2 бита номера конечного цилиндра (биты 6-7).

0x07

1

Младшие 8 бит номера конечного цилиндра.

0x08

4

Относительный номер сектора. Показывает количество секторов от начала диска до начала раздела.

0x12

4

Общее количество секторов в разделе.

* - смещения приведены относительно начала таблицы разделов.

Поле системного идентификатора определяет тип используемой файловой системы. Для Windows NT/2000 это поле также определяет то, какой драйвер файловой системы использовать при загрузке. Значение этого поля также может определять дополнительный раздел.

В таблице 2.2.11 представлены возможные значения поля системного идентификатора.

Таблица 2.2.11 – значения поля системного идентификатора.

Значение поля

Описание

0x01

Раздел или логический диск FAT12. Общее количество секторов в разделе не превышает 32679.

0x04

Раздел или логический диск FAT16. Общее количество секторов в разделе находится в пределах от 32680 до 65535.

0x05

Расширенный раздел.

0x06

Раздел или логический диск BIGDOS FAT.

0x07

Раздел или логический диск NTFS.

MS-DOS распознаёт только разделы с системным идентификатором 1, 4, 5 или 6.

Windows 9x также определяет и другие значения для поля системного идентификатора (см. табл. 2.2.12). Разделы с этими значениями не распознаются Windows NT, но распознаются Windows 2000/XP.

Таблица 2.2.12 – значения системного идентификатора, определяемые Windows 9x.

Значение поля

Описание

0x0B

Основной раздел FAT32 с использованием расширений прерывания Int 13h.

0x0C

Дополнительный раздел FAT32 с использованием расширений прерывания Int 13h.

0x0E

Основной раздел FAT16 с использованием расширений прерывания Int 13h.

0x0F

Дополнительный раздел FAT16 с использованием расширений прерывания Int 13h.

Поля начальной стороны, сектора и цилиндра используются MBR для считывания загрузочного сектора активного раздела в память.

Максимальный объём жёсткого диска, разделы которого могут быть описаны в таблице разделов зависит от максимального количества секторов, сторон и цилиндров. Поскольку современные жёсткие диски форматируются с использованием 512 байт на сектор, максимальный объём жёсткого диска для таблицы разделов можно вычислить по формуле:

MaxCapacity = sectors * cylinders * heads * 512 или

MaxCapacity = 63 * 1024 * 256 * 512 = 8455716864 байта (7,875 Гб).

Поскольку максимальный размер кластера для файловых систем FAT составляет 65536 байт, максимальный объём тома FAT составляет 4 Гб. Максимальный размер тома NTFS гораздо больше и составляет более 32 Гб.

Для жёстких дисков объёмом более 7,875 Гб доступ к разделом с использованием начальных сектора, стороны и цилиндра невозможен. Windows NT/2000 в этом случае используют относительный номер сектора, который показывает смещение (в секторах) раздела от начала жёсткого диска. Для таких жёстких дисков в BIOS Setup должен быть включён режим LBA, который вместо трёхмерной геометрии диска (цилиндр-головка-сектор) использует линейную адресацию по логическим номерам секторов.

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

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

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

Третья и четвёртая записи заполнены нулями и не ссылаются ни на один логический диск.

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

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

Для записи 1 относительный номер сектора – это смещение (в секторах) от начала логического диска до загрузочного сектора раздела. Общее количество секторов показывает размер текущего раздела за вычетом загрузочного сектора.

Для записи 2 относительный номер сектора – это смещение от начала дополнительного раздела до первого сектора следующего логического диска, содержащего расширенную таблицу разделов. Общее количество секторов показывает объём следующего логического диска.

2.3. Задания и методические указания
к выполнению работы

2.3.1. Задание на лабораторную работу

  1. Написать программу на языке C, позволяющую записывать загрузочный сектор дискет данными, взятыми из файла. Программа должна перезаписывать только загрузочный код. Размер файла с загрузочным кодом фиксированный – 448 байт.

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

  3. Выполнить перезагрузку компьютера с дискеты и проверить правильность работы программы.

2.3.2. Методические указания к выполнению работы

Для чтения физических секторов предназначена функция 02h прерывания BIOS INT 13h. Для записи физических секторов служит функция 03h прерывания BIOS INT 13h. Для работы с секторами также могут быть использованы функции 25h и 26h прерывания DOS INT 21h. Описание и примеры использования функций можно посмотреть в курсе «Программно-аппаратная организация ЭВМ».

Чтобы записать в загрузочный сектор дискеты только загрузочный код, необходимо:

  1. считать загрузочный сектор с дискеты;

  2. записать в считанный сектор (в памяти) загрузочный код;

  3. записать модифицированный загрузочный сектор на дискету.

Для вызова прерываний в библиотеке DOS.H языка C имеется функция:

void intr(int __intno, struct REGPACK _FAR *__preg);

Данная функция позволяет вызвать программное прерывание с номером __intno, предварительно записав в регистры процессора значения, взятые из структуры REGPACK. Функция принимает указатель на данную структуру (параметр __preg). Структура REGPACK имеет следующий вид:

struct REGPACK

{

unsigned r_ax, r_bx, r_cx, r_dx;

unsigned r_bp, r_si, r_di, r_ds, r_es, r_flags;

};

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

Ниже представлен пример программы, которая выводит на экран текстовое сообщение с использованием функции DOS 09h и функции языка C intr.

#include <dos.h>

void main()

{

REGPACK regs;

char Message[] = "Hello, World!\n$";

regs.r_ax = 0x0900;

regs.r_dx = (unsigned int)&Message;

regs.r_ds = _DS;

intr(0x21, &regs);

}

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

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

Загрузочный код начинается по смещению 0x003E в загрузочном секторе. Поэтому при написании программы нужно вместо директивы ORG 100h использовать директиву ORG 03Eh. К сожалению, компоновщик TLINK фирмы Borland не позволяет генерировать COM-файлы с точкой входа по смещению, отличному от 100h. В этом случае можно воспользоваться другими трансляторами и компоновщиками ассемблера для процессора Intel 8086. Поскольку загрузочный сектор считывается в память по адресу 07C00h, ссылки к переменным в программе должны учитывать это смещение. Можно было бы использовать директиву ORG 7C00h, одныко не все компоновщики ассемблера корректно обрабатывают аргументы директивы ORG большие 100h.

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

.186

code segment

org 03Eh

assume cs:code, ds:code

Start:

cli

xor ax, ax ; настройка стекового

mov ss, ax ; сегмента на начало памяти

mov sp, 07C00h ; установка вершины стека

mov ds, ax ; настройка сегмента данных на начало памяти

mov si, offset msg ; в SI - смещение текстового сообщения

add si, 07C00h

call OutStr ; вызов функции вывода строки

xor ax, ax

int 16h ; ожидание нажатия клавиши

int 19h ; перезагрузка компьютера

msg db 'Press any key to reboot, please!',0Dh,0Ah,0

; функция вывода текстовой ASCIIZ-строки

; SI - адрес строки

OutStr proc

cld

pusha ; сохранение значений регистров

next: lodsb ; загрузка очередного байта сообщения

or al, al ; если он равен нулю,

je short exit ; переход на возврат из процедуры

mov ah, 0Eh ; в AH - номер функции 0Eh

mov bx, 07h ; в BX - атрибут выводимого символа

; INT 10h, функция 0Eh - вывод символа в режиме телетайпа

int 10h

jmp next ; цикл по строке сообщения

exit: popa ; восстановление значений регистров

ret

OutStr endp

code ends

end Start

Для отладки программы можно сначала использовать директиву ORG 100h и отключить настройку стекового сегмента и сегмента данных. В этом случае программа отлаживается как обычно, при помощи отладчика Turbo Debugger. Не стоит, однако, вызывать в отладчике команду INT 19h, которая приведет к перезагрузке компьютера.

2.4. Контрольные вопросы

  1. Для чего служит загрузочный сектор?

  2. Какие данные хранятся в блоках параметров BIOS?

  3. Для чего служит главная загрузочная запись?

  4. Какая информация содержится в таблице разделов?

  5. Поясните, каким образом организуются логические диски в расширенном разделе.

2.5. Варианты заданий на лабораторную работу

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

  2. Написать программу, выводящую на экран следующие параметры блока параметров BIOS: количество секторов на дорожке, количество сторон (головок) и количество байт в секторе.

  3. Написать программу, выполняющую сложение двух целых чисел. Числа вводятся с клавиатуры.

  4. Написать программу, выполняющую поиск подстроки в строке. Строка и данные для поиска вводятся с клавиатуры.

  5. Написать программу, выводящую на экран содержимое загрузочного сектора в виде шестнадцатеричного дампа памяти.

  6. Написать программу, устанавливающую новое время. Время вводится с клавиатуры в формате чч:мм:сс. Предусмотреть проверку корректности входных данных.

  7. Написать программу, выводящую на экран имена файлов корневого каталога.

  8. Написать программу, рисующую на экране прямоугольник 100x100.

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

  10. Написать программу, определяющую, является ли введенная строка палиндромом. Ввод строки осуществляется с клавиатуры.

  11. Написать программу, генерирующую звуковые сигналы различной тональности при нажатии различных клавиш.

  12. Написать программу, вычисляющую и выводящую на экран первые 10 простых чисел.

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

  14. Написать программу, выводящую на экран двоичное представление введенного десятичного числа. Число должно вводиться с клавиатуры.

  15. Написать программу, выполняющую обработку команд, вводимых оператором. Команда ‘time’ – вывод текущего времени. Команда ‘reboot’ – перезагрузка компьютера. Команды вводятся с клавиатуры.

3. Лабораторная работа №2

3.1. Цель работы

Получить практические навыки работы с дополнительной памятью по спецификации EMS.

3.2. Основные теоретические положения

Прежде чем перейти к изучению способов работы с расширенной памятью, рассмотрим основные термины, относящиеся к типам памяти IBM-PC.

  1. Стандартная (основная) память – Conventional Memory. Это первые 640 Кб адресного пространства, доступная MS-DOS и программам реального режима. В некоторых BIOS присутствует опция изменения размера основной памяти до 512 Кб. Основная память распределяется следующим образом:

  • 00000h – 003FFh – таблица векторов прерываний;

  • 00400h – 004FFh – BIOS Data Area – область данных BIOS, в которой хранятся важные сведения о системе;

  • 00500h – 00xxxh – DOS Area – область DOS;

  • 00xxxh – 9FFFFh – область данных пользователя.

  1. Верхняя память (UMA – Upper Memory Area). Верхняя память имеет области различного назначения, которые могут быть заполнены буферной памятью различных адаптеров (например, видео- и сетевых), постоянной памятью (ROM BIOS) или оставаться незаполненными. С появлением механизма страничной переадресации (у процессоров 80386 и выше) свободные области верхней памяти используются для отображения в них доступной расширенной памяти. Этот механизм обеспечивается драйвером EMM386.EXE. Стандартное распределение верхней памяти выглядит следующим образом:

  • A0000h – BFFFFh – 128 Кб видеопамяти. Обычно эта память используется не полностью.

  • C0000h – DFFFFh – 128 Кб, постоянной и оперативной памяти адаптеров, использующих расширения ROM BIOS (например сетевые карты с возможностью загрузки по сети) и специальное ОЗУ, разделяемое с системной шиной.

  • E0000 – EFFFFh – 64 Кб свободной области, иногда занимаемой системной BIOS.

  • F0000 – FFFFFh – 64 Кб системной BIOS.

  • FD000h – FDFFFh – ESCD (Extended System Configuration Data) - область энергонезависимой памяти (NVRAM), используемая для конфигурирования устройств Plug and Play. Эта область имеется только при наличии PnP BIOS, ее положение и размер жестко не заданы.

  1. Память выше 100000h – Extended Memory – дополнительная память, непосредственно доступная только в защищенном режиме работы процессора (80286 и выше). В ней выделяется область 100000h – 10FFEFh (высокая память, HMA – High Memory Area) – единственная область памяти, доступная процессорам 80286 и выше в реальном режиме при открытой адресной линии A20. Эту область драйвер HIMEM.SYS делает доступной для размещения ядра DOS с целью экономии стандартной памяти.

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

  1. Отображаемая память – EMS (Expanded Memory Specification) – программная спецификация использования дополнительной памяти DOS-программами реального режима через 4 страницы по 16 Кб. Эти страницы, расположенные в области UMA (обычно с адреса D0000h), могут отображать любую область дополнительной памяти. В компьютерах на процессорах 80386 и выше спецификация EMS реализуется программно и память, доступная EMS, может выделяться динамически из числа дополнительной памяти. Обращение прикладных программ к памяти EMS осуществляется через диспетчер памяти (EMM – Expanded Memory Manager), в MS-DOS эти функции выполняет драйвер EMM386.EXE.

  2. Расширенная память XMS (extended Memory Specification) – программная спецификация использования дополнительной памяти DOS-программами через переключение в защищенный режим и обратно. Данная спецификация в MS-DOS поддерживается драйвером HIMEM.SYS, поверх которого может быть загружен драйвер EMM386.EXE, использующий память XMS для эмуляции EMS-памяти.

3.2.1. Работа с дополнительной памятью по спецификации EMS

Для работы с дополнительной памятью по спецификации EMS будут использоваться функции диспетчера отображаемой памяти EMM. EMM использует программное прерывание INT 67h. Для вызова функции драйвера необходимо ее номер занести в регистр AX и вызвать прерывание INT 67h. Результат выполнения функции возвращается в регистре AH. Ненулевое значение указывает на возникновение ошибки в ходе выполнения функции.

Далее будут рассмотрены стандартные функции EMM.

Функция 4000h

Назначение: получение состояния EMM. Функция возвращает в регистре AH текущее состояние EMM. При наличии и нормальной работе драйвера отображаемой памяти значение, возвращаемое в регистре AH – 0.

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

MOV AX, 4000H ; функция получения состояния EMM

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 4100h

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

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

MOV AX, 4100H ; функция получения сегмента окна

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 4200h

Назначение: получение общего количества страниц EMS и количества свободных страниц. Функция возвращает в регистре DX общее количество страниц памяти EMS (размер страницы – 16 Кб), а в регистре BX – количество свободных страниц.

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

MOV AX, 4200H ; функция получения количества страниц

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 4300h

Назначение: выделение EMS памяти и получение дескриптора выделенного блока памяти. Дескриптор является 16-битным числом, идентифицирующим блок выделенной EMS памяти. Дескриптор используется для получения доступа к EMS памяти после ее выделения. Для вызова функции необходимо указать ее номер в регистре AX и количество выделяемых страниц EMS-памяти в регистре BX. Функция возвращает дескриптор выделенного блока памяти в регистре DX.

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

MOV AX, 4300H ; функция выделения страниц EMS

MOV BX, 0100H ; 256 страниц (всего 4 Мб)

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 44h

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

  1. В регистре AL – номер физической страницы окна отображения (от 0 до 3).

  2. В регистре BX – номер логической страницы из блока EMS-памяти, выделенной при помощи функции 4300h.

  3. В регистре DX – дескриптор выделенного блока памяти, возвращенный функцией 4300h.

Функция возвращает в регистре AH код ошибки или 0 при отсутствии ошибки.

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

MOV AH, 44H ; функция отображения страницы

MOV AL, 1 ; физическая страница 1

MOV BX, 007Fh ; логическая страница 127

MOV DX, EMSHandle ; дескриптор блока памяти

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 4500h

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

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

MOV AX, 4500H ; функция освобождения памяти

MOV DX, EMSHandle ; дескриптор блока памяти

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

Функция 4600h

Назначение: получение номера версии EMM. Данная функция возвращает в регистре AL номер версии EMM в двоично-десятичном формате. Младшие 4 бита регистра AL определяют номер подверсии, а старшие 4 бита – номер основной версии. Например, для EMM версии 3.2 значение, возвращаемое в регистре AL, бедет 0x32.

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

MOV AX, 4600H ; функция получения версии EMM

INT 67H ; вызов прерывания EMM

OR AH, AH ; проверка на ненулевое значение

JNZ Error ; переход по ошибке

Error:

3.2.2. Пример программы

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

#include <fstream.h>

#include <dos.h>

#include <dir.h>

// размер страницы EMS

#define FRAME_SIZE 0x4000

// указатель на окно EMS

void far *PageFrame;

void main(int argc, char **argv)

{

REGPACK regs;

ffblk ffblk;

long FileSize;

long Remainder;

unsigned int NumPages;

unsigned int Handle;

unsigned int k;

ifstream in;

ofstream out;

char *buf;

if (argc < 3)

{

cout << "Недостаточное количество параметров!\n";

return;

}

// установлен ли EMM?

regs.r_ax = 0x4000;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00)

{

cout << "Ошибка: драйвер EMM не установлен!\n";

return;

}

// открытие файла для чтения

in.open(argv[1], ios::binary);

if (!in)

{

cout << "Невозможно открыть файл!\n";

return;

}

// открытие файла для записи

out.open(argv[2], ios::binary);

if (!out)

{

cout << "Невозможно создать файл!\n";

in.close();

return;

}

// вычисление размера файла через поиск его по шаблону

if (findfirst(argv[1], &ffblk, FA_NORMAL | FA_ARCH) == -1)

{

cout << "Файл не найден!\n";

return;

}

FileSize = ffblk.ff_fsize;

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

buf = new char[FRAME_SIZE];

// try-блок для обработки исключений при работе с функциями EMM

try

{

// получение версии EMM

regs.r_ax = 0x4600;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4600;

cout << "Версия EMM: " << char((regs.r_ax >> 4) + '0') <<

'.' << char((regs.r_ax & 0x000F) + '0') << '\n';

// получение сегмента окна EMS

regs.r_ax = 0x4100;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4100;

cout << "Сегмента окна EMS: ";

cout << hex << regs.r_bx << "h\n";

// инициализация указателя на окно EMS

PageFrame = MK_FP(regs.r_bx, 0x0000);

// получение общего количества страниц EMS и числа свободных страниц

regs.r_ax = 0x4200;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4200;

cout << dec;

cout << "Общее число страниц EMS: " << regs.r_dx;

cout << " (" << ((long)regs.r_dx << 4) << " Кб)\n";

cout << "Число свободных страниц EMS: " << regs.r_bx;

cout << " (" << ((long)regs.r_bx << 4) << " Кб)\n";

// вычисление необходимого числа страниц для хранения файла в памяти

NumPages = FileSize / FRAME_SIZE;

Remainder = FileSize % FRAME_SIZE;

if (Remainder) NumPages++;

// попытка выделить EMS-память

regs.r_ax = 0x4300;

regs.r_bx = NumPages;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00)

// код 0x87 или 0x88 указывает на недостаточное количество EMS

if ((regs.r_ax >> 8) == 0x87 ||

(regs.r_ax >> 8) == 0x88)

{

cout << "Недостаточно свободной EMS памяти!\n";

return;

}

else throw 0x4300;

Handle = regs.r_dx;

// цикл чтения файла в EMS-память блоками по 16 Кб

cout << "Чтение файла в память...";

for (k = 0; k < NumPages; k++)

{

// отображение очередной страницы EMS в окно

regs.r_ax = 0x4400;

regs.r_bx = k;

regs.r_dx = Handle;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4400;

// чтение из файла в буфер

in.read(buf, FRAME_SIZE);

// запись из буфера в страницу EMS

memcpy(PageFrame, buf, FRAME_SIZE);

cout << '.';

}

cout << '\n';

// цикл записи файла из EMS-памяти блоками по 16 Кб

cout << "Запись файла из памяти...";

for (k = 0; k < NumPages; k++)

{

// отображение очередной страницы EMS в окно

regs.r_ax = 0x4400;

regs.r_bx = k;

regs.r_dx = Handle;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4400;

// запись из страницы EMS в буфер

memcpy(buf, PageFrame, FRAME_SIZE);

// запись из буфера в файл с учетом остатка (когда размер файла

// не кратен 16 Кб

if (Remainder && k == NumPages - 1)

out.write(buf, Remainder);

else

out.write(buf, FRAME_SIZE);

cout << '.';

}

cout << '\n';

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

regs.r_ax = 0x4500;

regs.r_dx = Handle;

intr(0x67, &regs);

if (regs.r_ax & 0xFF00) throw 0x4500;

}

// обработка исключительной ситуации

catch (int fNo)

{

cout << "\nОшибка вызова функции EMM " << hex << fNo << "h\n";

cout << "\tКод ошибки: " << (regs.r_ax >> 8) << "h\n";

}

// освобождение буфера и закрытие файлов

delete buf;

out.close();

in.close();

}

Примечание. Программа не работает под операционными системами Windows 2000 и Windows XP (выдается сообщение об отсутствии EMM).

3.3. Задания и методические указания
к выполнению работы

3.3.1. Задание на лабораторную работу

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

  1. номер версии EMM;

  2. общее количество EMS-памяти;

  3. количество свободной EMS-памяти;

  4. сегментный адрес страничного кадра EMS.

Предусмотреть в программе выдачу сообщений при возникновении ошибок вызова функций EMM.

3.3.2. Методические указания к выполнению работы

Для вызова функций EMM используется функция intr библиотеки DOS языка C. Для передачи функции EMM значений регистров используется структура REGPACK. Описание и примеры использования функции intr и структуры REGPACK см. в разделе «Лабораторная работа №1».

3.4. Контрольные вопросы

  1. Какие типы памяти IBM PC Вы знаете?

  2. Какая информация хранится в верхней памяти (UMB)?

  3. Чем отличаются спецификации EMS и XMS?

  4. Каким образом можно получить доступ к странице EMS?

  5. Для чего служит дескриптор блока EMS-памяти?

3.5. Варианты заданий на лабораторную работу

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

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

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

  4. Написать программу для разбивки файла на несколько частей (фрагментов) заданного размера. Считать файл в EMS. Имя файла вводится в виде параметра программы с командной строки. Размер фрагмента вводится с клавиатуры. Имена файлов (фрагментов исходного файла) выбирать в формате FILENAME.N, где FILENAME – имя исходного файла, N – номер фрагмента. Предусмотреть выдачу сообщения об ошибке при нехватке места на диске в процессе записи данных в файл.

  5. Написать программу для объединения нескольких файлов в один файл. Считать файлы в EMS и затем сформировать новый файл, который является конкатенацией данных исходных файлов. Имена файлов вводятся в виде параметров программы с командной строки. Предусмотреть выдачу сообщения об ошибке при нехватке места на диске в процессе записи данных в файл.

4. Лабораторная работа №3

4.1. Цель работы

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

4.2. Основные теоретические положения

Программирование графического интерфейса пользователя в Windows 3.1 выполняется с использованием функций Windows API. Взаимодействие программы Windows с пользователем и системой осуществляется при помощи механизма сообщений.

Под сообщением понимается некая структура данных, которая содержит идентификатор окна, которому посылается сообщение и информацию о самом сообщении – его тип и данные, относящиеся к конкретному типу сообщения.

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

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

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

Далее рассмотрим пример простейшей программы для Windows 3.1, создающей одно основное окно и выполняющей обработку сообщений.

#include <windows.h>

#include <mem.h>

#define BITMAP_WIDTH 256

#define BITMAP_HEIGHT 128

const char szClassName[] = "ExWndClass";

const char szWindowName[] = "Простейшее Windows-приложение";

const int CMD_EXIT = 101;

HINSTANCE hInst;

void OnPaint(HDC hDC)

{

HDC MemDC = CreateCompatibleDC(hDC);

HBITMAP Bm = LoadBitmap(hInst, "BITMAP");

SelectObject(MemDC, Bm);

BitBlt(hDC, 0, 0, BITMAP_WIDTH, BITMAP_HEIGHT,

MemDC, 0, 0, SRCCOPY);

DeleteObject(MemDC);

DeleteObject(Bm);

}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

PAINTSTRUCT ps;

switch (msg)

{

case WM_DESTROY:

PostQuitMessage(0);

break;

case WM_LBUTTONDOWN:

MessageBox(hWnd, "Вы нажали левую кнопку мыши!",

"Сообщение", MB_OK | MB_ICONINFORMATION);

break;

case WM_RBUTTONDOWN:

MessageBox(hWnd, "Вы нажали правую кнопку мыши!",

"Сообщение", MB_OK | MB_ICONINFORMATION);

break;

case WM_COMMAND:

if (wParam == CMD_EXIT)

DestroyWindow(hWnd);

break;

case WM_PAINT:

BeginPaint(hWnd, &ps);

OnPaint(ps.hdc);

EndPaint(hWnd, &ps);

break;

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

}

return 0;

}

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

WNDCLASS wc;

MSG msg;

HWND hWnd;

hInst = hInstance;

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

wc.style = CS_HREDRAW | CS_VREDRAW;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hInstance = hInstance;

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

wc.lpszClassName = szClassName;

wc.lpszMenuName = "MENU";

wc.lpfnWndProc = WindowProc;

RegisterClass(&wc);

hWnd = CreateWindow(szClassName, szWindowName,

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

HWND_DESKTOP, 0, hInstance, NULL);

if (!hWnd)

{

MessageBox(HWND_DESKTOP,

"Ошибка при создании окна!", "Ошибка",

MB_OK | MB_ICONSTOP);

return 0;

}

ShowWindow(hWnd, nCmdShow);

UpdateWindow(hWnd);

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

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

В начале функции WinMain выполняется инициализация оконного класса. Функция LoadIcon используется для загрузки стандартного значка приложения. Функция LoadCursor используется для загрузки стандартного курсора мыши. Цвет окна (поле hbrBackground структуры WNDCLASS) устанавливается в стандартный цвет окна Windows. Параметр hInstance функции WinMain идентифицирует данный исполняемый модуль. Этот параметр используется в программе для загрузки различных ресурсов. Поле lpszMenuName структуры WNDCLASS используется для указания имени ресурса меню. При создании окна меню автоматически загружается из ресурсов исполняемого модуля.

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

Функция CreateWindow используется непосредственно для создания окна. При вызове функции указываются следующие параметры:

  1. имя оконного класса;

  2. заголовок окна;

  3. тип окна (в данном случае – обычное окно);

  4. расположение и размеры окна (в данном случае использованы значения по умолчанию – константы CW_USEDEFAULT);

  5. идентификатор родительского окна – в данном случае это рабочий стол (константа HWND_DESKTOP);

  6. идентификатор меню – в данном случае он равен 0, так как имя ресурса меню указано в оконном классе;

  7. идентификатор исполняемого модуля;

  8. указатель на параметр, передаваемый в сообщении создания окна – в данном случае он не нужен и равен NULL.

В случае успешного выполнения функция CreateWindow возвращает идентификатор окна. В случае возникновения ошибки возвращаемое значение равно 0.

Функция MessageBox используется для вывода сообщения в виде небольшого диалогового окна с текстом сообщения. При вызове функции указываются следующие параметры:

  1. идентификатор родительского окна;

  2. текст сообщения;

  3. заголовок сообщения;

  4. флаги – типы кнопок диалогового окна и тип значка.

Функция ShowWindow используется для отображения окна на экране. При вызове функции указывается идентификатор окна и то, каким образом отобразить окно (показать/скрыть и др.). В данном случае берется значение, переданное функции WinMain тем приложением, которое запустило данное приложение.

Функция UpdateWindow посылает окну сообщение о перерисовке, обновляя, таким образом, клиентскую часть окна.

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

Функция TranslateMessage используется для преобразования кодов виртуальных клавиш в коды ASCII.

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

Цикл обработки сообщений в функции WinMain состоит из следующих шагов:

  1. получить сообщение из очереди сообщения приложения (функция GetMessage);

  2. преобразовать коды виртуальных клавиш в ASCII-коды (функция TranslateMessage);

  3. передать сообщение окну приложения (функция DispatchMessage).

Цикл выполняется до тех пор, пока не получено сообщение о закрытии главного окна приложения.

При вызове оконной процедуры передаются следующие параметры:

  1. идентификатор окна, которому послано сообщение (параметр hWnd);

  2. код сообщения (параметр msg);

  3. 1-й параметр сообщения (wParam);

  4. 2-й параметр сообщения (lParam);

В Windows 3.1 параметр wParam имеет размер 2 байта (слово), а параметр lParam – 4 байта (двойное слово).

В оконной процедуре используется оператор switch для обработки кодов сообщений. В данном примере используются следующие сообщения:

  1. WM_DESTROY – посылается после закрытия окна;

  2. WM_LBUTTONDOWN – посылается при нажатии в окне левой кнопки мыши;

  3. WM_RBUTTONDOWN – посылается при нажатии в окне правой кнопки мыши;

  4. WM_COMMAND – посылается при выборе пункта меню;

  5. WM_PAINT – посылается при перерисовке окна.

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

При получении сообщения WM_COMMAND параметр wParam содержит идентификатор пункта меню, который был выбран. В примере, если был выбран пункт меню ФайлВыход, вызывается функция DestroyWindow, которая закрывает окно и освобождает связанные с ним ресурсы. Помимо этого функция DestroyWindow посылает окну сообщение WM_DESTROY.

При получении сообщения WM_DESTROY в примере вызывается функция PostQuitMessage, которая записывает в очередь сообщений приложения сообщение о закрытии главного окна приложения. Это сообщение затем обрабатывается функцией GetMessage – она возвращает 0 и цикл обработки сообщений на этом завершается.

Рассмотрим действия, выполняемые при перерисовке окна.

Сообщение WM_PAINT посылается при необходимости перерисовки клиентской области окна. Сообщение посылается, например, при вызове функции UpdateWindow, или когда окно было перекрыто другим окном.

Функция BeginPaint подготавливает окно для перерисовки и заполняет поля структуры PAINTSTRUCT информацией о перерисовке. После вызова этой функции вызывается функция OnPaint, которая осуществляет вывод растрового изображения в окне. Одним из полей структуры PAINTSTRUCT, необходимым для вывода растрового изображения, является поле hdc – идентификатор контекста устройства. Контекст устройства представляет собой некий системный объект, используемый для вывода в окно или на принтер различных геометрических примитивов и растровых изображений.

Функция EndPaint сигнализирует оболочке о завершении перерисовки окна и всегда вызывается в паре с функцией BeginPaint.

Рассмотрим работу функции OnPaint.

Для вывода растрового изображения в окно его сначала необходимо загрузить из ресурса исполняемого модуля – это действие осуществляется функцией LoadBitmap. При выводе растрового изображения используется копирование пикселей с одного контекста устройства на другой. Поэтому необходимо создать новый контекст устройства – MemDC функцией CreateCompatibleDC. При вызове данной функции в памяти создается контекст устройства, совместимый с указанным контекстом устройства. Для копирования растрового изображения в контекст устройства используется функция SelectObject. Данная функция также используется при работе с различными режимами закраски и вывода линий. После подготовки контекста устройства вызывается функция BitBlt, которая осуществляет копирование пикселей с одного контекста устройства на другой. При вызове функции BitBlt указываются следующие параметры:

  1. контекст устройства, на который копируются пиксели;

  2. положение и размеры копируемой области;

  3. контекст устройства, с которого копируются пиксели;

  4. положение копируемой области на источнике копирования;

  5. режим копирования.

Результаты работы программы представлены на рис. 4.1.

Рис. 4.1 – результаты работы программы.

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

4.3. Задания и методические указания
к выполнению работы

4.3.1. Задание на лабораторную работу

Написать программу на языке C/C++, функционирующую в операционной среде Windows 3.1. Программа должна выполнять следующие функции:

  1. выводить на экран основное окно с меню;

  2. в меню должен присутствовать пункт «Выход», при выборе которого программа должна завершаться;

  3. программа также должна завершаться при закрытии основного окна;

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

4.3.2. Методические указания к выполнению работы

Для написания программы может быть использована программная среда Borland C++ 3.1 for Windows.

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

В каждом конкретном задании (см. пункт 4.5) указаны функции и сообщения Windows API, необходимые для его выполнения, не включая функции базового шаблона. Информацию и примеры использования указанных функций можно получить в Windows 3.1 API Reference (входит в состав Borland C++ 3.1 for Windows и других программных сред).

Программа должна быть написана лишь с использованием функций Windows API. Использование VCL, MFC, OWL и других библиотек классов не допускается.

Некоторые задания предполагают использования в программе ресурсов. Ресурсы создаются в программе Resource Workshop программной среды Borland C++ 3.1 for Windows.

4.4. Контрольные вопросы

  1. Какой механизм лежит в основе программирования графического интерфейса пользователя Windows?

  2. Что такое сообщение?

  3. Каким образом осуществляется обработка сообщений в программе?

  4. Для чего служат оконные классы?

  5. Что такое контекст устройства и для чего он используется?

4.5. Варианты заданий на лабораторную работу

  1. Написать программу, выводящую в диалоговом окне растровое изображение. Диалоговое окно должно содержать кнопку «OK», при нажатии на которую диалоговое окно закрывается, а также список из 5-ти элементов. При выборе элемента списка должно выводиться сообщение с текстом выбранного элемента. Добавить пункт «Диалоговое окно» в главное меню программы. Используемые функции и сообщения: DialogBox, DialogProc, SendDlgItemMessage, EndDialog, LB_ADDSTRING, LBN_SELCHANGE, LB_GETCURSEL, LB_GETTEXT, WM_COMMAND.

  2. Написать программу, выводящую в главном окне квадрат с координатами (100, 100)-(200, 200) и отображающую координаты курсора мыши. При попадании курсора мыши в квадрат должно выводиться сообщение с запросом на закрытие главного окна. Реализовать вывод такого же запроса при попытке закрытия основного окна. Используемые функции и сообщения: Rectangle, TextOut, WM_MOUSEMOVE, WM_CLOSE.

  3. Написать программу, вычисляющую корни (в том числе и комплексные) квадратного уравнения ax2 + bx + c = 0. Значения коэффициентов a, b, c должны вводиться в полях ввода главного окна. Главное окно должно содержать кнопку «OK», при нажатии которой производятся вычисления. Используемые функции и сообщения: CreateWindow, WM_COMMAND.

  4. Написать программу, отображающую список из 5-ти элементов и пустой список в главном окне. При выборе элемента списка в меню «Список» должен добавляться пункт с текстом выбранного элемента списка. При выборе пункта из меню «Список» во второй список главного окна должен добавляться элемента, соответствующий выбранному пункту меню. Используемые функции и сообщения: CreateWindow, AppendMenu, SendMessage, LB_ADDSTRING, LBN_SELCHANGE, LB_GETCURSEL, LB_GETTEXT, WM_COMMAND.

  5. Написать программу, выводящую растровое изображение в главном окне. Если при изменении размеров окна изображение не умещается в нем полностью, использовать скроллеры для прокрутки изображения по горизонтали и вертикали. Используемые функции и сообщения: GetScrollPos, SetScrollRange, WM_HSCROLL, WM_VSCROLL.

  6. Написать программу, строящую в главном окне график функции sin(t) * e-t, где t – параметр от 0 до 4 (откладывается по оси абсцисс), а  – некоторый коэффициент от 0 до 1. Значение коэффициента  должно вводиться в поле ввода главного окна. Главное окно также должно содержать кнопку «OK», при нажатии которой производится перерисовка графика. Используемые функции и сообщения: CreateWindow, WM_COMMAND.

  7. Написать программу, отображающую в главном окне текущую дату и время. Для даты выбрать шрифт Times New Roman размером 24 пункта. Для времени выбрать шрифт Arial размером 36 пунктов. Выполнять перерисовку даты и времени по сообщению WM_PAINT. Используемые функции и сообщения: gettime, getdate (time.h), CreateFontIndirect.

  8. Написать программу, отображающую в поле ввода главного окна содержание текстового файла. Поле ввода должно быть многостроковым и иметь возможность вертикальной и горизонтальной прокрутки. Имя файла вводить в диалоговом окне. Используемые функции и сообщения: CreateWindow (стиль ES_MULTILINE для многострокового поля ввода), DialogBox, WM_COMMAND.

  9. Написать программу, рисующую в главном окне изображение треугольника Серпинского (см. рисунок). Добавить в подменю «Файл» пункт «Печать», при выборе которого осуществляется распечатка построенного изображения на принтере. Используемые функции и сообщения: MoveTo, LineTo, PrintDlg, Escape, DeleteDC, WM_COMMAND.

  1. Написать программу, использующую диалоговое окно для добавления и удаления элементов списка. В диалоговом окне должен присутствовать список, поле ввода, кнопки «Добавить» и «Удалить». При нажатии кнопки «Добавить» в список должен добавляться элемент, соответствующий тексту в поле ввода. При нажатии кнопки «Удалить» должен удаляться выбранный элемент списка. DialogBox, DialogProc, SendDlgItemMessage, EndDialog, LB_ADDSTRING, LB_DELETESTRING, LBN_SELCHANGE, LB_GETCURSEL, LB_GETTEXT, EM_GETLINE, WM_COMMAND.

5. Лабораторная работа №4

5.1. Цель работы

Ознакомиться с парадигмой программирования в операционных системах Windows 9x (Windows 95, Windows 98, Windows ME). Получить практические навыки создания консольных приложений для ОС Windows 9x. Самостоятельно изучить функции Win32 API, необходимые для выполнения лабораторной работы.

5.2. Основные теоретические положения

5.2.1. Архитектура Windows 95

Операционная система Windows 95 состоит из следующих программных компонентов:

  1. диспетчер виртуальных машин (Virtual Machine Manager, VMM);

  2. виртуальные устройства (Virtual Devices, VxDs);

  3. ROM BIOS;

  4. устанавливаемые драйвера устройств (Installable Device Drivers) и резидентные программы (TSR);

  5. 16bit и 32bit динамические подключаемые библиотеки ОС Windows 95 (Dynamic-Link Libraries, DLL’s);

  6. программы – компоненты MS DOS.

Схематично архитектура Windows 95 представлена на рис. 1.

Рассмотрим подробно основные компоненты архитектуры Windows 95.

Диспетчер виртуальных машин – это 32-битная операционная система защищенного режима, являющаяся ядром Windows 95 и отвечающая в основном за создание, запуск, управление и завершение виртуальных машин. VMM представляет различные виды сервисов, управляющих памятью, процессами, прерываниями и исключениями. VMM и взаимодействующие с ним виртуальные устройства исполняются в едином 32-разрядном линейном адресном пространстве с нулевым уровнем привилегий (т.е. кольцо 0). VMM обеспечивает вытесняющую многозадачность с многопоточностью (Preemptive Multitasking With Multithreading). VMM способен исполнять несколько приложений одновременно, распределяя процессорное время между виртуальными машинами, в которых эти приложения выполняются. Отметим, что VMM нереентерабелен, т.е. виртуальным устройствам приходится синхронизировать свой доступ к сервисным функциям VMM.

Рис. 1 - архитектура Windows 95.

Виртуальные устройства (VxDs) – это 32-разрядные программы, которые обеспечивают аппаратную независимость VMM, принимая на себя все функции по управлению аппаратурой. VxD необходим каждому аппаратному модулю, работа которого может быть нарушена при переключении виртуальных машин или отдельных потоков. Заметим, что некоторые VxD поддерживают программные, а не аппаратные устройства, предоставляя API для некоторого общесистемного сервиса.

В Windows 95 включен IOCTL-интерфейс, позволяющий Win32-приложениям напрямую обращаться к VxD.

Драйвер устройства в Windows – это DLL, используемая ОС для взаимодействия с аппаратным устройством. Каждый драйвер содержит экспортируемый набор функций внешнего API, с помощью которых Windows и выполняющиеся в ее среде приложения получают доступ к соответствующему ресурсу или ресурсам.

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

DLL-модули могут использоваться как источники ресурсов для приложений. Код в DLL могут использовать сразу несколько программ без его дублирования, что приводит к существенной экономии памяти и времени выполнения функций. Разработчики программного обеспечения могут расширять среду Windows, создавая DLL-модули с процедурами, позволяющими выполнять какие-либо нестандартные операции и доступными другим Windows-приложениям. Windows поддерживает как 16-, так и 32-разрадные DLL, которые могут храниться в файлах с расширением DLL, или каким-либо другим расширением.

Windows 95 также поддерживает выполнение программ MS-DOS. Любую из них можно запускать в полноэкранном режиме или в консольном окне. Для каждой выполняющейся программы MS-DOS системой создается отдельная виртуальная машина. Кроме того, в Windows для большой совместимости предусмотрен режим монопольного выполнения DOS-программы (MS-DOS mode), в котором выполняется только указанная программа, которой отдаются все ресурсы компьютера, а графический пользовательский интерфейс при этом выгружается из памяти. Отметим, что Windows 95 поддерживает все системные функции и прерывания MS DOS, а также предоставляет расширения, позволяющие программам DOS использовать длинные имена файлов и такие функциональные возможности Windows, как монопольная блокировка дисковых томов, сервисы виртуальных машин и поддержка информационных файлов программ (PIF-файлов).

Windows 95 поддерживает 16-разрядные программы для Windows 3.x и 32-разрядные приложения, использующие API-интерфейсы Win32 и Win32s. Для 16-разрядных приложений Windows 95 реализует режим кооперативной многозадачности, что и в Windows 3.x, т. е. все эти программы выполняются в одном виртуальном адресном пространстве (в одной виртуальной машине), совместно используя одну очередь сообщений (message queue) и один поток исполнения. В то же время у каждого 32-разрядного Windows-приложения свое адресное пространство, своя очередь сообщений и свои один или несколько потоков исполнения. Кроме того, каждый 32-битный поток работает в условиях вытесняющей многозадачности. Отметим, что любые новые приложения должны быть 32-разрядными и использовать Win32 API.

5.2.2. Основные компоненты Windows 95

Рассмотрим подробно основные компоненты Windows 95.

5.2.2.1. Средства и расширения оболочки

Windows 95 включает в себя набор COM-интерфейсов и функций (Component Object Model – модель многокомпонентных объектов), позволяющих расширять возможности стандартной оболочки. Рассмотрим основные понятия, связанные с данной моделью:

  • пространство имен оболочки;

  • ярлыки;

  • расширения оболочки;

  • средства просмотра и анализа файлов;

  • компоненты панели управления – специальные DLL-библиотеки для настройки системы;

  • экранные заставки;

Пространство имен (namespace) – это набор таких символов, как имена файлов и каталогов. В оболочке Windows 95 используется единое пространство имен с иерархической структурой всех объектов, представляемых пользователю для непосредственного управления, в том числе файлов, дисков, принтеров, сетевых устройств и т.д. Оболочка Windows 95 предоставляет COM-интерфейс и несколько функций, позволяющих просматривать пространство имен и находить информацию о его объектах.

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

Ярлык позволяет приложению обращаться к объекту, не имея подробной информации о его текущем имени и расположении. Windows 95 предоставляют COM-интерфейс, через который приложение может создавать ярлыки.

Разработчики приложений могут расширять оболочку Windows 95, при этом необходимо добавить соответствующую информацию в системный реестр или создать внутренний (встраиваемый в процесс – in process) COM-сервер OLE. Пример расширения оболочки – обработчик контекстно-зависимого меню для рабочего стола оболочки. Одним из стандартных расширений оболочки является обработчик значков файлов, определяющий отображение значка для каждого файла. По умолчанию для всех файлов с одинаковым расширением выводится один и тот же значок, однако новый обработчик значков может придерживаться и другой стратегии отображения файлов. Еще одно стандартное расширение оболочки – специальный обработчик для файловых операций (copy hook handler), разрешающий или запрещающий операции, связанные с перемещением, копированием, удалением или переименованием файловых объектов.

Оболочка предоставляет команду Quick View (Быстрый просмотр), позволяющий просматривать содержимое файла, не запуская приложение, в котором он был создан, и даже не требуя присутствия этого приложения в системе. Когда пользователь выбирает команду Quick View для некоторого файла, система запускает соответствующее средство просмотра (file viewer), определяемое по расширению выбранного пользователем файла. Средство просмотра обеспечивает пользовательский интерфейс для просмотра файла и представляет собой многокомпонентный OLE-объект, реализованный во встраиваемом в процесс DLL-сервере. Средство просмотра работает совместно с анализатором файлов (file parser) – специальной DLL, отвечающей за низкоуровневый анализ формата файла. Каждый тип или класс файлов и связан с определенным средством отображения файлов (display engine).

5.2.2.2. Реестр

Реестр (registry) – это централизованная база данных, в которой хранится текущая информация о конфигурации аппаратных средств компьютера, установленных в системе приложениях и программных компонентах, пользовательских настройках и определениях типов файлов. Практически, реестр есть логическое развитие конфигурационных ini-файлов Windows 3.x.

5.2.2.3. Шрифты

В Windows 95 шрифты используются для отображения текста на экране и других устройствах вывода. Шрифт (font) определяется как набор знаков и символов одного дизайна, определяемый тремя основными характеристиками: гарнитурой (typeface), начертанием (style), размером (size). Размер шрифта измеряется в пунктах (points). В Win9x предусматривается поддержка следующих операций со шрифтами:

  • вывод текста стандартным шрифтом;

  • проверка способности устройства выводить текст;

  • выравнивание текста;

  • вывод текста различными шрифтами;

  • вращение текстовых строк;

  • считывание контуров символов в шрифтах TrueType;

  • создание и установка собственных шрифтов.

5.2.2.4. Подсистема печати.

В Windows 95 весь вывод на любые графические устройства является аппаратно-независимым, за исключением некоторых особых случаев. Для работы с любым из устройств вывода используется стандартный набор функций GDI (Graphics Device Interface). За преобразование высокоуровневых команд в аппаратные и передачу заданий принтеру отвечает подсистема печати, реализуя часть GDI по отношению к любым устройствам печати. В нее входят:

  • Драйвер устройства. Это DLL-библиотека, поддерживающая интерфейс драйверов устройств (Device Driver Interface). Обрабатывая поступающие от GDI вызовы DDI-функций, драйвер устройства генерирует низкоуровневые команды печати.

  • Спулер печати (print spooler). Основной компонент подсистемы печати. Является приложением, управляющим процессом печати, в частности, спулер находит и загружает требуемый драйвер устройства, управляет очередью заданий на печать, преобразует вызовы GDI в регистрационные записи (journal records).

  • Процессор печати – DLL-библиотека, преобразующая регистрационные записи в вызовы DDI-функций.

  • Монитор порта. DLL-библиотека, которая передает печатающему устройству поток низкоуровневых команд через параллельный или последовательный порт.

Windows 95 также предоставляет набор функций для получения информации о следующих компонентах подсистемы печати:

  • монитор печати (спулер) на указанном сервере;

  • очередь заданий на печать для конкретного принтера;

  • порты, доступные для печати на указанном сервере;

  • установленные драйвера принтеров;

  • имеющиеся принтеры, серверы печати, домены или компоненты доступа к сетевым принтерам

  • процессоры печати, установленные на указанном сервере и типы данных, поддерживаемые ими.

5.2.2.5. Файловая подсистема

В Windows 95 основой файловой системы является FAT защищенного режима, мало отличающаяся от FAT16 MS-DOS. Одним из существенных изменений является поддержка длинных имен файлов. FAT расширенного режима совместима с FAT16 MS-DOS. Все Win32-приложения автоматически получают доступ ко всем возможностям файловой подсистемы через функции Win32 API.

5.2.3. Классификация API функций

Microsoft Win32 API предлагается как универсальный интерфейс для всех 32-разрядных платформ: Windows 9x, Windows NT, Windows 3.x c установленным расширением интерфейса Win32s. Несмотря на заявляемую универсальность, существует ряд отличий его реализации на разных платформах. Win32 API состоит из следующих основных частей:

  • GDI (Graphics Device Interface – интерфейс графических устройств);

  • подсистема управления окнами;

  • системные сервисы;

  • сервисы поддержки мультимедиа;

  • службы реализации механизмов RPC (Remote Procedure Calls).

Далее рассмотрим более подробно компоненты, входящие в состав Win32 API.

5.2.3.1. GDI

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

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

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

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

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

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

Программы формируют вывод, направляемый в окно, с помощью функций GDI. При этом приложения получают доступ не ко всему экрану, а только к конкретным окнам. Обычно приложения рисуют в окнах при обработке поступающих сообщений. Когда размеры или координаты окна изменяются, система передает программе сообщение, требуя дорисовать ранее невидимые или стертые участки окна. Программы принимают ввод с клавиатуры и от мыши в виде сообщений. Система генерирует соответствующие сообщения и помещает их в очередь сообщений приложения. Такая очередь автоматически создается для каждого приложения. Приложение в цикле обработки сообщений извлекает их из очереди и пересылает их соответствующим оконным процедурам для обработки. Функции оконной подсистемы также обеспечивают ряд других возможностей. В частности, это поддержка операций над буфером обмена и механизма DDE.

5.2.3.3. Системный сервис.

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

Сетевые функции управляют соединениями с разделяемыми ресурсами сети.

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

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

5.2.3.4. Мультимедиа

Мультимедийные функции предоставляют различные виды сервиса для управления мультимедийными устройствами, аудио- и видеоинформацией, файловым вводом/выводом, джойстиками и таймерами.

Для воспроизведения и записи аудиоданных в различных форматах программы используют функции аудиообработки. В ряде случае возможно программное микширование разных звуковых источников. Для эффективного хранения аудиоданных используются аудиокодеки, доступ к которым программы получают через диспетчер сжатия звука (Audio Compressor Manager, ACM).

Для захвата отдельных видеокадров, их сжатия и управления воспроизведением видеоклипов служат функции обработки видеоинформации. Для эффективного хранения видеоданных предназначены видеокодеки, доступные через соответствующий диспетчер (ICM, Installable Compression Manager). Воспроизводить видеоклипы можно либо на мониторе, либо на других устройствах, неявно используя MCI-интерфейс (Media Control Interface) через функции оконного класса MCIWnd. Тажке возможно непосредственное взаимодействие с функциями MCI.

Сохранение и считывание различных типов мультимедйных данных реализуется за счет функций файлового ввода/вывода, в том числе с буферизацией. Этими функциями поддерживаются форматы RIFF (Resource Interchange File Format) и AVI (Audio-Video Interleaved).

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

5.2.3.5. Вызовы удаленных процедур

Механизм RPC (Remote Procedure Calls) позволяет организовывать приложения с распределенной схемой вычислений. Примерами таких приложений являются совместно используемые базы данных, файл-серверы, серверы печати. Распределенное приложение, выполняемое как процесс в одном адресном пространстве, обращается к процедурам, исполняемым в адресном пространстве на другом компьютере. Самой программе такие вызовы представляютя обычными вызовами локальных процедур, но на самом деле они активизируют процедуры, которые, взаимодействуя со стандартной библиотекой RPC, обращаются к удаленному адресному пространству. RPC управляет всеми аспектами сетевой связи, необходимыми для поддержки этих вызовов, в том числе и связанными с сетевыми протоколами. Отметим, что Microsoft RPC – это всего лишь один компонент среды распределенных вычислений, отвечающей стандартам OSF (Open Software Foundation) – консорциума компаний, объединившихся для выработки стандартов на компоненты среды распределенных вычислений.

5.2.3.6. Библиотеки расширений

Библиотеки расширений (Extension Libraries) предоставляют программам сервис, выходящий за рамки базового Win32 API. Существуют стандартные библиотеки расширений, реализующие следующие возможности:

  • стандартные элементы управления;

  • стандартные диалоговые окна;

  • декомпрессия данных;

  • установка файлов;

  • поддержка DDE (DDE management library, DDEML);

  • поддержка DDE в сети (сетевой DDE).

5.3. Задания и методические указания
к выполнению работы

5.3.1. Задание на лабораторную работу

Написать консольное приложение на языке C/C++, функционирующее в ОС Windows 9x. Приложение должно обеспечивать выполнение функций, указанных в конкретном задании. Для вывода информации на экран использовать функцию WriteConsole. Для ввода информации с консоли (если это указано в задании) использовать функцию ReadConsole. Предусмотреть в программе обработку ошибок, возникающих при вызовах функций Win32 API. Для получения кода ошибки использовать функцию GetLastError. Вывести на экран информацию об ошибке в случае ее возникновения.

5.3.2. Методические указания к выполнению
лабораторной работы

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

Вывод-вывод для консоли можно организовать двумя способами:

  1. Использование стандартных потоков ввода-вывода для вывода информации в окно консоли и для ввода информации с клавиатуры. В языке C для этого могут использоваться функции printf() (вывод) и scanf() (ввод), а в языке C++ – потоки cout (вывод) и cin (ввод).

  2. Использование функции WriteConsole для вывода информации в окно консоли, и функции ReadConsole для ввода информации с клавиатуры.

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

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

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

Для создания консольных приложений используются опции конкретного компилятора. Если используется какая-либо среда разработки (например, Microsoft Visual C++ или Borland C++Builder), то, как правило, опция создания консольного приложения включена в параметры проекта.

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

  1. Microsoft Developer Network Library (MSDN Library).

  2. Microsoft Win32 Programmer’s Reference (обычно это файл win32.hlp – входит в состав таких сред разработки как Borland C++ 5.0, Borland C++Builder, Borland Delphi).

В указанных источниках функции Win32 API описаны достаточно подробно. Приведены различные рекомендации и примеры использования.

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

#include <windows.h>

#include <tchar.h>

int _tmain(int argc, LPTSTR argv[])

{

TCHAR szPath[MAX_PATH];

TCHAR szInfo[256];

WIN32_FIND_DATA FindFileData;

HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

DWORD Written;

if (argc > 1)

lstrcpy(szPath, argv[1]);

else

lstrcpy(szPath, _T(""));

lstrcat(szPath, _T("*.*"));

HANDLE hFind = FindFirstFile(szPath, &FindFileData);

if (hFind != INVALID_HANDLE_VALUE)

{

do

{

if (FindFileData.dwFileAttributes &

FILE_ATTRIBUTE_DIRECTORY)

wsprintf(szInfo, _T("%s <DIR>\n"),

FindFileData.cFileName);

else

wsprintf(szInfo, _T("%s %d\n"),

FindFileData.cFileName,

FindFileData.nFileSizeLow);

WriteConsole(hOut, szInfo, lstrlen(szInfo),

&Written, NULL);

} while (FindNextFile(hFind, &FindFileData));

FindClose(hFind);

}

}

5.4. Контрольные вопросы

  1. Какие основные компоненты входят в архитектуру Windows 95?

  2. Что такое диспетчер виртуальных машин?

  3. Какие основные компоненты входят в состав подсистемы управления печатью?

  4. Что такое реестр?

  5. Что такое вызов удаленных процедур?

  6. Для каких целей могут быть использованы функции системного сервиса?

5.5. Варианты заданий на лабораторную работу

  1. Написать программу, выполняющую поблочное копирование файлов. Имена файлов ввести с командной строки. Использовать функции CreateFile, ReadFile, WriteFile и CloseHandle. Если файл уже существует, выдать предупреждение и запрос на перезапись существующего файла.

  2. Написать программу, выполняющую построчное чтение данных с клавиатуры и запись считанных символов в файл. Для установки режима построчного чтения использовать функцию SetConsoleMode с константами ENABLE_PROCESSED_INPUT и ENABLE_LINE_INPUT. В этом случае Функция ReadConsole будет возвращать управление при нажатии клавиши Enter. Программа завершается при нажатии комбинации клавиш Ctrl+C. Программа должна завершаться при вводе пустой строки. Использовать в программе следующие функции: CreateFile, WriteFile, CloseHandle, GetStdHandle.

  3. Модернизировать пример программы чтения содержимого папки (см. выше) так, чтобы обеспечить возможность рекурсивного обхода дерева каталогов и вывода информации о файлах. Вывести помимо размера файла дату и время его создания, а также атрибуты. Использовать функцию FileTimeToSystemTime для вывода даты и времени создания файла.

  4. Написать программу, записывающую в файл шестнадцатеричный дамп указанного файла. Имена файлов ввести с клавиатуры. Шестнадцатеричный дамп представляет собой изображение значения байт в шестнадцатеричном виде. Например, значению 0x2F соответствует строка “2F”. Последовательность значений байт записывается в виде “XX XX XX …”, где XX – значение байта, записанное в шестнадцатеричном виде. Записывать в файл по 16 значений байт в строке. Использовать в программе следующие функции: CreateFile, ReadFile, WriteFile, CloseHandle.

  5. Написать программу, выводящую на экран содержимое текстового файла. Имя файла ввести с командной строки. Отображать на экране только символы с кодами 32 – 127. Использовать в программе следующие функции: CreateFile, ReadFile, CloseHandle.

  6. Написать программу, выводящую следующую информацию об указанном файле: дата и время создания, дата и время последнего обращения, размер файла, имя MS-DOS. Имя файла ввести с клавиатуры. Использовать в программе функцию: FindFirstFile и структуру WIN32_FIND_DATA.

6. Лабораторная работа №5

6.1. Цель работы

Ознакомиться с основными концепциями объектной модели компонентов (COM). Получить практические навыки написания встраиваемых в процесс COM серверов на примере реализации интерфейсов расширения оболочки Windows 9x/NT/2000.

6.2. Основные теоретические положения

6.2.1. Краткие сведения о COM

Объектная модель компонентов (Component Object Model – COM) это платформонезависимая, распределенная, объектно-ориентированная система, предназначенная для разработки взаимодействующих друг с другом двоичных программных компонентов. COM является фундаментальной основой многих распространенных технологий, включая ActiveX и OLE.

COM не является объектно-ориентированным языком программирования, а является стандартом. COM также не определяет структуру приложения. Язык, структура и детали реализации выбираются программистом. COM лишь определяет объектную модель и требования к программе, которые позволяют COM объектам (называемыми также COM компонентами или иногда просто объектами) осуществлять взаимодействие с другими объектами. Эти объекты могут присутствовать в едином процессе, в различных процессах или даже на различных компьютерах в сети. Объекты могут быть написаны на различных языках программирования, и их структуры могут сильно различаться. Вот почему о COM говорят, как о двоичном стандарте – это стандарт, применяемый после компиляции программы в двоичный машинный код.

Единственным требованием, предъявляемым COM к языку программирования, является возможность создания структур указателей и вызов функций через указатели. Объектно-ориентированные языки программирования, такие как C++ и Smalltalk, включают программные средства, упрощающие написание COM объектов. Однако, такие языки, как C, Pascal, Ada, Java и даже Basic также позволяют создавать и использовать COM объекты.

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

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

В данной лекции рассматриваются основные аспекты COM по отношению к разработке COM объектов.

6.2.2. COM объекты и интерфейсы

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

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

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

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

Простые объекты могут поддерживать только один интерфейс. Более сложные объекты обычно поддерживают несколько интерфейсов. Клиенты имеют доступ к COM объекту только через указатель на один из его интерфейсов, что позволяет клиентам вызывать методы конкретного интерфейса. Методы определяют использование клиентами данных объекта.

Интерфейсы, в свою очередь, определяют соглашение между объектом и его клиентами. Соглашение указывает методы, которые должны быть связаны с соответствующим интерфейсом, а также параметры методов и возвращаемые ими значения. Соглашение, однако, не определяет то, каким образом реализовывать методы интерфейса. Другим важным аспектом соглашения является то, что если объект поддерживает интерфейс, он должен каким-либо образом поддерживать все методы интерфейса. Не обязательно, чтобы реализация каждого метода выполняла какие-либо действия – если объект не поддерживает функционал метода, реализация метода может просто возвращать управление, либо возвращать сообщение об ошибке. В любом случае реализация метода должна существовать.

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

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

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

Ссылки на интерфейс во время выполнения программы определяются при помощи глобального уникального идентификатора интерфейса (Interface Identifier – IID). IID, являющийся экземпляром GUID (Globally Unique Identifier – глобальный уникальный идентификатор) позволяет клиенту точно узнать, поддерживает ли объект тот или иной интерфейс. Использование IID необходимо, поскольку в системе могут присутствовать несколько версий одного и того же интерфейса с одинаковыми именами.

Обобщим вышесказанное относительно интерфейса:

  • Интерфейс – это не то же самое, что и класс в языке C++;

  • Интерфейс – это не объект;

  • Интерфейсы строго типизированы;

  • Интерфейсы неизменяемы.

6.2.3. Интерфейс IUnknown и наследование инерфейсов

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

Наследование широко используется в предопределенных COM интерфейсах. Все предопределенные интерфейсы (и любые интерфейсы, определяемые программистом) наследуют свои определения от важного интерфейса IUnknown. Этот интерфейс содержит три виртуальных метода: QueryInterface, AddRef и Release. Все COM объекты должны реализовывать интерфейс IUnknown, поскольку он обеспечивает возможность переключения между несколькими интерфейсами, поддерживаемыми объектом (для этого используется метод QueryInterface). Также интерфейс IUnknown позволяет контролировать время жизни объекта при помощи методов AddRef и Release.

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

6.2.4. Назначение IUnknown::QueryInterface

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

Чтобы воспользоваться QueryInterface, клиент вызывает его с помощью любого из имеющихся у него в данный момент указателей на интерфейсы объекта. Клиент передает IID нужного ему интерфейса как параметр метода. Например, пусть у клиента уже имеется указатель на интерфейс А, и требуется получить указатель на интерфейс В. Клиент запрашивает данный указатель вызовом QueryInterface через указатель А, задавая в качестве параметра IID интерфейса В (шаг 1). Если объект поддерживает В, то он возвращает указатель на этот интерфейс (шаг 2), и клиент может теперь может вызывать методы В (шаг 3). Если же объект не поддерживает В, он возвращает NULL.

Вообще говоря, самый важный элемент СОМ — QueryInterface. Именно эта простая схема решает очень важную и сложную проблему — контроль версий. В современной практике программирования создание программ на основе СОМ-объектов — обычное дело. Объекты, составляющие приложения, создаются множеством организаций, каждая из которых модернизирует свои объекты независимо от остальных. Как все это будет работать, если новые возможности добавляются в разные объекты в разное время? Как установить новую версию объекта с расширенными возможностями, не повредив программам, использующим только старые возможности? И как после модернизации клиента под новые возможности обеспечить автоматическое начало их использования этим клиентом? Ответ на все эти вопросы дает QueryInterface.

Лучше всего продемонстрировать это на примере. Допустим, имеется некий набор инструментов обработки текста, реализованный в виде СОМ-объекта, поддерживающего интерфейс ISpellChecker. Если установить такой объект на компьютер, текстовый процессор (и другие клиенты) сможет его использовать. Чтобы получить доступ к сервисам объекта, текстовый процессор запрашивает указатель на ISpellChecker через QueryInterface. Так как объект поддерживает этот интерфейс, то возвращает соответствующий указатель, и текстовый процессор вызывает методы ISpellChecker.

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

Предположим теперь, что на машине установлена новая версия текстового процессора, поддерживающая работу со словарем синонимов. Когда в следующий раз пользователь вызовет старый текстовый процессор, он, как обычно, запустит объект — инструментарий для обработки текста и запросит указатель на интерфейс ISpellChecker. Однако новая версия текстового процессора обладает информацией, достаточной для того, чтобы запросить указатель на IThesaurus. Так как версия объекта, которая поддерживает данный интерфейс, была установлена ранее, нужный указатель будет возвращен, и текстовый процессор сможет воспользоваться новой функцией. Итак, в итоге установлена новая версия инструментария для обработки текста, не нарушающая при этом работы существующих его клиентов, а также обеспечено автоматическое использование этими клиентами функций новой версии, после того как сами клиенты были обновлены.

Что делать в случае установки новой версии текстового процессора, без приобретения новой версии инструментария для обработки текста? Все также работает за исключением того, что текстовый процессор не предоставляет таким пользователям возможностей словаря синонимов. Текстовый процессор запускает объект-инструментарий и через QueryInterface успешно получает указатель на ISpellChecker. Однако, запрашивая указатель на IThesaurus, он получает в ответ NULL. Если текстовый процессор написан с учетом подобной возможности, он отключает пункт меню Thesaurus. Поскольку объект, реализующий IThesaurus, отсутствует, у пользователя не будет доступа к функциям словаря синонимов. Как только пользователь установит модернизированный объект — инструментарий для обработки текста – этот пункт меню будет активизирован без каких-либо изменений в текстовом процессоре.

Рассмотрим еще один пример. Что, если создатель объекта — инструментария для обработки текста — пожелает изменить или расширить функциональные возможности объекта по корректировке орфографии? Это влечет за собой изменение или добавление новых методов, которые будут видимы клиенту объекта. Однако СОМ не разрешает изменять интерфейсы, поэтому существующий интерфейс ISpellChecker изменять нельзя. Вместо этого создатель объекта должен определить новый интерфейс, скажем, ISpellChecker2, и включить в него необходимые новые или измененные методы. Объект по-прежнему поддерживает ISpellChecker, но теперь он также будет поддерживать и ISpellChecker2. Добавление в объект поддержки ISpellChecker2 ничем не отличается от добавления поддержки любого нового интерфейса. Как и все СОМ-интерфейсы, новый имеет уникальный IID, который клиент, знающий о новом интерфейсе, может использовать для запроса указателя через QueryInterface. Как и в предыдущем случае с IThesaurus, клиенты, ничего не знающие о произошедшей модернизации, никогда не запросят указатель на ISpellChecker2, и не ощутят никакого воздействия со стороны изменений — они будут продолжать использовать ISpellChecker.

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

6.2.5. Подсчет ссылок

Чтобы воспользоваться СОМ объектом, клиент должен явно инициировать начало работы экземпляра этого объекта. Здесь возникает естественный вопрос: «Когда завершается работа объекта?» Кажется, очевидное решение — возложить на клиента, запустивший объект на выполнение, еще и обязанность сообщить объекту, когда тот должен остановиться. Однако данное решение не работает, так как данный клиент может со временем оказаться не единственным, кто этот объект использует. Весьма распространена практика, когда клиент запускает выполнение объекта, получает указатели на его интерфейсы и затем передает один из них другому клиенту. Последний может использовать указатель для исполнения методов в том же самом объекте, а также в свою очередь передать указатель другим клиентам. Если бы первый клиент мог деинициализировать экземпляр объекта по своему желанию, то положение остальных клиентов было бы затруднительным — исчезновение объекта в тот момент, когда его сервисы используются, в лучшем случае привело бы к программной ошибке.

В то время как один объект может использоваться несколькими клиентами одновременно, никто из них не в состоянии узнать, когда все остальные завершатся. Так что разрешить клиенту деинициализировать объект напрямую — небезопасно. Только сам объект может знать, когда он может безопасно завершить свою работу, и только в том случае, если все клиенты сообщают объекту, что они завершили работу с ним. Такой контроль объекты осуществляют с помощью механизма подсчета ссылок (reference counting), поддерживаемого двумя методами интерфейса IUnknown.

Каждый исполняющийся объект поддерживает счетчик ссылок. Всякий раз, выдав указатель на один из своих интерфейсов, объект увеличивает счетчик ссылок на 1 (вообще объект может поддерживать отдельные счетчики ссылок для каждого интерфейса СОМ). Если один клиент передает указатель интерфейса другому клиенту, т. е. увеличивает число пользователей объекта без оповещения последнего, то клиент, получающий указатель, должен вызвать с помощью этого указателя метод AddRef. В результате объект увеличивает свой счетчик ссылок. Независимо от того, как он получил указатель на интерфейс, клиент всегда обязан вызвать для этого указателя метод Release, закончив с ним работу. Исполнение этого метода объектом состоит в уменьшении числа ссылок на 1. Обычно объект уничтожает сам себя, когда счетчик ссылок становится равным 0.

Подсчет ссылок может вызывать проблемы. Если не все клиенты следуют правилам, то экземпляр объекта может либо существовать неопределенно долго, либо, что еще хуже, быть преждевременно удаленным. И все-таки подсчет ссылок выглядит единственным работающим способом управления временем жизни объектов в многопоточной и динамичной среде, которую позволяет создать СОМ.

6.2.6. Классы

Всякий СОМ-объект является экземпляром некоторого класса, и каждому классу может быть присвоен GUID-идентификатор класса (CLSID). Клиент может передавать этот CLSID библиотеке СОМ для создания экземпляра класса. Однако наличие CLSID не обязательно для всех классов — объекты некоторых классов не создаются с помощью библиотеки СОМ, поэтому такие классы не обязаны иметь CLSID. Допустимо существование в любой данный момент времени одного, двух или многих активных объектов некоторого класса.

Связь между классом объекта и интерфейсами, которые этот объект поддерживает, не очень очевидна. Естественно было бы предположить, что объект данного класса поддерживает определенный набор интерфейсов, и что добавление к объекту нового интерфейса изменяет его класс. Однако это не обязательно — добавление новых интерфейсов к объекту без изменения его класса не запрещается СОМ. Вместо этого основным назначением CLSID является идентификация некоторого фрагмента кода для библиотеки СОМ, чтобы можно было загружать и активизировать объекты данного класса. В СОМ класс объектов идентифицирует некую реализацию группы интерфейсов, а не просто саму эту группу. Предположим, что объект — инструментарий для работы с текстом — решили реализовать два разных производителя, и оба созданных ими объекта поддерживают как ISpellChecker, так и IThesaurus. Хотя эти объекты и поддерживают одинаковый набор интерфейсов, они относятся к разным классам с разными CLSID, так как их реализации различны.

6.2.7. Серверы СОМ объектов

Каждый СОМ объект реализован внутри некоторого сервера, содержащего код, который реализует методы интерфейсов объекта, а также контролирует данные объекта, пока тот активен. Один сервер может поддерживать более одного объекта некоторого класса и даже поддерживать несколько классов. Рассмотрим три основные типа серверов:

Сервер «в процессе» (in-process): объекты реализуются в динамически подключаемой библиотеке, и, таким образом, исполняются в том же процессе и адресном пространстве, что и клиент.

Локальный сервер (out-process): объекты реализованы в отдельном процессе, исполняющемся на той же машине, что и клиент.

Удаленный сервер: объекты реализованы в DLL либо в отдельном процессе, которые расположены на удаленном по отношению к клиенту компьютере. Возможность создания таких серверов поддерживает распределенная СОМ (DCOM).

С точки зрения клиента, объекты, реализованные в любой из трех разновидностей серверов, выглядят одинаково. Доступ к методам объектов клиент по-прежнему осуществляет через указатели интерфейсов. При необходимости он может проводить различие между разными типами серверов, но это не обязательно. Запуск объекта, получение указателей на его интерфейсы, вызов их методов и освобождение указателей выполняются клиентом одинаково независимо от того, каким сервером реализован объект: «в процессе», локальным или удаленным.

6.2.8. СОМ и многопоточность

В традиционном простом процессе в один и тот же момент времени выполняется только одно действие. Другими словами, у процесса имеется только один поток выполнения (thread of execution). Однако иногда полезно обеспечить выполнение процессом нескольких задач одновременно. С этой целью у процесса может быть более одного потока выполнения, т. е. он становится многопоточным (multithreaded). Многопоточный процесс может повысить производительность, например, в тех случаях, когда в компьютере установлено несколько процессоров и процесс может назначать потоки на разные процессы. Многопоточность может также использоваться в распределенной среде, где процесс на одной машине выполняет запрос к другой. Вместо того, чтобы ждать, пока вторая машина отработает запрос, вызывающий процесс может использовать отдельный поток для выполнения полезной работы, пока запрос не будет выполнен. Многопоточность вносит дополнительные сложности и в программирование. Программист должен учитывать возможность возникновения конфликтов внутри процесса, когда, например, два потока пытаются изменять одну и ту же переменную. Корректная обработка таких ситуаций требует дополнительных усилий. Библиотеки, используемые многопоточными программами, также должны быть многопоточными, иначе могут возникнуть трудные для локализации ошибки. Одна из причин сложности локализации таких ошибок в том, что их трудно воспроизвести. Так как детали выполнения потока могут изменяться от одного запуска программы к другому, точные обстоятельства проявления ошибки могут возникать лишь от случая к случаю.

На некоторых платформах, где СОМ использовалась первоначально (Microsoft Windows 3.x и Macintosh), вопрос потоков не возникает. Так как ни одна из этих операционных систем не поддерживает потоки, то и опасности, связанные с ними, отсутствуют. Но Microsoft Windows 9x и Microsoft Windows NT, как и другие платформы, поддерживающие СОМ, допускают создание многопоточных процессов, поэтому для эффективного использования СОМ в таких средах необходимо учитывать вопросы, связанные с потоками.

Первым подходом, применявшимся для обеспечения создания многопоточных СОМ объектов, была модель комнат (apartment model). Основная идея заключается в том, что хотя процесс и может быть многопоточным, отдельные объекты СОМ — нет. Каждый поток выступает как «комната», и каждый объект СОМ находится только в одной такой комнате (т. е. одном потоке). Методы объекта могут вызываться только этим потоком — вызовы из других потоков помещаются в очередь и затем последовательно обрабатываются потоком, в котором располагается объект.

Модель комнат, несомненно, полезна, но не лишена недостатков. Возможность наличия в процессе нескольких потоков удобна, но еще большие выгоды можно получить, обеспечив параллельный доступ к любому объекту СОМ из многих потоков. Соответствующая поддержка — свободные потоки (free threading), или просто многопоточность — появилась с выходом в 1996 году Windows NT версии 4.0. При использовании свободных потоков внутри данного СОМ объекта может выполняться несколько потоков одновременно. Программист, пишущий код для такого объекта, должен позаботиться и о многопоточной безопасности, но если это сделано, СОМ более не ограничивает выполнение методов объекта единственным потоком.

6.2.9. Создание СОМ объектов

До этого места в изложении материала предполагалось, что клиент уже каким-либо образом получил исходный указатель на один из интерфейсов исполняющегося объекта. Конкретный способ получения клиентом этого указателя не обсуждался. В действительности способов несколько. Например, указатель может быть передан другим клиентом. Тем не менее, для каждого объекта имеется некоторой клиент, создающий его и получающий самый первый указатель интерфейса. В конечном счете, данный процесс основывается на использовании функций библиотеки СОМ.

Любой процесс, использующий COM должен инициализировать и деинициализировать библиотеку COM. COM реализует некоторые важные сервисные возможности в специальной COM библиотеке. Эта библиотека представлена набором динамических библиотек и исполнительных файлов (основными файлами являются OLE32.DLL и RPCSS.EXE). В ОС Microsoft Windows библиотека COM включает следующие компоненты:

  • Небольшое количество базовых API функций, позволяющих создавать COM приложения, как клиентские, так и серверные. Для клиентских приложений COM обеспечивает базовые функции создания объектов. Для серверных приложений COM обеспечивает средства реализации объектов.

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

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

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

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

6.2.10. Поиск COM серверов

Запрашивая создание объекта, клиент передает библиотеке СОМ идентификатор класса данного объекта, используя который, библиотека должна найти сервер этого класса. Здесь используется системный реестр, содержащий CLSID и местоположение исполняемого кода сервера. Классы всех объектов, которые будут создаваться на данной машине посредством библиотеки СОМ, должны быть зарегистрированы в реестре.

СОМ реализована на разных системах, и точный формат системного реестра может быть разным. Microsoft Windows и Microsoft Windows NT используют стандартную системную таблицу — она так и называется: реестр (registry). Другие реализации СОМ могут использовать другие схемы, которые, однако, должны включать:

  • CLSID, выступающий как ключ для поиска записи;

  • указание типа доступного сервера («в процессе», локальный или удаленный);

  • для серверов «в процессе» и локальных (т. е. для выполняемых на той же машине, что и клиент) должно быть задано полное имя DLL или исполняемого файла сервера; для удаленных серверов (доступных через DCOM) — указание, где искать исполняемый файл сервера.

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

6.2.11. Классы и экземпляры

Прежде чем перейти к рассмотрению процесса создания объекта библиотекой СОМ, рассмотрим, что понимается под созданием объекта. Создать объект — значит, начать исполнение экземпляра класса данного объекта. По крайней мере, в случае первого экземпляра класса это подразумевает запуск сервера данного класса. Фактически основная задача библиотеки СОМ в том, чтобы запустить именно сервер, а не сам объект.

Так как библиотеке СОМ известен только CLSID, она в состоянии запустить лишь некий абстрактный экземпляр объекта. CLSID достаточен для поиска кода методов объекта, но не для поиска его данных. Как создать не просто некий объект данного класса, но конкретный экземпляр, содержащий данные объекта, т. е. инициализированный объект?

Одной библиотеки СОМ для этого недостаточно. СОМ требует, чтобы объект инициализировал себя сам при указании клиенту — это отдельная операция, выполняемая после того, как объект запущен. Таким образом, в чистом виде СОМ требует двухэтапного процесса загрузки и инициализации объекта. Тем не менее, есть способ сразу указать и класс, и данные. Эта технология — моникеры. Моникер в состоянии скрыть от клиента все детали, являясь единым указателем и методов, и данных некоторого экземпляра объекта.

6.2.12. Создание одного объекта

Рассмотрим самый простой способ создания одного неинициализированного экземпляра объекта. Вначале клиент вызывает функцию библиотеки СОМ CoCreateInstance. Кроме других параметров, данный вызов задает CLSID объекта, который должен быть создан, а также IID некоторого интерфейса, поддерживаемого объектом. Далее библиотека СОМ по CLSID находит в системном реестре запись, соответствующую классу данного объекта. Библиотека СОМ предоставляет выполнение этой задачи диспетчеру управления сервисами (SCM – Service Control Manager). Запись содержит информацию о местоположении сервера, способного создать экземпляр класса объекта. После того как сервер найден, SCM запускает его.

Вместе с CLSID и IID первого интерфейса, указатель которого необходим клиенту, параметры CoCreateInstance позволяют также клиенту указать, какой тип сервера должен быть запущен библиотекой СОМ — например, «в процессе» или локальный. Клиент может сообщить, что тип сервера для него не имеет значения, либо задать любую комбинацию допустимых типов серверов.

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

До этого места подразумевалось, что создаваемый объект реализован в сервере «в процессе» или локальном, т. е. будет выполняться на той же машине, что и клиент. Что же происходит в случае удаленного сервера? Как создается удаленный экземпляр объекта?

Поддержка удаленных объектов обеспечивается DCOM. Процесс во многом аналогичен созданию локального объекта: клиент выполняет тот же вызов библиотеки СОМ, SCM просматривает системный реестр и т. д. Если же указан удаленный сервер, СОМ установит для создания экземпляра объекта связь с удаленной машиной. Как и все межкомпьютерные коммуникации, в DCOM данный запрос выполняется вызовом удаленной процедуры. Просмотрев свой реестр, удаленная система находит исполняемый файл сервера и создает экземпляр объекта. Так же, как и в случае локального сервера, возвращается указатель на интерфейс, после чего клиент может вызывать методы вновь созданного объекта. Для клиента запуск объекта выполняется одинаково независимо от того, каким сервером реализован объект: «в процессе», локальным или удаленным. Данное различие должно учитываться клиентом только тогда, когда он сам считает это необходимым.

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

6.2.13. Создание нескольких объектов одного класса:
фабрики классов

Если клиенту нужен только один объект, то проще всего создать его с помощью CoCreateInstance. И все же случается, что клиенту может понадобиться много экземпляров объектов одного и того же класса. Чтобы их создание выполнялось эффективно, клиент может получить доступ к фабрике классов (class factory) — объекту, способному создавать другие объекты. Каждая фабрика классов знает, как создавать объекты одного конкретного класса (хотя название «class factory» не вполне удачно — ведь эти фабрики создают экземпляры классов, а не классы). Фабрики классов — полноценные СОМ объекты: доступ к ним осуществляется через интерфейсы, они поддерживают IUnknown и т. д. И все же они необычные объекты, так как могут создавать другие СОМ объекты.

Все объекты, которые обсуждались до сих пор, созданы фабрикой классов. Даже когда клиент просто вызывает CoCreateInstance, реализация этой функции в библиотеке СОМ создает объект с помощью фабрики классов. CoCreateInstance скрывает эти детали от клиента. Но на самом деле она использует методы интерфейса IClassFactory, описываемые ниже.

6.2.14. Интерфейс IClassFactory

Чтобы являться фабрикой классов, объект должен поддерживать интерфейс IClassFactory. Этот простой интерфейс содержит только 2 метода:

CreateInstance создает новый экземпляр класса, объекты которого может создавать данная фабрика. Клиент не передает этому методу в качестве параметра CLSID, так как класс объекта неявно определяется самой фабрикой. И все же клиент задает IID, чтобы получить указатель на нужный ему интерфейс. Реализация данного метода фабрикой классов, написанной на C++, может создавать новый объект с помощью операции new.

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

В некоторых случаях интерфейс IClassFactory слишком прост. На данный момент имеется новый интерфейс IClassFactory2, добавляющий новые возможности. Так как IClassFactory2 наследует от IClassFactory, в его состав входят методы Createlnstance и LockServer, однако он поддерживает и еще несколько методов, связанных с лицензированием. Используя эти методы, можно разрешить создание новых объектов только лицензированным клиентам — таким, на чьих компьютерах установлена легальная, предположительно оплаченная копия программного обеспечения. Данная возможность особенно полезна для управляющих элементов ActiveX.

6.2.15. Использование фабрики классов

Чтобы получить доступ к фабрике классов, клиент вызывает функцию библиотеки СОМ CoGetClassObject. Этой функции передается CLSID класса объектов, которые будут создавать фабрики, а не CLSID самой фабрики. Клиент задает также IID интерфейса, нужного ему для работы с фабрикой. Обычно это IID интерфейса IClassFactory. Кроме того, как и в случае с CoCreateInstance, клиент может также задать тип сервера, который должен быть запущен для фабрики и ее объектов. Если для фабрики запрашивается, например, сервер «в процессе», то и объекты, созданные фабрикой, тоже будут выполняться данным сервером «в процессе».

Продемонстрируем использование фабрики классов. Допустим, клиент уже вызвал CoGetClassObject, библиотека СОМ запустила фабрику классов и возвратила указатель на интерфейс IClassFactory этой фабрики. Получив указатель, клиент вызывает метод IClassFactory::Createlnstance данного интерфейса (шаг 1). Среди параметров этого вызова клиент передает IID интерфейса, указатель на который ему необходим. В ответ фабрика класса создает объект (шаг 2) и возвращает клиенту указатель на заданный интерфейс (шаг 3). Теперь клиент может использовать возвращенный ему указатель для вызовов методов интерфейса (шаг 4).

6.2.16. Эмуляция

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

Однако здесь присутствует потенциальная проблема. Предположим, существующий класс заменили другим, поддерживающим все интерфейсы старого, а также и дополнительные. Иначе говоря, новый класс является полиморфным по отношению к первому: клиент может использовать его так же, как и старый класс. Допустим, однако, что у нового класса другой CLSID. Существующие клиенты написаны так, что создают объекты старого класса, используя старый CLSID. Если последний будет полностью устранен, прежние клиенты не будут работать. Необходим способ, позволяющий таким клиентам использовать новый класс.

Этот способ называется эмуляцией. Идея эмуляции проста: пусть клиент вызывает CoCreateInstance со старым CLSID, но на самом деле будет создаваться экземпляр нового объекта. Для поддержки этого СОМ предоставляет функцию CoTreatAsClass с двумя параметрами: старым и новым CLSID. После вызова соответствующей функции результатом попыток создания объектов с использованием старого CLSID будет создание объектов с новым CLSID. Реализация этого вызова обычно осуществляется путем записи отношения эмуляции между двумя CLSID в системный реестр. Объекты нового класса поддерживают все интерфейсы старого класса, поэтому существующие клиенты продолжают работать как прежде.

Данный механизм может применяться и для создания абстрактных компонентов аналогичных объекту-корректору орфографии, описанного выше. Например, текстовые процессоры могли бы использовать только один CLSID для идентификации объекта — корректора орфографии, поддерживающего стандартизированный интерфейс ISpellChecker. Но так как CLSID в СОМ задает некоторую реализацию интерфейса, то объекты-корректоры орфографии разных производителей будут иметь разные CLSID, хотя оба поддерживают один и тот же интерфейс. В таком случае можно определить стандартный CLSID, который будет просто обозначать «корректор орфографии». Текстовый процессор будет всегда использовать для создания объекта-корректора орфографии именно этот CLSID. Чтобы на данной системе запускался конкретный корректор, с помощью функции CoTreatAsClass задается отображение CLSID абстрактного корректора орфографии в CLSID выбранного объекта-корректора.

6.2.17. Инициализация СОМ объектов

Как уже говорилось, клиент запрашивает создание объекта, задавая его CLSID и IID одного из интерфейсов. В ответ фабрика класса создает некий абстрактный экземпляр данного класса. По сути, данный процесс обеспечивает объектам доступ к методам. Однако у объекта есть не только методы, но и данные. Скажем, когда клиент обращается к банковскому счету, ему обычно нужен конкретный счет. Таким образом, чтобы завершить создание экземпляра объекта, необходимо в общем случае загрузить его данные, а не только методы.

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

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

6.2.18. Повторное применение СОМ объектов

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

Многие объектно-ориентированные технологии в качестве основного механизма повторного использования существующего кода применяют наследование реализации (когда новый объект наследует фактическую реализацию методов существующего объекта). Однако создатели СОМ полагают, что такой тип наследования является непрактичным для объектной системы, предназначенной для крайне неоднородной среды. Отсутствие в СОМ наследования реализации ни в коей мере не оказывает влияния на использование этой техники в поддерживающих ее языках программирования типа C++. Реализации СОМ объектов могут использовать наследование реализации как обычно. Не поддерживается лишь наследование реализации другого СОМ объекта. Это не противоречие: C++ — язык реализации объектов, тогда как СОМ — технология, позволяющая создавать компонентное программное обеспечение и многое другое.

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

Включение и агрегирование — простые концепции. Обе предоставляют способ повторного применения, и в основе обеих концепций лежит некоторая взаимосвязь объектов. В терминологии СОМ «внешним» (outer) называется объект, повторно использующий сервисы «внутреннего» (inner) объекта. Внешний объект выступает как клиент внутреннего, однако их взаимосвязь может быть и более тесной.

6.2.19. Включение

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

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

6.2.20. Агрегирование

Если включение так легко реализовать, почему не использовать эту технику для повторного применения объектов СОМ всегда? Повторное применение объекта всегда может быть реализовано посредством включения — как правило, этого достаточно. Однако встречаются ситуации, когда этого не достаточно. Включение — не всегда самое эффективное решение.

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

Эту проблему устраняет агрегирование (aggregation). Агрегирование позволяет внешнему объекту предоставлять в качестве собственных интерфейсов интерфейсы, реализованные внутренним объектом. Когда клиент запрашивает у внешнего объекта указатель на подобный интерфейс, этот объект возвращает указатель на интерфейс внутреннего, агрегированного объекта. Методы внутреннего объекта добавляются, или агрегируются, к методам внешнего объекта. Клиент ничего об этом не знает: возвращенный интерфейс обеспечивается для него только одним известным ему объектом, а именно внешним. Агрегирование повышает эффективность, но, как и включение, абсолютно невидимо для клиента.

Однако агрегирование не является невидимым для участвующих в нем объектов. В отличие от включения агрегирование требует поддержки со стороны внутреннего объекта. Для этой цели он должен быть особым образом написан, в противном случае объект можно повторно использовать только путем включения. Почему агрегирование требует поддержки со стороны внутреннего объекта? Проблемы вытекают из операций, поддержка которых обязательна для всех объектов — операций, определенных в интерфейсе IUnknown. Два основных вопроса при реализации агрегирования — это обеспечение правильного подсчета ссылок и корректной работы Ouerylnterface.

Чтобы понять причины этих проблем, обратимся к примеру. Внешний объект предоставляет интерфейс А и, конечно, поддерживает интерфейс IUnknown. Внутренний, агрегируемый объект поддерживает интерфейсы В и IUnknown. Так как внешний объект агрегирует внутренний, а не просто включает его, то интерфейс В доступен клиенту внешнего объекта непосредственно.

Допустим, у клиента есть указатель интерфейса В. С точки зрения клиента, этот интерфейс предоставляется ему тем же объектом, что и интерфейс А. У клиента должна быть возможность получить указатель интерфейса А вызовом Ouerylnterface через указатель на интерфейс В. Но откуда внутренний объект знает, что внешний поддерживает интерфейс А? И если клиент вызывает IUnknown::AddRef через указатель на интерфейс В, то как об этом узнает внешний объект? В конце концов, с точки зрения клиента, существует лишь один объект, так что каждый из этих вызовов должен быть успешным.

Решение обеих проблем очевидно. Любой внутренний объект должен делегировать вызовы методов своего IUnknown методам IUnknown внешнего объекта (агрегирующего его). Следовательно, внутреннему объекту нужно как-то передать указатель на интерфейс IUnknown внешнего. Данный указатель, известный под названием «управляющий IUnknown» (Controlling IUnknown), передается как параметр либо CoCreateInstance, либо IClassFactory::CreateInstance при создании агрегируемого объекта. Если соответствующий параметр имеет значение NULL (самый распространенный случай), то объект знает, что он не агрегируется, и будет обрабатывать все вызовы методов IUnknown самостоятельно. В противном случае новый объект будет функционировать только как агрегированный внутренний объект некоторого внешнего объекта — того, что передал ему свой управляющий IUnknown. В последнем случае вызовы методов IUnknown внутреннего объекта делегируются методам IUnknown внешнего объекта, т. е. управляющему IUnknown.

6.3. Задания и методические указания
к выполнению работы

6.3.1. Задание на лабораторную работу

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

  1. IUnknown;

  2. IClassFactory;

  3. IShellExtInit;

  4. IContextMenu.

Написать управляющий файл реестра (с расширением .REG), регистрирующий созданный COM сервер в реестре. Сервер должен быть реализован на языке C++.

Протестировать работу COM сервера.

6.3.2. Методические указания к выполнению
лабораторной работы

Рассмотрим каким образом можно получить и определить в тексте программы уникальный идентификатор класса (CLSID) создаваемого COM сервера.

Для генерации CLSID и любого другого универсального уникального идентификатора (UUID) используется утилита UUIDGEN, входящая в стандартный комплект поставки среды Microsoft Visual Studio. Утилита запускается с командной строки, при этом могут указываться дополнительные опции. При запуске утилиты без параметров на экран выдается сгенерированный UUID в формате

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,

где X – шестнадцатеричная цифра. Для повышения удобства использования утилиты используются различные ключи и параметры, задаваемые в командной строке. В таблице 6.1 приведено описание ключей UUIDGEN.

Таблица 6.1 – ключи утилиты UUIDGEN.

Ключ

Назначение

Пример
использования

–i

Вывод UUID в виде шаблона IDL

uuidgen –i

–s

Вывод UUID в виде инициализированной структуры языка C.

uuidgen –s

–o

Вывод UUID в указанный файл

uuidgen –omyfile.txt

–n

Количество генерируемых UUID

uuidgen –n100

–v

Отображение версии программы

uuidgen –v

–h, –?

Вывод информации о ключах программы

uuidgen –h

IDL – это язык определения интерфейсов (Interface Definition Language), используемый как для создания COM серверов, так и для написания программ, использующих вызовы удаленных процедур (Remote Procedure Calling – RPC). Для упрощения написания COM сервера язык IDL в лабораторной работе использоваться не будет.

Наиболее удобной является опция UUIDGEN, позволяющая генерировать UUID в виде инициализированной структуры языка C. Результат выполнения утилиты в этом случае выглядит следующим образом:

INTERFACENAME = { /* a640e00b-beb9-4fa4-80d1-56bd7c65ec20 */

0xa640e00b,

0xbeb9,

0x4fa4,

{0x80, 0xd1, 0x56, 0xbd, 0x7c, 0x65, 0xec, 0x20}

};

INTERFACENAME заменяется на имя конкретного CLSID. Ниже представлен пример определения CLSID в программе:

GUID CLSID_MyClass = { /* a640e00b-beb9-4fa4-80d1-56bd7c65ec20 */

0xa640e00b,

0xbeb9,

0x4fa4,

{0x80, 0xd1, 0x56, 0xbd, 0x7c, 0x65, 0xec, 0x20}

};

Тип GUID предназначен для определения глобальных уникальных идентификаторов. Отметим, что понятия UUID, GUID, IID и CLSID используются в различных контекстах, но имеют одинаковую структуру и представляют собой последовательность шестнадцатеричных цифр в указанном выше формате.

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

class имя_класса: public имя_интерфейса1, имя_интерфейса_2, …

{

public:

описание методов интерфейсов

};

Предопределенный COM интерфейсы, такие как IUnknown и IClassFactory, описаны в заголовочном файле COMBASE.H. Интерфейсы расширения оболочки описаны в заголовочном файле SHLOBJ.H.

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

class CMyClassFactory: public IClassFactory

{

protected:

ULONG m_cRef;

public:

CIconViewClassFactory();

~CIconViewClassFactory();

STDMETHODIMP QueryInterface(REFIID, LPVOID FAR *);

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR *);

STDMETHODIMP LockServer(BOOL);

};

Переменная класса m_cRef используется для подсчета количества ссылок. Поскольку интерфейс IClassFactory наследует интерфейс IUnknown, включать IUnknown в описание базовых интерфейсов класса не обязательно. STDMETHODIMP и STDMETHODIMP_(type) – это макросы, определения которых выглядят следующим образом:

#define STDAPIVCALLTYPE __export __cdecl

#define STDMETHODIMP HRESULT STDMETHODCALLTYPE

#define STDMETHODIMP_(type) type STDMETHODCALLTYPE

Эти макросы используются со всеми методами реализуемых интерфейсов. Отличие STDMETHODIMP от STDMETHODIMP_(type) заключается в том, что STDMETHODIMP определяет метод, возвращающий значение типа HRESULT, а STDMETHODIMP_(type) определяет метод, возвращающий значение типа type. В приведенном выше примере метод AddRef возвращает значение типа ULONG.

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

CMyClassFactory::CMyClassFactory()

{

m_cRef = 0L;

g_cRefThisDll++;

}

CMyClassFactory::~CMyClassFactory()

{

g_cRefThisDll--;

}

STDMETHODIMP CMyClassFactory::QueryInterface(REFIID riid,

LPVOID FAR *ppv)

{

*ppv = NULL;

if (IsEqualIID(riid, IID_IUnknown) ||

IsEqualIID(riid, IID_IClassFactory))

{

*ppv = (LPCLASSFACTORY)this;

AddRef();

return NOERROR;

}

return E_NOINTERFACE;

}

STDMETHODIMP_(ULONG) CMyClassFactory::AddRef()

{

return ++m_cRef;

}

STDMETHODIMP_(ULONG) CMyClassFactory::Release()

{

if (--m_cRef) return m_cRef;

delete this;

return 0;

}

STDMETHODIMP CMyClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,

REFIID riid, LPVOID *ppvObj)

{

*ppvObj = NULL;

if (pUnkOuter) return CLASS_E_NOAGGREGATION;

CMyClass *pMyClass = new CMyClass();

if (!pMyClass) return E_OUTOFMEMORY;

return pMyClass->QueryInterface(riid, ppvObj);

}

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

В деструкторе CMyClassFactory количество ссылок на объект данного класса уменьшается на 1.

В реализации метода IUnknown::QueryInterface проверяется, является ли указанный IID идентификатором интерфейса IUnknown или IClassFactory. Если это так, то возвращается указатель на объект класса CMyClassFactory и вызывается метод AddRef, увеличивающий количество ссылок на объект класса CMyClassFactory. В противном случае возвращается код ошибки. Константы IID_IUnknown и IID_IClassFactory определяют IID предопределенных интерфейсов IUnknown и IClassFactory.

В реализации метода AddRef количество ссылок на объект данного класса увеличивается на 1 и возвращается текущее значение количества ссылок.

В реализации метода Release количество ссылок на объект данного класса уменьшается на 1 и возвращается текущее значение количества ссылок, если оно не равно 0. В противном случае вызывается деструктор объекта.

Реализация метода IClassFactory::CreateInstance создается экземпляр класса CMyClass и возвращается указатель на запрашиваемый интерфейс, реализуемый классом CmyClass. Аргумент pUnkOuter служит для поддержки агрегирования, но в данной реализации IClassFactory агрегирование не поддерживается и если pUnkOuter != NULL возвращается код ошибки.

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

Интерфейс IShellExtInit предназначен для инициализации расширений оболочки. Интерфейс определяет только один метод – Initialize, вызываемый оболочкой при инициализации расширения. Реализация метода Initialize может выглядеть следующим образом:

STDMETHODIMP CMyClass::Initialize(LPCITEMIDLIST pIDFolder,

LPDATAOBJECT pDataObj, HKEY hRegKey)

{

STGMEDIUM medium;

HRESULT hr = 0;

FORMATETC fmte = {CF_HDROP,

(DVTARGETDEVICE FAR *)NULL, DVASPECT_CONTENT,

-1, TYMED_HGLOBAL};

if (pDataObj) hr = pDataObj->GetData(&fmte, &medium);

if (SUCCEEDED(hr))

DragQueryFile((HDROP)medium.hGlobal, 0,

m_szFileName, sizeof(m_szFileName));

return hr;

}

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

Аргумент pDataObj является указателем на интерфейс IDataObject, идентифицирующий объект, для которого вызывается расширение оболочки. Объектом может быть, например, файл или папака. В данной реализации значение интерфейс IDataObject используется для получения имени файла, для которого было вызвано контекстное меню. Функция DragQueryFile используется для непосредственного получения имени файла и записи его в переменную класса m_szFileName.

DLL библиотека, содержащая реализацию COM сервера должна экспортировать две функции: DllCanUnloadNow и DllGetClassObject. Функция DllCanUnloadNow вызывается оболочкой для определения момента выгрузки DLL библиотеки из памяти. Функция DllGetClassObject вызывается оболочкой для получения указателя на интерфейс IClassFactory, реализуемый классом COM сервера. Помимо двух экспортируемых функций DLL библиотека также включает в себя функцию DllMain, вызываемую операционной системой при подключении библиотеки к процессу или потоку, а также при отключении от процесса или потока.

Реализация функци DllMain, DllCanUnloadNow и DllGetClassObject может выглядеть следующим образом:

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason,

LPVOID lpReserved)

{

if (dwReason == DLL_PROCESS_ATTACH)

{

g_hmodThisDll = hModule;

}

return TRUE;

}

STDAPI DllCanUnloadNow()

{

return (!g_cRefThisDll ? S_OK : S_FALSE);

}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)

{

*ppvOut = NULL;

if (IsEqualIID(rclsid, CLSID_MyClass))

{

CMyClassFactory *pcf = new CMyClassFactory;

return pcf->QueryInterface(riid, ppvOut);

}

return CLASS_E_CLASSNOTAVAILABLE;

}

Переменная g_hmodThisDll используется для хранения дескриптора данной DLL библиотеки.

Функция DllCanUnloadNow возвращает значение S_OK, когда количество ссылок на библиотеку равно 0 и S_FALSE в противном случае.

Функция DllGetClassObject создает экземпляр класса, реализующего методы интерфейса IClassFactory и возвращает указатель на запрошенный интерфейс. Если указанный CLSID не равен CLSID класса, реализуемого данным COM сервером, возвращается код ошибки.

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

Интерфейс определяет три метода: GetCommandString, InvokeCommand и QueryContextMenu.

Метод IContextMenu::QueryContextMenu предназначен для добавления пунктов в контекстное меню. Определение метода выглядит следующим образом:

HRESULT QueryContextMenu(

HMENU hmenu,

UINT indexMenu,

UINT idCmdFirst,

UINT idCmdLast,

UINT uFlags

);

hmenu – дескриптор контекстного меню, используется со всеми операциями добавления пунктов меню.

indexMenu – позиция, в которую можно добавить пункт меню.

idCmdFirst – минимальное значение идентификатора пункта меню, который назначается расширением оболочки.

idCmdLast – максимальное значение идентификатора пункта меню, который назначается расширением оболочки.

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

Конкретный идентификатор пункта меню должен быть в диапазоне от idCmdFirst до idCmdLast.

Ниже представлен пример реализации метода IContextMenu::QueryContextMenu.

STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,

UINT indexMenu,

UINT idCmdFirst,

UINT idCmdLast,

UINT uFlags)

{

UINT idCmd = idCmdFirst;

char szMenuText[64];

lstrcpy(szMenuText, "&Пункт меню");

InsertMenu(hMenu, indexMenu++,

MF_SEPARATOR|MF_BYPOSITION, 0, NULL);

InsertMenu(hMenu, indexMenu, MF_STRING|MF_BYPOSITION,

idCmdFirst, szMenuText);

return (HRESULT)1;

}

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

Метод IContextMenu::GetCommandString используется для получения оболочкой текстовой подсказки к добавленному ранее пункту меню. Определение метода выглядит следующим образом:

HRESULT GetCommandString(

UINT idCmd,

UINT uFlags,

UINT *pwReserved,

LPSTR pszName,

UINT cchMax

);

idCmd – смещение идентификатора пункта меню. Для первого добавленного ранее пункта меню значение idCmd равно 0, для второго – 1 и т. д. Используется для идентификации выделенного пункта меню.

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

pwReserved – зарезервированный аргумент, не должен использоваться.

pszName – адрес буфера, используемого для записи текстовой подсказки к пункту меню.

cchMax – размер буфера (в символах), указываемого аргументом pszName.

Ниже представлен пример реализации метода IContextMenu::GetCommandString.

STDMETHODIMP CShellExt::GetCommandString(UINT idCmd,

UINT uFlags,

UINT FAR *reserved,

LPSTR pszName,

UINT cchMax)

{

If (idCmd == 0) lstrcpy(pszName, "Новый пункт меню №1");

return NOERROR;

}

Метод IContextMenu::InvokeCommand вызывается оболочкой при выборе добавленного ранее пункта меню. Определение метода выглядит следующим образом:

HRESULT InvokeCommand(

LPCMINVOKECOMMANDINFO lpici

);

lpici – указатель на структуру CMINVOKECOMMANDINFO, содержащую информацию о выполняемой команде.

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

Поле hwnd структуры определяет дескриптор родительского окна контекстного меню.

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

Ниже представлен пример реализации метода IContextMenu::InvokeCommand.

STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)

{

if (!HIWORD(lpcmi->lpVerb))

{

UINT idCmd = LOWORD(lpcmi->lpVerb);

if (idCmd == 0)

MessageBox(lpcmi->hwnd, “Выбран пункт меню №1”,

“Сообщение”, MB_OK | MB_ICONINFORMATION);

}

return NOERROR;

}

Управляющий файл реестра (с расширением .REG) используется для добавления в реестр некоторой информации. Файл представляет собой обычный текстовый файл с секциями, аналогичными файлу INI. Ниже представлен пример файла MY.REG, регистрирующий в реестре COM сервер расширения оболочки для обработки контекстного меню, связанного с текстовыми файлами.

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{87b9bd00-c65c-11cd-a259-00dd010e8c28}]

@="Shell Extension Sample"

[HKEY_CLASSES_ROOT\CLSID\{87b9bd00-c65c-11cd-a259-00dd010e8c28}\InProcServer32]

@="shellext.dll"

"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\txtfile\shellex\ContextMenuHandlers]

@="MyContextMenu"

[HKEY_CLASSES_ROOT\txtfile\shellex\ContextMenuHandlers\MyContextMenu]

@="{87b9bd00-c65c-11cd-a259-00dd010e8c28}"

Сначала регистрируется COM сервер (файл shellext.dll) в разделе HKEY_CLASSES_ROOT\CLSID реестра. Затем для текстовых файлов (раздел HKEY_CLASSES_ROOT\txtfile) в подразделе ContextMenuHandlers создается подраздел MyContextMenu, в значении по умолчанию которого указывается CLSID COM сервера.

Если необходимо в раздел HKEY_CLASSES_ROOT добавить информацию о новом типе (расширении) файла, можно использовать следующую последовательность команд в управляющем файле реестра:

[HKEY_CLASSES_ROOT\.<расширение>]

@="<имя_расширения>"

[HKEY_CLASSES_ROOT\<имя_расширения>]

@="<описание расширения>"

Например, для расширения .123 используются команды:

[HKEY_CLASSES_ROOT\.123]

@="123File"

[HKEY_CLASSES_ROOT\123File]

@="123 File Extension"

Для добавления информации в реестр необходимо просто запустить .REG файл из проводника Windows.

Для реализации COM сервера рекомендуется использовать среду разработки Microsoft Visual C++.

6.4. Контрольные вопросы

  1. Для чего предназначена модель COM?

  2. Что такое интерфейс, реализация интерфейса, объект?

  3. Для чего служит интерфейс IUnknown?

  4. Какие существуют потоковые модели?

  5. Как можно создать COM объект?

  6. Какие существуют механизмы повторного использования COM объектов?

6.5. Варианты заданий на лабораторную работу

  1. Добавить в контекстное меню файлов с расширением .txt пункт «Информация о файле». При выборе пункта на экране должно отображаться сообщение о количестве строк в текстовом файле.

  2. Добавить в контекстное меню файлов с расширением .exe пункт «Выполнить». При выборе пункта должен осуществляться запуск соответствующего приложения. Для запуска приложения использовать функцию Win32 API ShellExecute.

  3. Добавить в контекстное меню файлов с расширением .doc пункт «Печать». При выборе пункта должна осуществляться распечатка соответствующего документа. Для распечатки документа использовать функцию Win32 API ShellExecute.

  4. Добавить в контекстное меню файлов с расширением .tmp пункт «Удалить». При выборе пункта соответствующий файл должен удаляться. Для удаления файла использовать функцию Win32 API DeleteFile.

  5. Добавить в контекстное меню файлов с расширением .dir пункт «Записать содержимое папки». При выборе пункта в соответствующий файл должны записываться имена всех файлов текущей папки. Для поиска файлов по шаблону использовать функции Win32 API FindFirstFile и FindNextFile.

6.6. Пример программы

Далее представлен пример программы-расширения оболочки, реализованной в виде COM сервера. Программа добавляет в контекстное меню для файлов с расширением .txt пункт «Просмотр». При выборе данного пункта на экране отображается диалоговое окно с содержимым текстового файла. Программа реализована в среде Microsoft Visual C++ 6.0.

Файл TXTView.h

#ifndef _TXTVIEW_H

#define _TXTVIEW_H

#include <windows.h>

#include <windowsx.h>

#include <shlobj.h>

extern GUID CLSID_TXTView;

extern char szFileName[MAX_PATH];

class CTXTViewClassFactory: public IClassFactory

{

protected:

ULONG m_cRef;

public:

CTXTViewClassFactory();

~CTXTViewClassFactory();

STDMETHODIMP QueryInterface(REFIID, LPVOID FAR *);

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR *);

STDMETHODIMP LockServer(BOOL);

};

class CTXTView: public IContextMenu, IShellExtInit

{

protected:

ULONG m_cRef;

public:

CTXTView();

~CTXTView();

STDMETHODIMP QueryInterface(REFIID, LPVOID FAR *);

STDMETHODIMP_(ULONG) AddRef();

STDMETHODIMP_(ULONG) Release();

STDMETHODIMP GetCommandString(UINT idCmd, UINT uFlags,

UINT *pwReserved, LPSTR pszName, UINT cchMax);

STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi);

STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu,

UINT idCmdFirst, UINT idCmdLast, UINT uFlags);

STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder,

LPDATAOBJECT pDataObj, HKEY hKeyID);

};

#endif

Файл StdAfx.h

// stdafx.h : include file for standard system include files,

// or project specific include files that are used frequently, but

// are changed infrequently

//

#if !defined(AFX_STDAFX_H__5CE57FCA_CE76_4558_AA88_2D4491EDA0D1__INCLUDED_)

#define AFX_STDAFX_H__5CE57FCA_CE76_4558_AA88_2D4491EDA0D1__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// Insert your headers here

#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers

#include <windows.h>

// TODO: reference additional headers your program requires here

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__5CE57FCA_CE76_4558_AA88_2D4491EDA0D1__INCLUDED_)

Файл TXTView.cpp

// TXTView.cpp : Defines the entry point for the DLL application.

//

#include "stdafx.h"

#include <shellapi.h>

#include "TXTView.h"

UINT g_cRefThisDll = 0;

HINSTANCE g_hmodThisDll = NULL;

GUID CLSID_TXTView = {

0xa640e00b,

0xbeb9,

0x4fa4,

{0x80, 0xd1, 0x56, 0xbd, 0x7c, 0x65, 0xec, 0x20}

};

BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD dwReason,

LPVOID lpReserved)

{

if (dwReason == DLL_PROCESS_ATTACH)

{

g_hmodThisDll = hModule;

}

return TRUE;

}

STDAPI DllCanUnloadNow()

{

return (!g_cRefThisDll ? S_OK : S_FALSE);

}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)

{

*ppvOut = NULL;

if (IsEqualIID(rclsid, CLSID_TXTView))

{

CTXTViewClassFactory *pcf = new CTXTViewClassFactory;

return pcf->QueryInterface(riid, ppvOut);

}

return CLASS_E_CLASSNOTAVAILABLE;

}

CTXTViewClassFactory::CTXTViewClassFactory()

{

m_cRef = 0L;

g_cRefThisDll++;

}

CTXTViewClassFactory::~CTXTViewClassFactory()

{

g_cRefThisDll--;

}

STDMETHODIMP CTXTViewClassFactory::QueryInterface(REFIID riid, LPVOID FAR *ppv)

{

*ppv = NULL;

if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))

{

*ppv = (LPCLASSFACTORY)this;

AddRef();

return NOERROR;

}

return E_NOINTERFACE;

}

STDMETHODIMP_(ULONG) CTXTViewClassFactory::AddRef()

{

return ++m_cRef;

}

STDMETHODIMP_(ULONG) CTXTViewClassFactory::Release()

{

if (--m_cRef) return m_cRef;

delete this;

return 0;

}

STDMETHODIMP CTXTViewClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,

REFIID riid,

LPVOID *ppvObj)

{

*ppvObj = NULL;

if (pUnkOuter) return CLASS_E_NOAGGREGATION;

CTXTView *pTXTView = new CTXTView();

if (!pTXTView) return E_OUTOFMEMORY;

return pTXTView->QueryInterface(riid, ppvObj);

}

STDMETHODIMP CTXTViewClassFactory::LockServer(BOOL fLock)

{

return NOERROR;

}

CTXTView::CTXTView()

{

m_cRef = 0L;

g_cRefThisDll++;

}

CTXTView::~CTXTView()

{

g_cRefThisDll--;

}

STDMETHODIMP CTXTView::QueryInterface(REFIID riid, LPVOID FAR *ppv)

{

*ppv = NULL;

if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))

*ppv = (IShellExtInit *)this;

else if (IsEqualIID(riid, IID_IContextMenu))

*ppv = (IContextMenu *)this;

if (*ppv)

{

AddRef();

return NOERROR;

}

return E_NOINTERFACE;

}

STDMETHODIMP_(ULONG) CTXTView::AddRef()

{

return ++m_cRef;

}

STDMETHODIMP_(ULONG) CTXTView::Release()

{

if (--m_cRef) return m_cRef;

delete this;

return 0;

}

STDMETHODIMP CTXTView::Initialize(LPCITEMIDLIST pIDFolder,

LPDATAOBJECT pDataObj, HKEY hRegKey)

{

STGMEDIUM medium;

HRESULT hr = 0;

FORMATETC fmte = {CF_HDROP, (DVTARGETDEVICE FAR *)NULL, DVASPECT_CONTENT,

-1, TYMED_HGLOBAL};

if (pDataObj) hr = pDataObj->GetData(&fmte, &medium);

if (SUCCEEDED(hr))

DragQueryFile((HDROP)medium.hGlobal, 0, szFileName, sizeof(szFileName));

return hr;

}

Файл StdAfx.cpp

// stdafx.cpp : source file that includes just the standard includes

// TXTView.pch will be the pre-compiled header

// stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

// TODO: reference any additional headers you need in STDAFX.H

// and not in this file

Файл CtxMenu.cpp

#include "stdafx.h"

#include "TXTView.h"

#include "resource.h"

#include <fstream.h>

#include <shellapi.h>

extern UINT g_cRefThisDll;

extern HINSTANCE g_hmodThisDll;

char szFileName[MAX_PATH];

BOOL CALLBACK TXTViewDlgProc(HWND, UINT, WPARAM, LPARAM);

void ReadFile(HWND hList);

STDMETHODIMP CTXTView::QueryContextMenu(HMENU hMenu, UINT indexMenu,

UINT idCmdFirst, UINT idCmdLast,

UINT uFlags)

{

UINT idCmd = idCmdFirst;

char szMenuText[64];

lstrcpy(szMenuText, "&Ïðîñìîòð");

InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);

InsertMenu(hMenu, indexMenu, MF_STRING|MF_BYPOSITION,

idCmdFirst, szMenuText);

return (HRESULT)1;

}

STDMETHODIMP CTXTView::GetCommandString(UINT idCmd, UINT uFlags,

UINT *pwReserved, LPSTR pszName,

UINT cchMax)

{

if (idCmd == 0) lstrcpy(pszName,

"Âûâîä íà ýêðàí ñîäåðæèìîãî òåêñòîâîãî ôàéëà");

return NOERROR;

}

STDMETHODIMP CTXTView::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)

{

if (!HIWORD(lpcmi->lpVerb))

{

UINT idCmd = LOWORD(lpcmi->lpVerb);

if (idCmd == 0)

DialogBox(g_hmodThisDll, MAKEINTRESOURCE(IDD_TXTVIEWDIALOG),

lpcmi->hwnd, (DLGPROC)TXTViewDlgProc);

}

return NOERROR;

}

BOOL CALLBACK TXTViewDlgProc(HWND hWnd, UINT msg,

WPARAM wParam, LPARAM lParam)

{

switch (msg)

{

case WM_INITDIALOG:

ReadFile(GetDlgItem(hWnd, IDC_LIST));

break;

case WM_CLOSE:

EndDialog(hWnd, 0);

break;

case WM_COMMAND:

if (wParam == IDOK || wParam == IDCANCEL)

EndDialog(hWnd, 0);

break;

}

return 0;

}

void ReadFile(HWND hList)

{

char buf[256];

ifstream in(szFileName);

if (!in) return;

while (!in.eof())

{

in.getline(buf, sizeof(buf));

SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)buf);

}

in.close();

}

Файл TXTView.def

;txtview.def : Declares the module parameters for the DLL.

LIBRARY TXTVIEW

DESCRIPTION 'TXTView Shell Extension'

EXPORTS

DllCanUnloadNow private

DllGetClassObject private

Файл TXTView.reg

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{a640e00b-beb9-4fa4-80d1-56bd7c65ec20}]

@="TXTView Shell Extension Sample"

[HKEY_CLASSES_ROOT\CLSID\{a640e00b-beb9-4fa4-80d1-56bd7c65ec20}\InProcServer32]

@="txtview.dll"

"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\txtfile\shellex\ContextMenuHandlers]

@="TXTMenu"

[HKEY_CLASSES_ROOT\txtfile\shellex\ContextMenuHandlers\TXTMenu]

@="{a640e00b-beb9-4fa4-80d1-56bd7c65ec20}"

1

Смотреть полностью


Скачать документ

Похожие документы:

  1. Том 2 документации об аукционе для проведения открытого аукциона для субъектов малого предпринимательства

    Инструкция
    ... с некоторыми операционными системами) - Вес ... Методическое пособие по ... пособием и предназначена для использования на учебных занятиях по химии, и другим общеобразовательным дисциплинам ... выполнения лабораторных работ по биологии» ... (лота), указанным в томе ...
  2. Ищите тесты не только по названиям

    Тесты
    ... методического) уровня системы ... работой всех устройств ПК и процессом выполнения прикладных программ, называется 3. Операционные системы ... дисциплины ... , лабораторное оборудование ... пособия по ... по достижении: 108. Выполнение налогоплательщиком письменных указаний ...
  3. Постановление исполнительного комитета г казани 30 06 2009 №5258 об утверждении стандарта муниципальной услуги «предоставление общедоступного бесплатного начального общего основного общего среднего (полного) общего образования по основным

    Программа
    ... Д Д - 1.4 Методическое пособие для учителя К К - 1.5 Рабочие тетради по физике Д Д - 1.6 Хрестоматия по физике К** К** По возможности 1.7 Комплекты пособий для выполнения лабораторных ...
  4. III ПРИРОДНЫЙ ЗАКОН ПИТАНИЯ ЧЕЛОВЕКА

    Закон
    ... операционный ... указанный срок подлежат увольнению по профессиональной непригодности. Обязательно выполнение ... системы и системы обмена веществ, нарушается работа всей пищеварительной системы ... наглядные пособия, ... “Дисциплина” ... методически ... У лабораторных мышей ...
  5. Методическое пособие по по дисциплине «теоретические основы автоматизированного управления» и указания к выполнению лабораторных работ

    Методическое пособие
    ... Методическое пособие по по дисциплине «Теоретические основы автоматизированного управления» и указания к выполнению лабораторных работ Номер специальности – 230102 «Автоматизированные системы ... и под различными операционными системами. При этом ...

Другие похожие документы..