Программирование на Visual C++. Архив рассылки
Шрифт:
Путь среди функций этого COM сервера имеется функция MessageArchiving, которая архивирует почтовые сообщения программы Outlook фирмы Майкрософт. Архивы могут быть настолько огромными, что этот процесс подвесит ваше клиентское приложение на несколько часов (без какой-либо возможности взаимодействия с пользователем), пока функция архивирования наконец не выполнится полностью COM-сервером. Как можно бороться с этим? Первое, что приходит на ум, – это создание отдельного потока, в котором и вызовется эта "долгая" функция. Но не все тут так просто: Давайте вспомним, что для того, чтобы использовать модель COM в вашем приложении, вы должны проинициализировать её с помощью функции CoInitialize или 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 выполнять задачу в фоновом для вас режиме. Однако если идти дальше, то вам может понадобиться и механизм, с помощью которого вы смогли бы не только наблюдать за
Думаю к настоящему моменту уже должно быть понятно, зачем нужны Connection points , а также хотя бы примерно, что это такое. Теперь перейдем к более детальному рассмотрению.
Connection point подразумевает под собой соединение. Соединение состоит из 2 частей: из объекта, вызывающего интерфейс и называемого источником, и из объекта, содержащего реализацию этого интерфейса и называемого приемником. Устанавливая точку соединения, источник позволяет приемнику присоединиться к себе. С помощью механизма точки соединения (интерфейс IConnectionPoint) указатель на интерфейс приемника передается объекту источника. Этот указатель обеспечивает источнику доступ к функциям-членам реализации приемника. К примеру, для возбуждения события, реализованного приемником, источник может вызвать соответствующий метод из реализации приемника.
Программа-пример Server
Итак, давайте создадим внутризадачный COM-сервер (DLL), который будет предоставлять клиенту одну-единственную функцию, результатом работы которой будет возбуждение события. Для этого запустите Visual Studio и выберите меню File->New. Затем в появившемся диалоге выберите вкладку Projects и в поле имени проекта введите ту информацию, что указана на рисунке 2, а также в качестве используемого мастера выберите MFC AppWizard (dll).
Рисунок 2
Нажмите ОК и вы увидите следующий диалог (рисунок 3). Поставьте установки, как показано на рисунке, и не забудьте отметить птичкой опцию Automation. Это важно, т.к. лишь в этом случае мастер добавит к нашему проекту код, необходимый для COM-сервера.
Рисунок 3
Нажмите Finish. Если вы сделали все правильно, то в окне ClassView вы должны увидеть ту же картину, что и на рисунке 4.
Рисунок 4
Итак, что же нам создал мастер? Он добавил минимальный джентльменский набор, который обеспечит существование скромному внутризадачному (DLL) COM-серверу. Это функции DllCanUnloadNow, DllGetClassObject и DllRegisterServer. Среди этих функций также должна находиться функция DllUnregisterServer, которая, на мой взгляд, также важна, как и DllRegisterServer, однако разработчики в Майкрософт почему-то так не считают. О том, как добавить в COM-сервер механизм unregistered, я расскажу в другой статье. Кроме того, в функцию InitInstance мастер добавил следующий код регистрации фабрики классов, за что ему и спасибо.
Пожалуй, все? Нет, не все. Если вы посмотрите в файлы проекта, созданные мастером, то увидите среди них замечательный файл описания интерфейсов PointServer.odl, содержимое которого имеет вид: