Скачиваний:
100
Добавлен:
01.05.2014
Размер:
1.56 Mб
Скачать

Основные понятия

Что нужно, чтобы уверенно понимать о чем дальше пойдет речь? Нуно знание некоторых базовых концепций программирования. Во-первых, следует хорошо представлять себе понятия "процесс", "поток" и "адресное пространство", а также концепцию мультизадачности в операционной системе. Весьма желательно знать, как именно загружается DLLи создаётся новый процесс в системе. Требуется в какой-то достаточной мере знать концепции объектно-ориентированного программирования, а для абсолютно прозрачного понимания механизмов технологииCOMна нижнем уровне совершенно необходимо неплохо представлять себе конструкции, которые строит компиляторC++в объектном коде. Во-вторых, требуется довольно четкое представление, что есть вычислительная сеть - хотя бы на уровне локальной сети, и представление каким образом компьютеры взаимодействуют друг с другом. В-третьих - требуется понимать концепцию архитектуры "клиент-сервер".

Начнем с концепции "клиент-сервер". Это словосочетание с некоторых пор стало привычным и связалось с контекстом доступа к базам данных. Точнее, "для широкой публики" оно стало означать "клиент - сервер базы данных". Хотя на самом деле - это совсем не так. Концепция "клиент-сервер" значительно мощнее, чем принято об этом думать. Идея концепции исходит из понятия "сервиса" - некоторого действия, совершить которое зачем-то требуется сторонеAи которое она сама выполнять не умеет. Зато сторонеBсовершение этого действия не нужно, но как раз она-то и умеет его совершать. В таком случае сторонаAкаким-то образом вынуждает сторонуBсовершить это действие и предоставить сторонеАрезультат. В таком взаимодействии сторона, которая умеет совершать действие, но не имеет никакой инициативы его совершения называется "сервером", а сторона, которая состоит только из инициативы - называется "клиентом". В этом взаимодействии "клиент" запрашивает, а "сервер" предоставляет "сервис".

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

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

Понятия "адресного пространства", "процесса", "потока" для тех, кто программирует на C/C++являются одними из фундаментальных. Для программистов наVBможно сказать следующее.

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

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

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

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

Объект- это абстракция прямо отражающаяся в окружающей реальности. У любой сущности видимого мира есть состояние и поведение. Вcomputer sciencesсостояние описывается данными, а поведение - процедурами. Объединив оба понятия в одну сущность получим объект - классика, начиная уже со времен Страуструпа! Является теоремой, но сейчас доказывать уже не нужно - любая программа может быть сконструирована как совокупность взаимодействующих объектов. Вот и посмотрим, как бы могли между собой взаимодействовать части такой единой программы. Соответствующая конфигурация показана на рисунке:

Конфигурация модуля, сконструированного в ООП

Здесь имеется два взаимодействующих объекта CиSкоторые располагаются в одном исполняемом модуле, а модуль, в свою очередь, располагается в среде операционной системы. Расположение в одном модуле позволяет им "по рождению" знать друг о друге все, что знал о них компилятор. Также, здесь и ниже под термином "взаимодействиеСиS" мы имеем в виду, чтоCполучил ссылку наSи вызывает его метод.

Разделим модуль, изображённый на рис.1 на два модуля, так чтобы в каждом оказалось по одному объекту. Этим мы разорвем их взаимодействие, но не уничтожим способность объектов взаимодействовать друг с другом - ведь сами-то объекты не изменились. Сделать это можно, если один из объектов поместить в DLL, а второй оставить вEXE. Получим конфигурацию, изображённую на рисунке:

Конфигурация разделенных модулей

Рассмотрим возможный сценарий такого взаимодействия. Сразу оговоримся - сценарий "загрузки DLLпо требованию" здесь невозможен - модульMCничего не знает о модулеMS. Это мы их наименовали так только для нашего рассмотрения. Очевидно так же, что модульMC- загружен в память и выполняется, а иначе как бы объектCмог проявлять свои желания?

Итак, объект Cимеет твёрдое намерение взаимодействовать с объектомS, а объектS- такого желания совершенно не испытывает и ведет себя пассивно: разыщут - исполнит. Более того, вообще говоря,Си вовсе неизвестно - есть ли "в живых" этот самый объектS. Пока они оба "жили" в одном модуле этой проблемы не было вовсе - и адресное пространство было одно и ссылки на объект из "своего" модуля создавались еще компилятором. А сейчас... сейчас объектСзнает только, что где-то в природе должен существовать объектSи хочет его достать.

Что должен делать объект C? Что он вообще может сделать? Разумно предположить, что первое обращение последует отCк операционной системе - "я знаю, что в твоих недрах есть объектS, прошу выдать мне ссылку на него". Система в таком случае должна как-то вести учёт своего "объектного поголовья" и иметь средства отличать одни объекты от других. Без этой способности запросы такого рода не выполнить. Адрес, как характеристика объекта, здесь неприменим - адрес памяти известен только для заведомо существующих объектов, аCдаже про существованиеSничего не знает. Кроме того, система должна также знать, что объектS"живет" именно в модулеMS, а этот модуль - располагается во вполне определенном каталоге файловой системы. О том, какие модули составляют процесс, система знает и так.

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

Как "сделать объект S" система, конечно, могла бы знать. Но - сколько модулей, столько и мнений, и - все их помнить? Поэтому гораздо разумнее, если система не будет пытаться что-то сделать сама, а совершенно стандартным образом запросит модульMS- "выдать ссылку на объектS". Тут появляется уже некоторая возможность волюнтаризма со стороны модуляMS- система-то просила у него только ссылку. Как получается эта ссылка - создает ли модуль новый объектSвсякий раз, как его запрашивают, имеет ли он объектSодин на всех и всем выдает одну и ту же ссылку - дело модуляMS, а не системы.

Модуль MS"проворачивается" - он "делает объектS" в памяти, получает его адрес и возвращает этот адрес системе - "получите, что просили". Система же, зная, от кого поступил запрос, возвращает адрес тому, кто вызывал - объектуC. Поскольку объектыCиSраньше жили в одном модуле, то, как вызывать методы объектаSобъектCзнает! А ссылку он получил... Взаимодействие - состоялось.

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

Итак, сценарий возможного взаимодействия объектов СиSпотребовал некоторых специальных ресурсов:

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

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

  3. В системе должна быть специальная, доступная всем желающим функция "дать адрес "живого" объекта по его имени" и эта функция должна делать сравнительно много работы!

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

  5. Объекты должны заранее знать, каким образом они взаимодействуют между собой - система не вмешивается в этот процесс.

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

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

Вот и вопрос - не дорого ли это? Этот вопрос часто задают программисты, для которых COM- ещё одна новая среди множества уже известных им технологий. Раньше они просто описывали объекты, строили их экземпляры используя операторnew, передавали указатели-ссылки между ними и не имели дополнительных хлопот. Ради чего все это?

На этот конкретный вопрос существует и не менее конкретный ответ, правда ответ этот лежит немного в стороне от "чистого программирования". COM-технология позволяет повторно использовать "абсолютный код", код для которого все затраты - уже в прошлом. Если вы распространяете библиотеку объектных модулей вам требуется программирование, сборка, распространение новой версии всего программного продукта в целом… да еще не одного продукта, а - всех, которые используют новую версию объектной библиотеки. А вот если у вас есть двоичные компоненты, то все эти проблемы на стороне пользователя вашего продукта исчезают. Вы просто предоставляете новую версию компоненты, которую только копируете и регистрируете в системе.

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

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

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