Шрифт:
Интервал:
Закладка:
Итак, начнем! Для начала неплохо бы открыть MSDN и посмотреть словарик на этот счет – Glossary (Platform SDK: COM). Мы можем увидеть следующую вещь:
Connection point object (объект точки связи)
Это COM-объект, который управляется Connectable object и содержит реализацию IConnectionPoint интерфейса. Одна или более точек соединения объектов может быть создана и управляться Connectable object. Каждая точка соединения объекта управляет поступлением событий от специфического интерфейса к другому объекту и пересылкой этих событий к клиенту.
Теперь смотрим, что такое Connectable object:
Connectable object (соединяемый объект)
Это COM-объект, который реализует, как минимум, интерфейс IConnectionPointContainer для управления точкой соединения объектов. Соединяемые объекты поддерживают связь от сервера к клиенту. Соединяемый объект может создавать и управлять одной или более точками соединения подобъектов, которые получают события от интерфейсов реализованных в других объектах и посылают их клиентской стороне.
Ну как, все понятно? Мне не очень: – тогда читаем дальше.
Давайте вспомним знаменитый CALLBACK способ общения интерфейса API программ на языке C. Предположим, что у вас есть некая DLL, которая содержит в себе экспортируемую функцию, предназначенную для архивирования документов. Пусть у неё имеется два параметра. Первый – это путь к папке с документами, которую следует заархивировать, а второй: ну второй – это указатель на функцию. Callback-функцию – функцию обратного вызова.
FolderArchiving( LPCSTR lpszFolderPath, LOGCALLBACKFUNC *pfnLogCallbackFunc)
Где формат функции обратного вызова следующий:
typedef BOOL (LOGCALLBACKFUNC)(LPCSTR lpszDocPath, int nCurrenDoc);
Т.е. в эту функцию будет передаваться номер архивируемого документа и путь к нему.
Как же это все работает? Клиент определяет в своем приложении функцию с любым именем, строго имеющую те же параметры, что описаны выше – речь идет о функции обратного вызова, – а затем передает указатель на неё в FolderArchiving. После чего DLL начинает свою работу по архивированию сообщений и периодически, перед началом упаковки каждого документа будет вызывать ту функцию, адрес которой передал ей клиент, указывая в её параметрах номер документа и путь к нему. Таким образом, клиентское приложение получает весьма симпатичный механизм наблюдения за процессом архивирования. И при желании может вести log-файл, а также отображать диалог прогресса, если таковой не реализован в DLL. Вот, собственно, что такое CALLBACK, если объяснить на пальцах в двух словах. На рисунке 1 вы видите небольшую диаграмму, схематически поясняющую процесс работы, описанный выше.
Рисунок 1
Подобный механизм "обмена" повсеместно распространен среди функций API операционной системы. Его же часто используют разработчики в своих модулях, как в приведенном выше примере.
Теперь представим ситуацию, что та DLL, о которой шла речь, получила кроме API-интерфейса ещё и COM-интерфейс. А в вашу задачу входит реализация всех API-функций в виде COM-функций. И что же мы будем делать с нашим Callback? Вот тут на помощь и придет технология Connection point! Это фактически тот же механизм обратного вызова, только приспособленный для COM модели, естественно со своими правилами и отличиями.
Модель COM не всегда имела возможность взаимодействия с помощью исходящих интерфейсов. Было время, когда она воспринималась исключительно как модель входящих интерфейсов. В этой связи, чтобы подчеркнуть важность существования Connection points, давайте смоделируем следующую ситуацию.
Пусть у нас имеется приложение PostAgent с COM-интерфейсом. Пусть оно будет ЕХЕ-сервером и может работать как самостоятельное приложение с графическим интерфейсом. В число достоинств этой программы входит работа с архивами почтовых программ. Некоторые особенности работы с почтовыми архивами оказались настолько важны, что разработчики PostAgent вынесли их в отдельные COM функции в каком-либо интерфейсе. Замечательно! Теперь вы сможете пользоваться услугами этого приложения, и ваша программа получит тот функционал, которого, возможно, вам так не хватало все это время.
Путь среди функций этого COM сервера имеется функция MessageArchiving, которая архивирует почтовые сообщения программы Outlook фирмы Майкрософт. Архивы могут быть настолько огромными, что этот процесс подвесит ваше клиентское приложение на несколько часов (без какой-либо возможности взаимодействия с пользователем), пока функция архивирования наконец не выполнится полностью COM-сервером. Как можно бороться с этим? Первое, что приходит на ум, – это создание отдельного потока, в котором и вызовется эта "долгая" функция. Но не все тут так просто: Давайте вспомним, что для того, чтобы использовать модель COM в вашем приложении, вы должны проинициализировать её с помощью функции CoInitialize или CoInitializeEx. Думаю, первая функция нам не подходит, т. к. наверняка вы будете использовать не только MessageArchiving, но и другие методы. Если учесть, что специально для неё (MessageArchiving) мы создали отдельный поток, то логично предположить, что работа с остальными функциями сервера PostAgent будет осуществляться, как минимум, ещё из основного потока. Поэтому, чтобы из всех потоков можно было работать с одним и тем же COM-сервером, нам необходима многопоточная инициализация модели COM (MTA), с помощью CoInitializeEx. Доказал? Будем надеяться, что да.
Что дальше? У нас все работает – потоки отлично исполняются, сообщения архивируются, даже налажен четкий механизм критических секций в коде: Но вот ваш шеф сказал вам, что ваше приложение ДОЛЖНО поддерживать механизм OLE-drag-drop. Вы сталкивались с этим? Нет? Значит, ваше счастье. Почему я так говорю? А потому, что для того, чтобы работать с drag-drop, нужно инициализировать однопоточную модель COM (STA), и все тут! Дело в том, что пока нет известного мне механизма OLE-drag-drop, реализованного через MTA. Когда разрабатывалась и создавалась технология ОЛЕ, в ней вообще не было понятия MTA.
Ну и что теперь будем делать дальше? Можно попытаться объяснить шефу, что это "невозможно". Но если он не поймет этого? Тогда придется инициализировать COM для каждого потока отдельно, в каждом из них вызывать COM-сервер, вводить глобальные переменные для того, чтобы обмениваться информацией между разными функциями одного и того же COM-сервера. А можно инициализировать STA один раз, но при этом применять процедуры маршалинга и самому следить за синхронизацией потоков и вызовами COM. В общем, кошмар. Допускаю, что это несколько утрированный пример, и, возможно, его можно как-то решить способами, которых я не знаю. Однако я сомневаюсь, что эти решения дадутся без боли в голове и будут претендовать на изящность. Ну а если, ко всему прочему, я добавлю ещё и неаккуратность самих разработчиков сервера PostAgent, то вполне может произойти ситуация, когда PostAgent просто повиснет, зациклится: да что угодно, но управления вам так и не вернет. Почему вы должны страдать от этого?
Вполне логично спросить, наконец, чего ради я начал тут столь бурные излияния на искусственно придуманный пример, и какое отношение ко всему этому имеет Connection point. А вот давайте все же немного дофантазируем, раз уж вы смогли дочитать этот текст до настоящего момента и понять, о чем идет речь.
А если с самого начала разработчики PostAgent подошли бы немного с другой точки зрения к реализации своих интерфейсов? Что я имею ввиду? А вот что. Все вызовы COM являются по своей природе синхронными вызовами. Это означает, что COM-сервер не отдаст вам управление до тех пор, пока не выполнится функция, которую вы вызвали. Но с появлением Connection point получила право на жизнь и асинхронная модель функций в COM. То есть "солидные" приложения и их разработчики (не в пример разработчикам PostAgent) реализуют в своих серверах 2 интерфейса для одного и того же набора функций. Один из них является синхронным, а другой – асинхронным. Про некоторые прелести синхронных функций я вам только что рассказал. А как работают асинхронные функции?
Примерно так. Вы вызываете асинхронную функцию, реализация которой заключается в том, чтобы сохранить переданные ей параметры в некой глобальной структуре данных, затем запустить внутри COM-сервера поток, который будет выполнять действия по обработке переданных данных и вернуть управление клиенту. Вот и все! И никакой головной боли, что я описывал выше. Вы вызвали MessageArchiving, она запомнила переданные вами данные, вернула вам управление, а сама заставила PostAgent выполнять задачу в фоновом для вас режиме. Однако если идти дальше, то вам может понадобиться и механизм, с помощью которого вы смогли бы не только наблюдать за тем, что делает сейчас работающий COM-сервер, но так же и знать когда он закончит свою работу. Ведь так?! Понимаете, куда я клоню? Правильно! Сигнализировать о том, что работа функции, наконец, закончена, может специально предназначенный для этого Connection point. Кроме того, с его же помощью можно и прервать работу функции, если сделать соответствующий функционал в сервере. Но об этом позже. Таким образом, умелое сочетание синхронных и асинхронных функций даст возможность реализовать максимально эффективное взаимодействие через COM.
- Программирование - Ирина Козлова - Программирование
- Программирование — вторая грамотность - Андрей Ершов - Программирование
- 97 этюдов для архитекторов программных систем - Нил Форд - Программирование
- Платформа J2Me - Автор неизвестен - Программирование