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

Добавление механизма self-unregistration в СОМ-сервера, созданные на основе библиотеки MFC

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

Часть 1. Внутризадачный DLL-сервер

Демонстрационный проект DLLServer

Данная статья посвящена реализации механизма self-unregistration в СОМ-серверах, созданных на базе библиотеки MFC. Сразу стоит оговориться, что данная возможность (просто необходимая для любого нормального СОМ-сервера), не добавляется в MFC-приложения по умолчанию. Причина, по которой это делается, мне непонятна, однако факт остается фактом. В MSDN говорится только, что "self unregistration" code is not added by default. Подобное замечание наводит на мысль, что «руководство Microsoft» считает, что СОМ-сервер, созданный с помощью MFC, является настолько «крутым», что у пользователя даже мысли не возникнет удалять его из системы. Либо данное заявление очень смахивает на то, что под выражением is not added by default следует понимать просто красивое заявление об очередном баге. Информация, предоставленная в этой статье, поможет вам быстро и без проблем добавить механизм self-unregistration в любой СОМ-сервер (MDI, SDI, Dialog based, DLL) созданный с помощью библиотеки MFC.

Итак, что же такое механизм self-unregistration? Давайте вспомним, как рождается, живет и умирает любой СОМ-сервер (ActiveX) в вашей операционной системе. Для того, чтобы СОМ-компонент мог использоваться различными СОМ-клиентами, он должен оставить о себе сведения в системном реестре операционной системы. С этого и начинается его рождение. Конечно, существует определенное множество серверов, которые могут отлично работать и просто через графический интерфейс. Яркий тому пример - Microsoft Word. Он может работать как обычное приложение и как СОМ-сервер. Однако, даже он, не оставив о себе должные сведения в реестре, не будет востребован его СОМ-клиентами, т.к. они просто не смогут его найти. Таким образом, Word потеряет приличную часть своей публики. Такое вот «рождение» носит название регистрации компонента (сервера). Информация, которую заносит в реестр СОМ-сервер, представляет собой сведения о нем самом. Как он называется, где находится, как реализован (DLL или EXE) и так далее. Естественно, что подобные данные должен предоставлять сам сервер. Поэтому в зависимости от того, как он реализован, СОМ-компонент либо предоставляет для этого специальную функцию, либо делает это при первом своем запуске. Теперь любой СОМ-клиент сможет найти информацию о нем и начать его использовать. Так начинается цикл жизни СОМ-сервера.

Однако настает такой момент, когда приложение или компонент становится ненужным, и его просто удаляют. Но физическое удаление исполняемого файла какого-либо СОМ-сервера не есть достаточная процедура. Ведь в системном реестре останется о нем информация, которую сервер занес туда при своей регистрации. И если её не удалить оттуда, то возможны как минимум два неприятных последствия. Во-первых, наличие одной только информации о СОМ-сервере (пусть даже уже несуществующего) может ввести в «заблуждение» некоторых его СОМ-клиентов. А второе последствие самое прозаичное… Это элементарное захламление системного реестра и, как следствие, увеличение его размеров, а значит и увеличение времени выборки информации из него. Конечно, при современных скоростях жестких дисков это незаметно, однако сам факт того, что ваш реестр может превратиться в большую мусорную свалку, разве не доставляет вам неудобства? Поэтому удаление СОМ-сервера - это не просто физическое удаление его файла, но также и уничтожение той информации, которую он заносил в реестр. Для этих целей и существует так называемый механизм самоудаления - self-unregistration. Т.е. перед тем, как удалить какой-либо СОМ-компонент, программа uninstall (или вы сами) вызывает, например, специальную функцию в СОМ-сервере, которая уничтожает нужную информацию в реестре. Теперь вы можете подвергнуть компонент физическому удалению. Вот на этой ноте и заканчивается цикл существования любого СОМ-сервера.

Реализацию механизма самоудаления начнем с DLL-компонента. Для этого нам нужно его создать.

ПРИМЕЧАНИЕ
Ввиду того, что реализацию self-unregistration мы будем делать для Dll, SDI, MDI и Dialog-based серверов, то приведенную ниже информацию можно также использовать (в какой-то мере) как руководство к созданию СОМ-серверов на базе вышеперечисленных технологий.

Запустите Microsoft Visual Studio и выберите пункт меню File->New. Затем в появившемся диалоговом окне сделайте активной вкладку Projects и введите необходимую информацию, как это сделано на рисунке 1. Т.е. укажите, что вы будете использовать MFC AppWizard (dll), имя проекта DLLServer, и поместите его в папочку Unregister (здесь будут находиться также все остальные сервера, которые мы создадим).


Рисунок 1

Нажмите ОК. Сейчас должен был запуститься мастер создания DLL. Оставьте все настройки по умолчанию и дополнительно отметьте пункт Automation (рисунок 2), после чего жмите Finish.


Рисунок 2

Вы получили проект, в который мастер добавил несколько необходимых вещей для существования нашего внутризадачного сервера. Если вы делали все, как было описано выше, то должны были получить картинку, как на рисунке 3.


Рисунок 3

У нас имеется три важные функции. Это DllCanUnloadNow, которая периодически вызывается СОМ для проверки возможности выгрузки DLL, DllGetClassObject, которая является точкой входа и используется для предоставления интерфейсов фабрик классов клиентам, и DllRegisterServer - функция, с помощью которой наш СОМ-сервер будет регистрировать себя в системном реестре. Для полноты картины не хватает только функции DllUnregisterServer, которая будет удалять из реестра нужную информацию. Вот её реализацией мы сейчас и займемся.

Давайте для полноты примера создадим в нашем сервере пару интерфейсов. Для этого выберите пункт меню View->ClassWizard и в появившемся диалоге выберите вкладку Automation. Нажав AddClass->New создайте новый класс, который будет отвечать за один из наших интерфейсов (см. рисунок 4). Назовем класс Interface1 и сделаем его потомком CCmdTarget (подобное наследование является необходимой вещью, так как именно этот класс в иерархии MFC предоставляет необходимую нам поддержку COM). Не забудьте отметить пункт Createble by type ID, где можно указать удобный вам ProgId вашего интерфейса. В данном случае, я оставил его таким, как это было предложено по умолчанию.


Рисунок 4

Нажмите ОК и создайте ещё один класс (точно так же) с именем Interface2. Нажмите ОК в ClassWizard и посмотрите, что получилось (рисунок 5). Мастер заполнил за нас файл с определениями нашего СОМ-объекта (DllServer.odl). В результате чего мы получили два интерфейса: Interface1 и Interface2. Также было добавлено два класса Interface1 и Interface2 (по имени соответствующих интерфейсов), которые будут содержать реализации функций, объявленных в СОМ-интерфейсах.


Рисунок 5

Следующим шагом постройте созданный проект и зарегистрируйте полученную DLL. Для этого можно воспользоваться утилитой regsvr32.exe. Она поставляется вместе с операционной системой, а потому должна иметься на вашей машине. Regsvr32 должна загрузить наш СОМ-сервер и вызвать в нем функцию DllRegisterServer, которая внесет нужные изменения в реестр. Если до настоящего момента вы делали все согласно указаниям, то проблем с регистрацией возникнуть не должно.

Далее посмотрим, какие следы пребывания оставил наш сервер в реестре. Для этого запустите regedit.exe, откройте ветку HKEY_CLASSES_ROOT и выполните поиск по тому ProgId, который мы указали, когда создавали интерфейсы (см. рисунок 6).


Рисунок 6

В каждом ProgId нашего интерфейса находится его CLSID. У меня это {035FC7F4-3955-11D5-9669-00001CDC1022} для DLLServer.Interface1 и {035FC7F7-3955-11D5-9669-00001CDC1022} для DLLServer.Interface2. Ваши CLSID должны быть отличными от моих. Теперь в той же ветке HKEY_CLASSES_ROOT найдите пункт CLSID и откройте его (см. рисунок 7).


Рисунок 7

Здесь находятся CLSID тех компонентов, которые зарегистрированы в вашей операционной системе. Найдите среди них те, что были указаны в ProgId (которые мы искали пунктом выше). Не забудьте, что вам нужно искать свои CLSID. На рисунке 8 показаны результаты моего поиска.


Рисунок 8

Как видно, каждый из найденных мной CLSID содержит пункты InProcServer32 (в котором указывается физическое местонахождение нашей DLL) и ProgID (в котором находится имя ProgId нашего интерфейса, которое было указано на этапе создания интерфейса в мастере). Само наличие пункта InProcServer32 говорит о том, что данный СОМ-сервер реализован как внутризадачная DLL. Вот эта информация была добавлена функцией DllRegisterServer нашего компонента.

Предположим, что мы хотим оказаться от использования построенного СОМ-сервера. Для этого (перед тем как удалить его) вызовем regsvr32.exe с ключом /u указав ей путь к DLLServer.dll. И подобная попытка завершится неудачей. Посмотрите на рисунок 9.


Рисунок 9

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

Обратите внимание как реализованы функции DllCanUnloadNow, DllGetClassObject, DllRegisterServer:

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    return AfxDllGetClassObject(rclsid, riid, ppv);
}

STDAPI DllCanUnloadNow(void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    return AfxDllCanUnloadNow();
}

// by exporting DllRegisterServer, you can use regsvr.exe
STDAPI DllRegisterServer(void)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    COleObjectFactory::UpdateRegistryAll();
    return S_OK;
}

Все они используют класс COleObjectFactory (если посмотреть на реализацию функций AfxDllGetClassObject и AfxDllCanUnloadNow (файл OLEDLL.cpp), то можно убедиться, что там также применяется этот класс). Что делает функция UpdateRegistryAll, которая используется в явном виде в DllRegisterServer? Согласно описанию она регистрирует все объекты фабрик приложения в реестре и имеет передаваемый параметр, который в документации не описан. Этот параметр имеет тип BOOL и по умолчанию устанавливается в TRUE. В этом случае данная функция регистрирует СОМ-сервер, в противном - нет. Мы будем использовать её для удаления информации из реестра, но с параметром FALSE (к алгоритму работы этой функции мы вернемся позже).

Сразу за реализацией DllRegisterServer добавьте следующий код:

// GUID библиотеки типов, продекларированный в файле 
// DLLServer.odl как uuid(035FC7E6-3955-11D5-9669-00001CDC1022)
const GUID CDECL BASED_CODE g_GUIDTypeLib =
{ 0x035FC7E6, 0x3955, 0x11D5,
{ 0x96, 0x69, 0x0, 0x0, 0x1C, 0xDC, 0x10, 0x22 } };

// Мажорный номер версии библиотеки типов. В данном случае 
// версия библиотеки типов установлена как version(1.0). 
// Мажорный номер находится слева от десятичной точки и равен 1
const WORD g_wVerMajor = 1;

// Минорный номер версии библиотеки типов. В данном случае 
// версия библиотеки типов установлена как version(1.0). 
// Минорный номер находится справа от десятичной точки и равен 0
const WORD g_wVerMinor = 0;

// Функция DllUnregisterServer. Позволяет удалить информацию
// о данном СОМ-сервере из реестра
STDAPI DllUnregisterServer(void)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState());

 // Успешный вызов данной функции приведет к удалению
 // информации о библиотеки типов данного СОМ-сервера
 // из реестра операционной системы
  if (!AfxOleUnregisterTypeLib(g_GUIDTypeLib, g_wVerMajor, g_wVerMinor))
    return ResultFromScode(SELFREG_E_TYPELIB);

 // Обновить информацию
  if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
    return ResultFromScode(SELFREG_E_CLASS);

 return S_OK;
}

Как видно из приведенного выше кода, удаление информации из реестра сводится к вызову функции AfxOleUnregisterTypeLib и последующего UpdateRegistryAll. Функция AfxOleUnregisterTypeLib в качестве параметров принимает UUID удаляемой библиотеки типов и её версию. Данную информацию можно взять из ODL файла нашего сервера, как это указанно в комментариях к коду. Будьте внимательны, записывая свой GUID библиотеки типов, так как вам придется его самостоятельно перевести из той формы записи, в которой он объявлен в DLLServer.odl в ту, которая используется в данном коде. Ну вот, видите, все просто. Но здесь используется не класс COleObjectFactory, а COleObjectFactoryEx. Это что-то значит? Для нас нет. Вот объявление этого класса:

// Класс COleObjectFactoryEx определен для совместимости со старым CDK
#define COleObjectFactoryEx COleObjectFactory

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

; DLLServer.def : Declares the module parameters for the DLL.

LIBRARY      "DLLServer"
DESCRIPTION  'DLLServer Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can go here
    DllCanUnloadNow PRIVATE
    DllGetClassObject PRIVATE
    DllRegisterServer PRIVATE
    DllUnregisterServer PRIVATE

Сейчас обратимся к функции UpdateRegisryAll и посмотрим, как она работает:

BOOL PASCAL COleObjectFactory::UpdateRegistryAll(BOOL bRegister)
{
    AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
    AfxLockGlobals(CRIT_OBJECTFACTORYLIST);
    for (COleObjectFactory* pFactory = pModuleState->m_factoryList;
        pFactory != NULL; pFactory = pFactory->m_pNextFactory)
    {
        if (!pFactory->UpdateRegistry(bRegister))
        {
            AfxUnlockGlobals(CRIT_OBJECTFACTORYLIST);
            return FALSE;
        }
    }
    AfxUnlockGlobals(CRIT_OBJECTFACTORYLIST);
#ifdef _AFXDLL
    AfxLockGlobals(CRIT_DYNLINKLIST);
    // register extension DLL factories
    for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL;
        pDLL = pDLL->m_pNextDLL)
    {
        for (pFactory = pDLL->m_factoryList;
            pFactory != NULL; pFactory = pFactory->m_pNextFactory)
        {
            if (!pFactory->UpdateRegistry(bRegister))
            {
                AfxUnlockGlobals(CRIT_DYNLINKLIST);
                return FALSE;
            }
        }
    }
    AfxUnlockGlobals(CRIT_DYNLINKLIST);
#endif

    return TRUE;
}

Если отбросить всю мишуру этой функции, то для нас представляет интерес только 2 её цикла. Особенно второй. Как написано в комментариях, он предназначен для регистрации фабрик классов именно для DLL-сервера (а мы сейчас работаем как раз с ним). Как работает этот цикл? В переменную pFactory заносится указатель на фабрику классов каждого из наших интерфейсов, а затем для него вызывается функция UpdateRegistry. Т.е. алгоритм работы этого цикла, как и функции UpdateRegistryAll, сводится к тому, чтобы вызвать функцию UpdateRegisry для каждого интерфейса - рисунок 10.


Рисунок 10

Теперь обратимся к коду функции UpdateRegistry. Дело в том, что здесь применена перегрузка функций С++ и для UpdateRegistry существует две декларации:

void COleObjectFactory::UpdateRegistry(LPCTSTR lpszProgID)

…

BOOL COleObjectFactory::UpdateRegistry(BOOL bRegister)

Однако из кода UpdateRegistryAll вызывается именно булевский вариант, поэтому мы и обратимся к её коду:

BOOL COleObjectFactory::UpdateRegistry(BOOL bRegister)
{
    if (bRegister)
        UpdateRegistry();   // will register with default m_lpszProgID

    return TRUE;
}

Как видите, все до безобразия просто! Если передан параметр TRUE, то вызывается UpdateRegistry(LPCTSTR lpszProgID) (с параметром по умолчанию) и происходит регистрация. Если же в качестве параметра передано значение FALSE, то просто ничего не происходит. А по идее должен происходить обратный процесс. Вот его мы сейчас и реализуем.

Фактически, регистрация в функции UpdateRegistry (LPCTSTR lpszProgID) происходит с помощью AfxOleRegisterServerClass. Для обратного процесса нужно применять функцию AfxOleUnregisterClass. Мы не будем изменять исходный код библиотеки MFC, а просто переопределим функцию UpdateRegistry в каждом из классов наших интерфейсов, вставив в неё нужный нам код. Для этого необходимо заменить макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE (здесь не будет рассматриваться устройство и назначение этих очень важных макросов - это тема следующей статьи) на их расширенные версии DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX, а также вставить свои реализации функции UpdateRegistry.

Теперь, давайте по порядку. Откройте файл StdAfx.h и найдите в нем строку #include <afxdisp.h>. Затем сразу перед ним добавьте ещё одно включение #include <afxctl.h> - здесь находятся нужные нам определения, которые мы будем использовать далее. Сохраните и закройте StdAfx.h. Далее откройте файл объявления класса Interface1 (Interface1.h) и найдите в нем макрос DECLARE_OLECREATE:

DECLARE_OLECREATE(Interface1)

Замените его на DECLARE_OLECREATE_EX:

DECLARE_OLECREATE_EX(Interface1)

Сохраните Interface1.h и закройте его. Теперь откройте файл реализации класса Interface1(Interface1.cpp) и найдите в нем макрос IMPLEMENT_OLECREATE:

// {035FC7F4-3955-11D5-9669-00001CDC1022}
IMPLEMENT_OLECREATE(Interface1, "DLLServer.Interface1", 0x35fc7f4, 0x3955, 0x11d5, 0x96, 0x69, 0x0, 0x0, 0x1c, 0xdc, 0x10, 0x22)

Замените его на IMPLEMENT_OLECREATE_EX:

// {035FC7F4-3955-11D5-9669-00001CDC1022}
IMPLEMENT_OLECREATE_EX(Interface1, "DLLServer.Interface1", 0x35fc7f4, 0x3955, 0x11d5, 0x96, 0x69, 0x0, 0x0, 0x1c, 0xdc, 0x10, 0x22)

Затем, сразу после данного макроса добавьте код функции UpdateRegistry:

BOOL Interface1::Interface1Factory::UpdateRegistry(BOOL bRegister)
{
    if (bRegister)
        return AfxOleRegisterServerClass(m_clsid,
                            m_lpszProgID,
                            m_lpszProgID,
                            m_lpszProgID,
                            OAT_DISPATCH_OBJECT);
    else
        return AfxOleUnregisterClass(m_clsid,
                            m_lpszProgID);
}

Код именно этой функции теперь будет вызываться из функции UpdateRegistryAll во время регистрации нашего СОМ-сервера и во время его удаления. Здесь мы используем переменную bRegister по своему прямому назначению. Теперь проделайте аналогичные манипуляции с классом Interface2 и не забудьте правильно, по аналогии, сформировать определение функции UpdateRegistry:

BOOL Interface2::Interface2Factory::UpdateRegistry(BOOL bRegister)

На этом все. Сохраните все сделанные вами изменения и постройте проект. Затем точно так же, как мы это делали раньше, вызовите утилиту regsvr32.exe с параметром /u и попробуйте удалить информацию о нашем СОМ-сервере из реестра. У вас должен быть тот же результат, что и на рисунке 11.


Рисунок 11

Итак, резюме. Для того, чтобы добавить механизм self-unregistration в СОМ-сервер, созданный с помощью библиотеки MFC в качестве DLL необходимо:

  1. Добавить глобальную функцию DllUnregisterServer в тот же cpp-файл, где находятся функции типа DllRegisterServer и др. В общем случае это файл <Project_name>.cpp. (Реализацию функции DllUnregisterServer смотрите в теле статьи.)
  2. Добавить функцию DllUnregisterServer в def-файл вашего проекта.
  3. Включить в проект (в файле SdtAfx.h) файл afxctl.h
  4. Во всех классах интерфейсов (производных от CCmdTarget) найти макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE и заменить их на DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX.
  5. Во всех классах интерфейсов добавить реализацию функции UpdateRegistry. (Реализацию этой функции смотрите в теле статьи).

Часть 2. SDI и MDI сервера

Демонстрационный проект SDIServer

Демонстрационный проект MDIServer

Сейчас мы добавим поддержку механизма самоудаления для SDI и MDI серверов. Однако подробно рассматривать я буду только SDI сервер (файлы MDI-сервера вы можете загрузить к себе и посмотреть их, они прилагаются к данной статье). Дело в том, что по той части COM, с которой мы сейчас работаем, они различаются весьма незначительно, фактически вообще не различаются. Их единственным отличием является реализация функции InitInstance, да и то, только потому, что разработчики мастера посчитали, что так нужно. Я объясню, в чем заключаются их отличия в работе через СОМ, а вы сами решите, оставить ли код, предложенный мастером, или изменить его по своему усмотрению.

SDI, MDI, Dialog based - это все MFC-ориентированная классификация СОМ-серверов. На самом деле существует разделение только на DLL и EXE сервера. А SDI и все прочие отлично туда вписываются, ведь в конечном счете все они - исполняемые EXE файлы. Если вы внимательно читали первую часть данной статьи, то должны были усвоить, что регистрация DLL сервера происходит с помощью функции DllRegisterServer, а удаление из реестра - функцией DllUnregisterServer. Но эти функции предназначены для использования исключительно в DLL. Как же реализуется подобный механизм для ЕХЕ-серверов?

Ответ, как всегда, прост. Это старая добрая командная строка. С её помощью передаются ключевые слова, которые анализируются при запуске приложения. В результате выполняется то или иное действие. Для регистрации ЕХЕ-сервера, ему нужно передать команду /regserver, а для удаления - /unregserver. Т.е. сама программа должна проверять передачу ей этих параметров и делать разбор командной строки. Но стоит отметить тот факт, что этому предписанию не всегда следуют. Например, ЕХЕ-сервер, написанный на ATL, как правило, всегда проверяет присутствие параметра /regserver, в то время, как в MFC редко кто делает такой анализ. В серверах от MFC общепринятой практикой является обязательная регистрация ЕХЕ, при каждом его запуске в функции InitInstance. Таким образом, MFC-сервер достаточно лишь однажды запустить, чтобы он зарегистрировался. Т.е. в данном вопросе действует правило «кто во что горазд». На самом деле способ регистрации зависит от того, где и как будет использоваться ваш сервер. Я бы порекомендовал вам написать универсальную процедуру, которая могла бы осуществлять все возможные способы занесения информации о вашем сервере в реестр. Что же касается способа удаления записей из него, то тут обе технологии (MFC и ATL) сошлись во мнении. Они обе используют анализ команды /unregserver. В MFC для этого есть даже специальная константа в классе CCommandLineInfo:

enum { FileNew,
 FileOpen,
 FilePrint,
 FilePrintTo,
 FileDDE,
 AppUnregister,
 FileNothing = -1 }m_nShellCommand;

Итак, создайте проект SDI сервера, задав начальные установки, как показано на рисунке 12.


Рисунок 12

Затем на первом шаге появившегося мастера выберите пункт, как показано на рисунке 13.


Рисунок 13

На третьем шаге мастера поставьте настройки, как показано на рисунке 14 и нажмите Finish.


Рисунок 14

Давайте теперь посмотрим на код функции InitInstance, который создал мастер для SDI и MDI приложений (я приведу только тот код, который имеет для нас значение, отфильтровав остальной):

/////////////////////////////////
// CSDIServerApp initialization

BOOL CSDIServerApp::InitInstance()
{
...
// Parse command line for standard shell commands,
// DDE, file open 
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Check to see if launched as OLE server
if (cmdInfo.m_bRunEmbedded ||
        cmdInfo.m_bRunAutomated)
{
   // Register all OLE server (factories) as 
   // running. This enables the OLE libraries
  // to create objects from other applications.
   COleTemplateServer::RegisterAll();

   // Application was run with /Embedding or 
   // /Automation. Don't show the main window
   // in this case.
   return TRUE;
}

// When a server application is launched stand-
// alone, it is  a good idea to update the system 
// registry in case it has been damaged.
m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
COleObjectFactory::UpdateRegistryAll();

...

// The one and only window has been initialized, so 
// show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

return TRUE;
}






////////////////////////////////
// CMDIServerApp initialization

BOOL CMDIServerApp::InitInstance()
{
...
// Register all OLE server factories as running.  
// This enables the OLE libraries to create objects 
// from other applications.
COleTemplateServer::RegisterAll();
// Note: MDI applications register all server 
// objects without regard to the /Embedding
// or /Automation on the command line.

...

// Parse command line for standard shell commands, 
// DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Check to see if launched as OLE server
if (cmdInfo.m_bRunEmbedded ||
    cmdInfo.m_bRunAutomated)
{
   // Application was run with /Embedding
 // or /Automation. Don't show the main window
   // in this case.
   return TRUE;
}

// When a server application is launched
// stand-alone, it is a good idea to update
// the system registry in case it has been
// damaged.
m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
COleObjectFactory::UpdateRegistryAll();

...

// The main window has been initialized, so
// show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();

return TRUE;
}

SDI:
Итак, что мы имеем? Сначала происходит разбор командной строки (ParseCommandInfo), переданной нашему приложению. Если сервер запущен как OLE сервер (в командной строке передана команда /Automation) или как внедренный OLE элемент (в командной строке передана команда /Embedding), то будут установлены соответствующие флаги (m_bRunEmbedded или m_bRunAutomated). Другими словами, эти флаги будут установлены тогда, когда приложение запущено не пользователем (например, с помощью щелчка мыши), а программой-клиентом COM. Как посчитал мастер, установка этих флагов есть повод зарегистрировать фабрики классов нашего СОМ-сервера(RegisterAll) и завершить выполнение функции. При этом не будет выполнен тот участок кода, где стоит вызов функции ShowWindow. Таким образом, обеспечивается скрытый режим работы приложения. Т.е. запуск СОМ-сервера предполагает его работу в «фоновом» режиме, невидимом для пользователя.

Если же ни один из флагов (m_bRunEmbedded или m_bRunAutomated) не установлен, то это означает, что приложение запущено, например, пользователем. В этом случае (учитывая тот факт, что приложение может быть запущенно первый раз и нуждается в регистрации) происходит вызов функции UpdateRegistryAll, которая регистрирует наш СОМ-сервер в реестре. Далее идет вызов функции ShowWindow и программа готова к работе. Нетрудно заметить, что при подобной логике работы программы приложение, запущенное пользователем, не сможет являться СОМ-сервером (так как регистрация фабрик классов происходит исключительно при установленных флагах). Имейте это в виду и меняйте подобную логику работы так, как того требует постановка вашей задачи - не полагайтесь на мастера.

MDI:
Если сейчас вы внимательно посмотрите на код функции InitInstance MDI приложения, то сразу поймете, в чем его главное отличие от SDI. Конечно! Мастер поставил вызов функции, которая регистрирует фабрики классов, независимым от значения флагов m_bRunEmbedded или m_bRunAutomated. Таким образом, MDI приложение будет являться СОМ сервером даже в том случае, когда оно запущено пользователем. Я повторюсь ещё раз, не полагайтесь на мастера и варьируйте поведение приложения, так как вы хотите.

Добавим в наш сервер пару интерфейсов Interface1 и Interface2 так же, как мы это делали в первой части для DLL-сервера. Теперь займемся функцией InitInstance. Найдите место, где в ней стоят строки регистрации нашего приложения:

m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
COleObjectFactory::UpdateRegistryAll();

И замените их на следующую конструкцию, которая позволит нам отличить, что нужно сейчас делать - регистрировать или удалять:

//Если в командной строке передан ключ /unregister...
if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister)
{
    //Во время un-регистрации мы вызываем
    //UpdateRegistry(OAT_DISPATCH_OBJECT)
    //для установки флага m_bOAT(члена класса COleTemplateServer),
    //для правильного типа un-регистрации OLE-объекта,
    //поддерживающего наследование от CDocument. Этот процесс
    //будет завершен в ProcessShellCommand.
     m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);

    //Un-регистрация всех COM объектов, наследованных от CCmdTarget.
     COleObjectFactory::UpdateRegistryAll(FALSE);
}
else
{
     m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
     COleObjectFactory::UpdateRegistryAll();
}

Как видно, алгоритм весьма простой - проанализировать присутствие флага AppUnregister и, в зависимости от этого, выполнить то или иное действие. То же самое нужно добавить и в InitInstance MDI приложения. Теперь вы должны выполнить те же действия, что мы делали при «исправлении» DLL-сервера, а именно:

  1. Включить в проект (в файле SdtAfx.h) файл afxctl.h
  2. Во всех классах интерфейсов (производных от CCmdTarget) найти макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE и заменить их на DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX.
  3. Во всех классах интерфейсов добавить реализацию функции UpdateRegistry. (Реализацию этой функции смотрите в теле статьи).

Думаю сейчас все это вы в состоянии сделать уже сами. Следующим шагом постройте проект и запустите его на выполнение. В результате наш SDI(MDI) сервер должен выполнить ту веточку алгоритма, в которой происходит его регистрация в реестре. Если вы все сделали правильно, то в вашей системной базе данных должна появиться информация, аналогичная той, что показана на рисунках 15 и 16.

SDI MDI



Рисунок 15
SDI MDI



Рисунок 16

Теперь давайте запустим наш сервер из командной строки, передав ему ключ /unregister. При желании можете выполнить трассировку кода, чтобы убедиться, что все выполняется, именно так, как я говорю. Я же довольствуюсь тем, что просто проверю мой реестр и увижу, что оттуда исчезла информация, показанная на рисунках 15 и 16. Если вы делали все правильно, то именно так и должно все быть.

Подведем итоги 2 части. Для того, чтобы добавить механизм самоудаления информации о СОМ-сервере (EXE - реализация), построенного на базе библиотеки MFC с использованием технологии MDI или SDI, необходимо:

  1. В функции InitInstance изменить ту часть, которая отвечает, за регистрацию СОМ-сервера. А именно, добавить анализ ключа /unregister (пример реализации смотрите во 2 части данной статьи)
  2. Включить в проект (в файле SdtAfx.h) файл afxctl.h
  3. Во всех классах интерфейсов (производных от CCmdTarget) найти макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE и заменить их на DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX.
  4. Во всех классах интерфейсов добавить реализацию функции UpdateRegistry. (Реализацию этой функции смотрите в 1 части данной статьи).

Часть 3. Dialog based сервер

Демонстрационный проект DIALOGServer

Вот мы и подошли к третьей, заключительной части этой статьи. Здесь мы рассмотрим особенности работы с EXE-сервером, созданным на базе диалогового приложения. Не будем забывать, что это все тот же ЕХЕ-сервер, а значит и он подчиняется общим принципам регистрации и удаления, только мастер реализует эти возможности несколько отлично от SDI/MDI.

Создайте новый проект с установками, показанными на рисунке 17.


Рисунок 17

Нажмите ОК и на первом шаге мастера выберите Dialog based технологию - рисунок 18.


Рисунок 18

Затем на втором шаге выберите поддержку автоматизации и нажмите Finish - рисунок 19.


Рисунок 19

Смотрим код, который находится в InitInstance:

...
// Parse the command line to see if launched as OLE server
if (RunEmbedded() || RunAutomated())
{
    // Register all OLE server (factories) as running.  This enables the
    //  OLE libraries to create objects from other applications.
    COleTemplateServer::RegisterAll();
}
else
{
    // When a server application is launched stand-alone, it is a good idea
    //  to update the system registry in case it has been damaged.
    COleObjectFactory::UpdateRegistryAll();
}
...

Итак, что же мы имеем? Практически то же, что и раньше, только в сильно упрощенной форме. Никакого разбора командной строки и прочих прелестей. Давайте изменим этот недостаток. Замените указанный выше кусочек кода на следующий:

// Работаем с командной строкой.
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

// Если приложение запущено, как OLE сервер...
if (RunEmbedded() || RunAutomated())
{
// ... то выполнить регистрацию его фабрик классов
    COleTemplateServer::RegisterAll();
}
else
{
    //Если был передан ключ /unregister...
    if (cmdInfo.m_nShellCommand == CCommandLineInfo::AppUnregister)
    {
        // ..то выполнить удаление информации из реестра
        COleObjectFactory::UpdateRegistryAll(FALSE);
    }
    else
    {
        // в противном случае занести информацию в реестр
        COleObjectFactory::UpdateRegistryAll();
    } 
}

Здесь все понятно. Этот кусок кода мы писали в той или иной форме, начиная с самого начала статьи, и он должен быть вам уже знаком. В данном случае он выглядит чуть-чуть иначе…

Теперь выполните остальную рутинную работу. Добавьте два класса Interface1 и Interface2, замените в них нужные макросы на их EX расширение и добавьте функцию UpdateRegistry…

Сейчас обратите внимание на то, что в вашем проекте присутствует класс CDIALOGServerDlgAutoProxy, который также имеет макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE2. С DECLARE_OLECREATE все понятно - смело меняйте его на EX собрата. Но откуда взялся IMPLEMENT_OLECREATE2? Он определен в файле StdAfx.h. Не будем заострять сейчас внимание на этом факте - это тема следующей статьи. Просто давайте определим ниже IMPLEMENT_OLECREATE2 (в StdAfx.h) свой расширенный макрос:

#ifndef IMPLEMENT_OLECREATE2_EX
#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), TRUE, \
        _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; }
#endif // IMPLEMENT_OLECREATE2_EX

Сохраните и закройте файл StdAfx.h и откройте файл реализации класса CDIALOGServerDlgAutoProxy - DIALOGServerDlgAutoProxy.cpp. Здесь поменяйте IMPLEMENT_OLECREATE2 на IMPLEMENT_OLECREATE2_EX и добавьте функцию UpdateRegistry:

BOOL CDIALOGServerDlgAutoProxy::CDIALOGServerDlgAutoProxyFactory::UpdateRegistry(BOOL bRegister)
{
    if (bRegister)
        return AfxOleRegisterServerClass(m_clsid,
                            m_lpszProgID,
                            m_lpszProgID,
                            m_lpszProgID,
                            OAT_DISPATCH_OBJECT);
    else
        return AfxOleUnregisterClass(m_clsid,
                            m_lpszProgID);
}

На этом все. Постройте проект и запустите его на выполнение. После чего проверьте свой реестр. Вы должны обнаружить в нем информацию, аналогичную моей - рисунок 20, 21.


Рисунок 20


Рисунок 21

Теперь запустите наш сервер через командную строку с ключом /unregister и убедитесь, что вся информация была корректно удалена.

Сделаем очередное, на этот раз последнее, резюме. Для добавления механизма само удаления информации из реестра о СОМ-объекте, реализованного как ЕХЕ сервер, с помощью MFC библиотеки по технологии Dialog based необходимо:

  1. В функции InitInstance изменить ту часть, которая отвечает, за регистрацию СОМ-сервера. А именно, добавить анализ ключа /unregister (пример реализации смотрите в 3 части данной статьи)
  2. Включить в проект (в файле SdtAfx.h) файл afxctl.h
  3. Во всех классах интерфейсов (производных от CCmdTarget) найти макросы DECLARE_OLECREATE и IMPLEMENT_OLECREATE и заменить их на DECLARE_OLECREATE_EX и IMPLEMENT_OLECREATE_EX.
  4. Найти в файле StdAfx.h объявление макроса IMPLEMENT_OLECREATE2 и заменить его на IMPLEMENT_OLECREATE2_EX (тело этого макроса смотрите в 3 части данной статьи).
  5. В классе C<Project name>DlgAutoProxy заменить макрос DECLARE_OLECREATE на DECLARE_OLECREATE_EX и макрос IMPLEMENT_OLECREATE2 на IMPLEMENT_OLECREATE2_EX.
  6. Во всех классах интерфейсов добавить реализацию функции UpdateRegistry. (Реализацию этой функции смотрите в 1 части данной статьи).

За сим позвольте откланяться… :)


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