Сообщений 0    Оценка 32        Оценить  
Система Orphus

Работа СОМ-сервера в режимах Single/Multiple Instances

неcколько подробностей об IMPLEMENT_OLECREATE

Автор: Евгений Щербатов
Опубликовано: 17.07.2001
Исправлено: 13.03.2005
Версия текста: 1.0

ВВЕДЕНИЕ

Данная статья является продолжением цикла статей, посвященных разработке СОМ-серверов с помощью библиотек MFC и ATL, а также решению разнообразных проблем, с которыми вам, возможно, придется столкнуться. Здесь мы рассмотрим вопросы, связанные с использованием работы ваших серверов в режиме Single/Multiple Instances, а так же способы, которыми решается эта задача в MFC и ATL.

Итак, если предположить, что вы прочли предыдущие статьи данного цикла, что же мы уже знаем о СОМ-серверах? Знаем, что:

Это те вещи, о которых уже шла речь.

Сегодня же мы поговорим о фабриках классов СОМ-серверов (в несколько специфическом контексте). Перед тем, как стать доступными клиентским приложениям, они (фабрики классов) должны «зарегистрировать» себя. Делается это с помощью функций CoRegisterClassObject и DllGetClassObject. Первую из них могут вызывать только ЕХЕ-сервера для регистрации фабрик классов как доступных к использованию.

STDAPI CoRegisterClassObject(
  REFCLSID    rclsid,
  IUnknown    *pUnk,
  DWORD    dwClsContext,
  DWORD    flags,
  LPDWORD    lpdwRegister
);

где

Эта функция должна быть вызвана для каждой фабрики классов, содержащейся в исполняемом коде. Делаться это должно как можно раньше. В идеальном случае - до начала цикла обработки сообщений операционной системы. Повторюсь снова, что она используется только для локальных серверов, реализованных в виде ЕХЕ модуля. Для DLL модулей следует использовать DllGetClassObject:

STDAPI DllGetClassObject(
  REFCLSID rclsid,
  REFIID     riid,
  LPVOID * ppv
);

где

Как видим, для каждого типа сервера имеется своя регистрирующая функция. Однако сейчас мы займемся исключительно ЕХЕ-серверами, потому как запускаются они в отдельном адресном пространстве от клиента, и поэтому представляют для нас интерес. В частности, сосредоточим внимание на функции CoRegisterClassObject, на её четвертом параметре flags. Как было сказано выше, он может принимать следующие значения:

REGCLS_SINGLEUSE
REGCLS_MULTIPLEUSE
REGCLS_MULTI_SEPARATE
REGCLS_SUSPENDED
REGCLS_SURROGATE

В данном случае нам интересны только REGCLS_SINGLEUSE и REGCLS_MULTIPLEUSE. Разберемся, какие возможности они предоставляют:

REGCLS_SINGLEUSE - данный флаг означает, что ваш сервер может обслуживать только один компонент. Только одно клиентское приложение сможет присоединиться к вашему объекту с помощью CoGetClassObject, потому что при установке этого флага объект будет удален из области видимости так, что другие приложения не смогут к нему присоединиться. Значение этого флага используется, как правило, для SDI приложений.
REGCLS_MULTIPLEUSE - данный флаг предполагает, что множество клиентских приложений может быть присоединено к нашему СОМ-объекту.

То есть, эти флаги означают, что если СОМ-сервер реализован в виде ЕХЕ-модуля, то он может обладать такими особенностями, как запуск своей копии при каждом новом создании его СОМ-объекта (фактически при появлении нового клиента), либо обслуживание всех клиентов всего одним объектом. Довольно занимательная особенность, не правда ли? Но не только занимательная, но и необычайно полезная! Простейшими примерами являются SDI и MDI приложения. Так как SDI приложение может обслуживать только один документ (например) в данный момент, вполне логичным будет применить для него параметр REGCLS_SINGLEUSE. При этом мы получим ситуацию, когда каждый новый клиент (новый объект), который захочет открыть документ в нашем SDI приложении, будет запускать каждый раз по одному новому экземпляру СОМ-сервера. Полная противоположность данному случаю - пример MDI приложения, запуск нового экземпляра которого для каждого вновь открываемого документа есть непростительное расточительство ресурсов. Ведь MDI приложение потому так и называется, что оно в состоянии открыть внутри себя множество дочерних документов. А потому, именно здесь будет уместен флаг REGCLS_MULTIPLEUSE. Примеров на эту тему можно привести множество, не только в контексте документно-ориентированного направления SDI/MDI, но также и различные сервера мульти-обработки (REGCLS_MULTIPLEUSE) чего-либо и многое другое.

Теперь вы знаете, как управлять копиями своих ЕХЕ-серверов. Достаточно правильно использовать один из параметров функции CoRegisterClassObject. Однако, мы живем в современном мире рыночных отношений, где скорость разработки приложений играет настолько существенную роль, что мало кому придет в голову писать СОМ-обертку на «чистом С++», не пользуясь при этом библиотеками MFC или ATL (разве что только в порядке эксперимента или досконального изучения каких либо особенностей работы СОМ). Естественно, что и та, и другая библиотеки инкапсулируют в себе функцию CoRegisterClassObject, и чтобы добраться до её возможностей, иногда нужно поломать голову или просто знать, как это сделать. Именно это мы и будем рассматривать далее.

БИБЛИОТЕКА ATL

Начнем с библиотеки ATL. Здесь, к счастью для многих программистов, работа с СОМ продумана до мелочей, потому как ATL, собственно, для этого и предназначена. Иначе и быть не может. Как бы тогда она завоевала такую бешеную популярность!?

Создайте проект с использованием ATL COM AppWizard, задав ему опцию создания ЕХЕ-сервера. Вы должны получить код, который вам любезно предоставил AppWizard. Найдите в нем функцию _tWinMain - точку входа - и следующий код в ней (возможно у вас он будет отличаться с небольшими вариациями):

    if (bRun)
    {
        _Module.StartMonitor();
#if _WIN32_WINNT >= 0x0400 & defined(_ATL_FREE_THREADED)
        hRes = _Module.RegisterClassObjects(
                 CLSCTX_LOCAL_SERVER, 
                 REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED);

        _ASSERTE(SUCCEEDED(hRes));
        hRes = CoResumeClassObjects();
#else
        hRes = _Module.RegisterClassObjects(
                CLSCTX_LOCAL_SERVER, 
                REGCLS_MULTIPLEUSE);
#endif
        _ASSERTE(SUCCEEDED(hRes));

        MSG msg;
        while (GetMessage(&msg, 0, 0, 0))
            DispatchMessage(&msg);

        _Module.RevokeClassObjects();
        Sleep(dwPause); //wait for any threads to finish
    }

Обратите внимание на функцию RegisterClassObjects. Она определена в классе CComModule и перекрывает собой функцию COM CoRegisterClassObject. При желании вы можете проследить, как она это делает. Я не буду углубляться в такие подробности, но замечу, что там нет ничего сложного и сделано все довольно просто. Для нас важен тот факт, что использование такой замечательной возможности СОМ, как использование объекта СОМ в multiple/single режимах становится в ATL ещё проще, чем при использовании функции CoRegisterClassObject. Потому что _Module.RegisterClassObjects, перекрывая ее, увеличивает абстракцию настолько, что теперь нам нужно заполнять всего два её параметра (вместо четырех в функции CoRegisterClassObject). Это действительно замечательно!

Итак, сделаем небольшое резюме. Для использования возможности регистрации фабрик классов к использованию в режиме REGCLS_SINGLEUSE или REGCLS_MULTIPLUSE в ATL достаточно подставить необходимый параметр в функцию RegisterClassObjects, которая определена в классе CComModule.

БИБЛИОТЕКА MFC

А сейчас «вернемся к нашим баранам», т.е. к MFC. И посмотрим, как данная особенность СОМ реализована здесь. Я ни в коей мере не имею ничего против MFC. Это действительно мощная и универсальная библиотека для быстрой разработки приложений в Microsoft Visual Studio C++ (по крайней мере, на данный момент мы не имеем никакой другой столь же удовлетворительной альтернативы). Но за все приходится платить, и за универсальность в том числе. В нашем случае её абстрактность достигает таких высот, что когда-то для реализации подобной возможности в MFC мне пришлось порядком потратить время. И не потому, что это так сложно, а потому, что найти данную информацию нужно ещё суметь. Да и вообще, способ, которым предлагается это сделать, больше напоминает шаманство с бубном. Что поделаешь… - зато в MFC множество других преимуществ, в том числе и возможность использования ATL.

Создайте MDI проект с помощью MFC AppWizard(exe) и не забудьте отметить пункт Automation. Затем добавьте в данный проект один СОМ-интерфейс (назовем его Test). Как провести все эти манипуляции, не раз рассказывалось в предыдущих статьях. Загляните в объявления полученного класса CTest, там находится декларация

DECLARE_OLECREATE(CTest)

которая объявляет статический объект типа COleObjectFactory. А в файле реализации находится макрос

// {6633DDBD-7772-11D5-96B9-00001CDC1022}
IMPLEMENT_OLECREATE(CTest, "MDI.Test", 0x6633ddbd, 0x7772, 0x11d5, 0x96, 0xb9, 0x0, 0x0, 0x1c, 0xdc, 0x10, 0x22)

который создает этот объект, вызывая конструктор COleObjectFactory с параметром bMultiInstance установленным в FALSE.

Именно этот макрос для нас и будет представлять интерес. На данный момент, это приложение будет работать, как и положено MDI с параметром REGCLS_MULTIPLEUSE. Но давайте попытаемся изменить это и заставим его использовать флаг REGCLS_SINGLEUSE. Как это сделать?

Что означает параметр bMultiInstance в конструкторе класса COleObjectFactory? Он указывает, как будет работать приложение. Будет ли оно использовать одну свою копию в системе (пример MDI) или же будет работать в single-режиме. Если этот параметр есть TRUE, то каждый раз при создании этого объекта, СОМ будет запускать новую копию приложения. Если же он (параметр) равен FALSE (как это и сделано по умолчанию), то данного явления происходить не будет, и сервер будет иметь запущенным только один свой экземпляр. А все клиенты будут к нему присоединяться. Для нас этот параметр и есть переопределение функции CoRegisterClassObject. Для того, чтобы избежать его начальной установки в FALSE, вы не должны использовать макрос IMPLEMENT_OLECREATE. Мы определим другой макрос, очень похожий на IMPLEMENT_OLECREATE, так, как нам нужно.

Сначала посмотрим на то, как описан IMPLEMENT_OLECREATE, и выясним, где он передает в конструктор класса COleObjectFactory значение FALSE:

#define IMPLEMENT_OLECREATE(class_name, external_name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
   AFX_DATADEF COleObjectFactory class_name::factory(class_name::guid, \
   RUNTIME_CLASS(class_name), FALSE, _T(external_name)); \
   AFX_COMDAT const AFX_DATADEF GUID class_name::guid = \
   { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \

Думаю, что сейчас это ясно видно, и сомнений быть не может. Следующим шагом определим свой макрос, который назовем IMPLEMENT_OLECREATE2:

#define IMPLEMENT_OLECREATE2(class_name, external_name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
   AFX_DATADEF COleObjectFactory class_name::factory(class_name::guid, \
   RUNTIME_CLASS(class_name), TRUE, _T(external_name)); \
   AFX_COMDAT const AFX_DATADEF GUID class_name::guid = \
   { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \

Заметили, что изменилось? Только то, что мы поменяли FALSE на TRUE. Теперь вам достаточно вставить это определение в файл StdAfx.h. И везде, где вы хотите использовать флаг REGCLS_SINGLEUSE вместо REGCLS_MULTIPLEUSE, смело меняйте IMPLEMENT_OLECREATE(…) на IMPLEMENT_OLECREATE2(…) с теми же параметрами внутри скобок.

Если вы внимательно читали статью о добавлении механизма self-unregistered в приложения на базе библиотеки MFC, то должны были заметить, что для диалогового типа ЕХЕ-сервера MFC AppWizard использует эту же технологию.

Ну, вот и все. Мне остается только добавить, что, помимо макросов DECLARE_OLECREATE и IMPLEMENT_OLECREATE, существуют их расширенные версии DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX, которые мы уже использовали и рассматривали раньше. Однако там также используется по умолчанию FALSE и для получения желаемого эффекта (когда это необходимо) вам нужно проделать те же манипуляции, что и с обычным IMPLEMENT_OLECREATE. Здесь я приведу пример подобного варианта расширенного макроса для тех, у кого нет желания делать это самим:

#define IMPLEMENT_OLECREATE2_EX(class_name, external_name, \
   l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
   const TCHAR _szProgID_##class_name[] = _T(external_name); \
   AFX_DATADEF class_name::class_name##Factory class_name::factory( \
   class_name::guid, RUNTIME_CLASS(class_name), FALSE, \
   _szProgID_##class_name); \
   const AFX_DATADEF GUID class_name::guid = \
   { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \
   HRESULT class_name::GetClassID(LPCLSID pclsid) \
   { *pclsid = guid; return NOERROR; }

В следующей статье мы рассмотрим такую важную тему COM-программирования, как агрегация средствами библиотек MFC и ATL.


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