Огляд

На відміну від інших галузей, розробка програмного забезпечення вимагає,
щоб прикладні модулі були скомпільовані і зв'язані з іншими залежними
частинами програми. Кожного разу, коли розробник хоче використати в
додатку іншу логіку чи нові можливості, для того, щоб ці зміни
вступили в силу, йому або їй необхідно модифікувати і перекомпілювати
первинне додаток.


У виробництві таке обмеження неприпустимо. Чи можете ви собі уявити,
що вам довелося б переробляти автомобільний двигун, якби ви захотіли
замінити ваші шини від виробника на більш досконалі? Це могло б пролитися
золотим дощем на механіків, але надмірні експлуатаційні витрати приведуть до
зменшення попиту на автомобілі, а від цього постраждають усі: споживачі,
виробники автомобілів, ті ж механіки. Фактично, одним з основних
факторів успіху промислової революції стала здатність взаємозамінності
деталей машин, тобто використання компонентів. Сьогодні ми не замислюючись
замінюємо компоненти і додаємо нові речі в наші автомобілі.


Автомобілі нічого "не знають" про шини, які вони використовують. Шини мають
властивості (ширина колеса та ін.) Якщо властивості у різних шин співпадають, то ці
шини взаємозамінні. Світильник нічого "не знає" про лампи, які в ньому
використовуються. Якщо параметри ламп (діаметр загвинчується частини) задовольняють
вимогам виробника освітлювального приладу, то ці лампи взаємозамінні.
Чи давно індустрія програмного забезпечення стала наздоганяти решту світу
і будувати компоненти, які поняття не мають про те, як вони будуть
використовуватися? Для галузі, яка є передовою, ми дійсно
пасемо задніх.


На перший погляд, спільні бібліотеки (DLL) забезпечують
вирішення зазначених вище проблем. Наступна вигадана історія покаже, що це не
так.


Припустимо, вам потрібно розробити програму для компанії Acme Gas Tanks.
Додаток буде показувати рівень бензину в новому престижному паливному баку
Acme на 1000 галонів. По-перше, ви створюєте індикатор рівня на основі
ActiveX ™, який має три відмітки: поточний рівень палива в баку,
мінімально можливий безпечний рівень і максимально можливий безпечний
рівень. Ви пишете DLL, назвавши її GasTankLevelGetterDLL, Яка має
наступні функції:



Природно, GasTankLevelGetterDLL підтримує можливість деякого
пристрою безперервно зчитувати дані про кількість палива в новому паливному
баку Acme. Ваша програма працює чудово і не "глючить".


Пару тижнів опісля, містер Річі Річ (Richy Rich) викликає вас до себе і
повідомляє, що ваш ActiveX для індикації рівня є найкрасивішої річчю,
яку він коли-небудь не бачили в своєму житті. Річі говорить вам, що хоче
використовувати його для контролю рівня у своєму акваріумі на 5000 галонів. Він
заявляє, що індикатор повинен показувати ті ж три рівні, що і для паливного
бака. Ви говорите йому, що зайдете до нього завтра, а поки подумаєте над його
пропозицією.


На наступний день ви приходите до думки, називати все DLL, які реалізують
ті самі три функції, хоча і з різною внутрішньою обробкою, однаково –
LevelGetterDLL. Проблема контролю рівня води в акваріумі містера Річі
вирішена. Він перевіряє ваше додаток 24 години на добу, щоб переконатися, що його
рибки знаходяться в цілковитій безпеці. Ви також передаєте нову версію
LevelGetterDLL Acme. Інші компанії зв'язуються з вами на предмет
використання вашого ActiveX індикатора рівня. Ви відповідаєте їм: "Немає проблем!
Візьміть ці три функції, назвіть вашу DLL LevelGetterDLL, І всі
готове. "Вам необхідно всього лише один раз перекомпілювати ваш додаток,
щоб воно підтримувало нову версію LevelGetterDLL, Але оскільки в усьому
світі все називають свої DLL однаково (LevelGetterDLL) І використовують
однакові незмінні три методи, то все працює чудово, і вам ніколи не
доведеться перекомпілювати ваш додаток знову. Ви повертаєтеся додому,
відчуваючи себе трошки генієм.


На наступний день, відкривши The Wall Street Journal , Ви виявляєте,
що Річі Річ розбився на своєму вертольоті. По дорозі в штаб-квартиру Rich Inc.
йому не вистачило палива. Схоже, Річі був клієнтом Acme і запускав обидва додатки
на своєму комп'ютері одночасно. Додаток 1 було те саме, яке ви
розробили з використанням LevelGetterDLL для контролю рівня в його
акваріумі. Додаток 2 було зроблено на замовлення Acme для контролю рівня палива,
в ньому використовувалася та ж версія LevelGetterDLL, Яка була
встановлена на вертольоті Річі. І хоча Річі запускав обидва додатки, Додаток 2
для паливних баків Acme використовувало DLL LevelGetterDLL для акваріума і
показувало рівні 5000-галонів акваріума замість 1000-галонів паливного
бака, оскільки версія для акваріума була встановлена на комп'ютер останньої.
Річі нічого не знав про те, що його вертольоту не вистачить палива. Rich Inc. подає
до суду на Acme, яка, у свою чергу, подає до суду на вас. Інші компанії,
яким ви порадили ваше рішення, також подають на вас до суду. Якщо б ви
використовували Component Object Model (COM), Річі Річ був би живий, і вам не
довелося б сідати на лаву підсудних.



Правило Якщо дві або більше DLL надають однакові функції
(Immutability), ви можете використовувати будь-яку з цих DLL. Однак один додаток
не може використовувати відразу кілька DLL, як і не можуть одночасно кілька
таких DLL перебувати на одному і тому ж комп'ютері. Технологія COM вирішує цю
проблему. Два сервера COM з ідентичними інтерфейсами (і отже методами)
можуть використовуватися двома різними програмами та можуть перебувати на одному і
тому ж комп'ютері, оскільки вони мають різні ідентифікатори CLSID, і,
отже, різні на бінарному рівні. Крім того, технічно ці два
сервера COM взаємозамінні.


Відсутність "взаємозамінних деталей" (компонентів) притаманне індустрії
програмних розробок в силу її відносно молодого віку. Однак, подібно
індустріальної революції, яка створила незалежні деталі машин, технологія COM
реалізує це через програмні компоненти. Розуміючи сенс CLSID і незмінності
інтерфейсів, можна написати закінчений plug-in без будь-якого знання про
клієнті. Це означає, що Додаток 1 може використовувати або Plug-In1 або
Plug-In2. Ще краще, щоб Додаток 1 могло динамічно перемикати Plug-In1 і
Plug-In2. Проектування додатків, що використовують динамічно замінні вставки
(Plug-ins) зробить для програмної індустрії те ж саме, що зробили деталі
машин та механізмів для промислової революції.


Захоплюючись Active Template Library (ATL) та Distributed COM (DCOM), ми
поступово забуваємо, що лежало в основі появи COM. Здатність DCOM
використовувати віддалений виклик процедур (remote procedure calls, RPC) вибудовувати
дані (marshaling) надихає (і, можливо, є однією з причин зростання
популярності COM за останні 12 місяців), проте це не головне, чому була
розроблена технологія COM. Головне, задля чого створювалася COM, – надати
виробникам програм можливість вбудовувати нові функціональні частини в
існуючі програми без перестроювання цих додатків. Компоненти COM
повинні бути спроектовані як взаємозамінні вставки (plug-ins), незалежно
від того, чи є компонент COM локально підключається DLL або віддалено
запускаються сервером.


Мета


Ця стаття продемонструє компоненти COM, які допускають багаторазове
використання аналогічно автомобільних шин. Використання COM дозволить
розробити серію програмних продуктів за більш короткий термін, ніж без
застосування цієї технології. Знаючи як створювати COM об'єкти і інтерфейси, можна
розробляти взаємозамінні компоненти.


Що потрібно


Для побудови прикладів вам буде потрібно Microsoft Visual C + + (r) 5.0. Ні
необхідності в десятирічному досвіді по Windows (r) і C, досить деякого
знайомства з Visual C + +, MFC, спадкуванням та поліморфізмом. Приклади будуть
побудовані та виконані під Windows NT (r) або Windows 95. Ми будемо використовувати
OLE/COM Object Viewer – Зручну утиліту, що поставляється разом з Visual C + +
5.0 і Visual Basic (r) 5.0, а також доступну для download на
http://www.microsoft.com/oledev.


Частина 1: Дублювання інтерфейсів


У наведеному вище випадку з Річі Річем ми бачили, що DLL для акваріума і DLL
для паливного бака не могли перебувати на одному і тому ж комп'ютері, тому що
ні клієнтське додаток, ні дві DLL не були COM компонентами. Яка б DLL ні
була скопійована на комп'ютер, тільки скопійована останньої буде
використовуватися клієнтським додатком. Як ми вже бачили, використання
некоректної DLL може призвести до катастрофічних результатів: вертоліт
розбився. Ми припустили, що якби розробник програми використовував
технологію COM, то він мав би обидві DLL на машині. Оскільки дві DLL були б
помітні по їх CLSID, вони могли б використовуватися в межах однієї програми.
За технологією COM обидві DLL повинні задіяти ідентичні методи через
замінні інтерфейси.


Щоб це довести, ми збираємося створити єдине GUI-додаток,
яке використовує і показує інформацію, що отримується від двох серверів COM:
GasTankLevelGetter DLL і FishTankLevelGetter DLL. Ми також
створимо один додаток, який буде отримувати інформацію від кожної COM DLL і
відображати їх. Опитування кожної DLL буде відбуватися поперемінно по
четирехсекундному таймером. Щоб підкреслити незмінність інтерфейсів і що COM
є двійковим стандартом, ми збираємося написати GUI-додаток
FishTankLevelGetter COM DLL виключно на основі інформації про
GasTankLevelGetter COM DLL. Однак ми не збираємося надавати вам
вихідний код GasTankLevelGetter COM DLL. Якщо ви переписали приклад, ви
знайдете GasTankLevelGetter COM DLL в папці Binaries. Ми вам навіть не
скажімо на чому написана GasTankLevelGetter: на Delphi, Visual C + +,
Java ™, Cobol, Turbo Pascal або Visual Basic. Вам, однак, доведеться
зареєструвати GasTankLevelGetter DLL за допомогою RegSvr32.


Як тільки ви зареєстрували GasTankLevelGetter DLL за допомогою
RegSvr32, ви готові почати, озброївшись OLE/COM Object Viewer. Якщо ви
використовуєте Visual C + + 5.0, OLE / COM Object Viewer знаходиться в програмній групі
Visual C + + 5.0 при навігації через Start | Programs в Explorer. Якщо у
вас немає OLE/COM Object Viewer, Спишіть його з
http://www.microsoft.com/oledev/ і запустіть програму.


Запустивши OLE/COM Object Viewer, Виберіть режим View | Expert
для перегляду Type Libraries. Прокрутіть список і відкрийте папку під назвою
Type Libraries. Прокрутіть папку поки не знайдете GasTankLevelGetter 1.0
TypeLibrary (Ver 1.0). Виділіть цей елемент списку і ви побачите на правій
панелі ID бібліотеки типу та її повний шлях, як показано на малюнку.



Подвійний клацання на GasTankLevelGetter відкриє вікно, яке відображає всю
бібліотеку типу. Ця інформація береться їх даних регістрів, які створюються
при реєстрації DLL. Дані по TypeLib зберігаються в HKEY_CLASSES_ROOT
TypeLib
.



Розділ coclass містить список підтримуваних інтерфейсів для
компонентного об'єкту. Об'єкт може мати будь-яку кількість інтерфейсів,
перераховуються в його тілі і повністю описують той набір інтерфейсів, які
цей об'єкт реалізує, як входять, так і вихідних. Нижче наведені CLSID і
інтерфейс, що містяться в coclass для даного COM об'єкту:


CLSID: 8A544DC6-F531-11D0-A980-0020182A7050
Interface Name:
ILevelGetter

[
uuid(8A544DC6-F531-11D0-A980-0020182A7050),
helpstring("LevelGetter Class")
]
coclass LevelGetter {
[default] interface ILevelGetter;
};

Розкриваючи далі інформацію по інтерфейсу зразок coclass, ми можемо
визначити:


[
odl,
uuid(8A544DC5-F531-11D0-A980-0020182A7050),
helpstring("ILevelGetter Interface")
]
interface ILevelGetter : IUnknown {
HRESULT _stdcall GetLowestPossibleSafeLevel ([out, retval] long *
plLowestSafeLevel);
HRESULT _stdcall GetHighestPossibleSafeLevel([out, retval]
long* plHighestSafeLevel);
HRESULT _stdcall GetCurrentLevel([out, retval] long*
plCurrentLevel);
HRESULT _stdcall GetTextMessage([out, retval] BSTR*
ppbstrMessage);
};

Більш детальний погляд на структуру type library відкриває нам методи і ID
інтерфейсів.




Тепер, оскільки ми знаємо, як побудувати інтерфейс ILevelGetter,
давайте створимо наш власний компонент COM на основі цієї інформації. Якщо
ви вирішили працювати з існуючим прикладом, всі джерела знаходяться в папці
LevelViewer. Запустіть Visual C + + 5.0 і створіть новий проект. Визначте
тип ATLComAppWizard як проект і "FishTankLevelGetter" як ім'я проекту.
Ми вважаємо, що ви створили нову папку проекту. Вікно New Project Dialog має
виглядати як це показано нижче.



У AppWizard для Server Type вкажіть Dynamic Link Library (DLL). Відзначте обидві
опції Allow merging of proxy / stub code і Support MFC.



Коли ви створили новий проект FishTankLevelGetter, Виберіть у меню
Insert | New Class… для створення нового ATL класу. Ви можете вибрати
будь-яке ім'я класу, але переконайтеся, що інтерфейс називається IlevelGetter, А
його тип – Custom, Що вказує на спадкування ILevelGetter від
IUnknown. Якщо б ILevelGetter в GasTankLevelGetter COM DLL
дісталася у спадок від IDispatch, Нам довелося б вибрати тип інтерфейсу
Dual, Який вказував би на те, що новий інтерфейс буде похідним від
IDispatch. Якщо діалог New Class виглядає як показано нижче, натисніть OK,
щоб створити новий клас.



Наступний крок полягає в редагуванні FishTankLevelGetter.IDL. У
IDL файлі вам потрібно мати новий інтерфейс ILevelGetter, Успадковані з
IUnknown. Якщо ви працюєте з прикладами, ви побачите наступний код,
який містить чотири однакових незмінних методу інтерфейсу
IlevelGetter, Які ми бачили в інтерфейсі ILevelGetter
GasTankLevelGetter
.



  • [
    object,
    uuid(7F0DFAA2-F56D-11D0-A980-0020182A7050),
    helpstring("ILevelGetter Interface"),
    pointer_default(unique)
    ]
    interface ILevelGetter : IUnknown
    {
    HRESULT GetLowestPossibleSafeLevel ([out, retval] long * plLowestSafeLevel);
    HRESULT GetHighestPossibleSafeLevel ([out, retval] long * plHighestSafeLevel);
    HRESULT GetCurrentLevel([out, retval] long* plCurrentLevel);
    HRESULT GetTextMessage([out, retval] BSTR* ppbstrMessage);
    };

  • Якщо ви пишіть код, як і ми, самостійно, ви захочете додати
    вищевказаний код так, що ваш інтерфейс відповідав чотирьом ідентичним
    незмінним методам. Найбільш просто додати код за допомогою "copy and paste"
    безпосередньо з вікна ITypeLib Viewer. Ваш код повинен виглядати точно
    також, як у прикладі, за винятком ID інтерфейсу.


    Відкрийте LevelGetter.H і оголосите методи в класі. У вашому класі
    оголошення методів повинне виглядати як це показано нижче:



  • class LevelGetter :
    public ILevelGetter,
    public CComObjectRoot,
    public CComCoClass<LevelGetter,&CLSID_LevelGetter>
    {
    public:
    LevelGetter(){}

    BEGIN_COM_MAP(LevelGetter)
    COM_INTERFACE_ENTRY(ILevelGetter)
    END_COM_MAP()
    //DECLARE_NOT_AGGREGATABLE(LevelGetter)
    / / Remove the comment from the line above if you don "t want your object to
    // support aggregation.

    DECLARE_REGISTRY_RESOURCEID(IDR_LevelGetter)

    // ILevelGetter
    public: //THE FOUR NEW METHODS
    STDMETHOD (GetLowestPossibleSafeLevel) (long * plLowestSafeLevel);
    STDMETHOD (GetHighestPossibleSafeLevel) (long * plHighestSafeLevel);
    STDMETHOD (GetCurrentLevel) (long* plCurrentLevel);
    STDMETHOD (GetTextMessage) (BSTR* ppbstrMessage);
    };


  • Вам тепер потрібно зробити чотири методи. Для демонстраційних цілей, давайте
    залишимо методи простими. Реалізуйте їх на ваш розсуд або скопіюєте
    наступний код із зразків.



  •  //———————————————— —————————
    STDMETHODIMP LevelGetter:: GetLowestPossibleSafeLevel (long * plLowestSafeLevel)
    {
    *plLowestSafeLevel = 70;
    return S_OK;
    }

    //———————————————— —————————
    STDMETHODIMP LevelGetter:: GetHighestPossibleSafeLevel (long * plHighestSafeLevel)
    {
    *plHighestSafeLevel = 98;
    return S_OK;
    }

    //———————————————— —————————
    STDMETHODIMP LevelGetter::GetCurrentLevel(long* plCurrentLevel)
    {
    *plCurrentLevel = 94;
    return S_OK;
    }

    //———————————————— —————————
    STDMETHODIMP LevelGetter::GetTextMessage(BSTR* ppbstrMessage)
    {
    * PpbstrMessage =:: SysAllocString (L "All clear, water level is fine");
    return S_OK;
    }


  • Оскільки у вас вже є методи, скомпілюйте і Слінки вашу COM DLL.
    Потім ми почнемо створювати клієнтську програму.


    Створення клієнтської програми для обох COM об'єктів


    Ми збираємося створити клієнтську програму, яка буде підтримувати два
    COM об'єкту GasTankLevelGetter і FishTankLevelGetter. Використовуючи
    AppWizard, створіть MFC діалог програми, який би підтримував і керуючі
    елементи Automation, і ActiveX одночасно (вкажіть це у відповідних check
    box під час роботи з AppWizard).


    Як тільки ви створили програму, відредагуйте ваш основний діалог у
    редакторі ресурсів, так щоб він мав схожість з наступним:





  •  

  • Зауваження: Можливо ви захочете переглянути ID елементів управління в
    прикладі, оскільки ми збираємося змінити ці значення.

  • Наступний крок полягає в додаванні покажчиків повідомлень для двох кнопок
    Gas Tank Level і Fish Tank Level. У прикладі ці методи називаються
    OnGas і OnFish відповідно


    Якщо ви створили клас діалогу і додали покажчики повідомлень для кнопок, вам
    необхідно відкрити цей клас і додати кілька членів класу і методів
    класу. Перше, що ми зробимо, – це опишемо далі інтерфейс ILevelGetter
    так, щоб ми могли додавати члени класу (class member) для цього типу
    інтерфейсу. По-друге, додамо два додаткові методи класу (class methods)
    ClearMembers і SetNewData і два члени класу m_pILevelGetter
    і m_sLastCalled. Потім, використовуючи Class Wizard, додамо методи
    OnDestroy і OnTimer. Як тільки це зроблено, ваш опис класу
    повинно бути таким, як показано нижче.



  • //forward declaration so for our class member
    interface ILevelGetter;

    class CLevelViewerDlg : public CDialog
    {
    DECLARE_DYNAMIC(CLevelViewerDlg);
    friend class CLevelViewerDlgAutoProxy;

    public:
    CLevelViewerDlg (CWnd * pParent = NULL); / / standard constructor
    virtual ~CLevelViewerDlg();

    //{{AFX_DATA(CLevelViewerDlg)
    enum { IDD = IDD_LEVELVIEWER_DIALOG };
    //}}AFX_DATA

    //{{AFX_VIRTUAL(CLevelViewerDlg)
    protected:
    virtual void DoDataExchange (CDataExchange * pDX); / / DDX / DDV support
    //}}AFX_VIRTUAL

    // Implementation
    protected:
    CLevelViewerDlgAutoProxy* m_pAutoProxy;
    HICON m_hIcon;

    BOOL CanExit();

    //added by manually typing these into the class
    void ClearMembers();
    void SetNewData(const CLSID& clsid, const IID& iid);

    ILevelGetter* m_pILevelGetter;
    CString m_sLastCalled;

    // Generated message map functions
    //{{AFX_MSG(CLevelViewerDlg)
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg void OnClose();
    virtual void OnOK();
    virtual void OnCancel();

    //added by the Class Wizard
    afx_msg void OnFish();
    afx_msg void OnGas();
    afx_msg void OnDestroy();
    afx_msg void OnTimer(UINT nIDEvent);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
    };


  • Далі змінимо файл опису реалізації (implementation file). У конструкторі
    класу проініціалізіруйте змінні членів класу як це показано нижче:

     //———————————————— ————–
    CLevelViewerDlg::CLevelViewerDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CLevelViewerDlg::IDD, pParent)
    {
    //{{AFX_DATA_INIT(CLevelViewerDlg)
    //}}AFX_DATA_INIT
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
    m_pAutoProxy = NULL;

    m_pILevelGetter = NULL;
    m_sLastCalled = _T("CheckedGas");
    }


    Реалізація методу ClearMembers наводиться далі. Ця функція очищає
    елементи керування діалогу (dialog controls). (Зазначимо, що ми використовували б
    Dialog Data exchange для членів класу.)

     //———————————————— ——————–
    void CLevelViewerDlg::ClearMembers()
    {
    CWnd* pWnd = GetDlgItem(IDC_TANK_TYPE);
    if(pWnd != NULL)
    pWnd->SetWindowText("");

    pWnd = GetDlgItem(IDC_LOWEST_SAFE);
    if(pWnd != NULL)
    pWnd->SetWindowText("");

    pWnd = GetDlgItem(IDC_HIGHEST_SAFE);
    if(pWnd != NULL)
    pWnd->SetWindowText("");

    pWnd = GetDlgItem(IDC_CURRENT);
    if(pWnd != NULL)
    pWnd->SetWindowText("");

    pWnd = GetDlgItem(IDC_MESSAGE);
    if(pWnd != NULL)
    pWnd->SetWindowText("");
    }


    OnDestroy, Показаний нижче, використовується для очищення при закритті
    діалогу.

     //———————————————— ——————–
    void CLevelViewerDlg::OnDestroy()
    {
    CDialog::OnDestroy();
    KillTimer(1);
    }

    Даний клас використовує OnTimer для виклику методів кнопок
    OnFish і OnGas так, що користувачеві не потрібно натискати кнопки
    для оновлення даних.

     //———————————————— ——————–
    void CLevelViewerDlg::OnTimer(UINT nIDEvent)
    {
    if(m_sLastCalled == _T("CheckedFish"))
    OnGas();
    else
    OnFish();
    }


  • Зауваження: У реальному житті краще використовувати технологію
    з натисканням кнопок і інтерфейс IConnectionPoint. Закінчений приклад такої
    реалізації ви знайдете на
    http://www.microsoft.com/workshop/prog/com/overview-f.htm.

  • Віртуальна функція OnInitDialog використовується в основному для запуску
    таймера, хоча вона також повертає дані з GasTankLevelGetter COM
    DLL.

     //———————————————— ——————–
    BOOL CLevelViewerDlg::OnInitDialog()
    {
    CDialog::OnInitDialog();

    SetIcon(m_hIcon, TRUE); // Set big icon
    SetIcon(m_hIcon, FALSE); // Set small icon

    OnGas(); //obtain data
    SetTimer(1, 4000, NULL); //set timer for 4 seconds

    return TRUE; / / return TRUE unless you set the focus to a control
    }


    Тепер ми готові описати реалізацію наших методів кнопок OnFish і
    OnGas, Які викликаються поперемінно кожні 4 секунди. Обидві ці функції
    ідентичні на процедурному рівні; вони передають CLSID і IID в
    SetNewData. Єдина різниця полягає в тому, що CLSID і
    IID, Передаються методом OnGas, Використовуються в
    GasTankLevelGetter, А CLSID і IID передаються методом
    OnFish, – У FishTankLevelGetter.


    OnGas повертає CLSID, Взятий з рядка GUID, Яка
    мається на даних coclass TypeLib. Таким же чином повертається IID і,
    крім того, він відображається у OLE/COM Object Viewer. Після отримання
    GUID, Викликається SetNewData.

     //———————————————— ——————–
    void CLevelViewerDlg::OnGas()
    {
    m_sLastCalled = _T("CheckedGas");
    CLSID clsid;
    IID iid;
    HRESULT hRes;
    hRes = AfxGetClassIDFromString(
    "{8A544DC6-F531-11D0-A980-0020182A7050}",
    &clsid);

    if(SUCCEEDED(hRes))
    {
    hRes = AfxGetClassIDFromString(
    "{8A544DC5-F531-11D0-A980-0020182A7050}", & iid);

    if(SUCCEEDED(hRes))
    SetNewData(clsid, iid);
    }
    }


    Метод SetNewData, Показаний нижче, створює instance в
    GasTankLevelGetter COM об'єкті або FishTankLevelGetter COM об'єкті
    в залежності від CLSID. Після цього SetNewData викликає методи інтерфейсу
    ILevelGetter для отримання даних.

     //———————————————— ——————–
    void CLevelViewerDlg:: SetNewData (const CLSID & clsid, const IID & iid)
    {
    ClearMembers();

    ASSERT(m_pILevelGetter == NULL);

    HRESULT hRes = CoCreateInstance(clsid, NULL, CLSCTX_ALL,
    iid, (void**)&m_pILevelGetter);

    if(!SUCCEEDED(hRes))
    {
    m_pILevelGetter = NULL;
    return;
    }

    long lLowestSafeLevel, lHighestSafeLevel, lCurrentLevel;
    BSTR bstrMessage = NULL;

    m_pILevelGetter-> GetLowestPossibleSafeLevel (& lLowestSafeLevel);
    m_pILevelGetter-> GetHighestPossibleSafeLevel (& lHighestSafeLevel);
    m_pILevelGetter->GetCurrentLevel(&lCurrentLevel);
    m_pILevelGetter->GetTextMessage(&bstrMessage);

    m_pILevelGetter->Release();
    m_pILevelGetter = NULL;

    CString sLowest, sHighest, sCurrent, sMessage;
    sLowest.Format("%d",lLowestSafeLevel);
    sHighest.Format("%d",lHighestSafeLevel);
    sCurrent.Format("%d",lCurrentLevel);
    sMessage = bstrMessage;
    ::SysFreeString(bstrMessage);

    CString sItem;
    if(m_sLastCalled == _T("CheckedFish"))
    {
    //we are checking the fish tank now
    sItem = _T("Fish Tank");
    }
    else //m_sLastCalled == _T("CheckedGas")
    {
    //we are checking the fish tank now
    sItem = _T("Gas Tank");
    }

    CWnd* pWnd = GetDlgItem(IDC_TANK_TYPE);
    if(pWnd != NULL)
    pWnd->SetWindowText(sItem);

    pWnd = GetDlgItem(IDC_LOWEST_SAFE);
    if(pWnd != NULL)
    pWnd->SetWindowText(sLowest);

    pWnd = GetDlgItem(IDC_HIGHEST_SAFE);
    if(pWnd != NULL)
    pWnd->SetWindowText(sHighest);

    pWnd = GetDlgItem(IDC_CURRENT);
    if(pWnd != NULL)
    pWnd->SetWindowText(sCurrent);

    pWnd = GetDlgItem(IDC_MESSAGE);
    if(pWnd != NULL)
    pWnd->SetWindowText(sMessage);
    }


    Оскільки інтерфейси однакові, ми впевнені, що методи будуть працювати з
    обома COM об'єктами. Останні два кроки повинні реалізувати OnFish і
    включити визначення інтерфейсу.

     //———————————————— ——————–
    void CLevelViewerDlg::OnFish()
    {
    m_sLastCalled = _T("CheckedFish");
    CLSID clsid;
    IID iid;
    HRESULT hRes = AfxGetClassIDFromString(
    "{7F0DFAA3-F56D-11D0-A980-0020182A7050}", & clsid);

    if(SUCCEEDED(hRes))
    hRes = AfxGetClassIDFromString(
    "{7F0DFAA2-F56D-11D0-A980-0020182A7050}", & iid);

    if(SUCCEEDED(hRes))
    SetNewData(clsid, iid);
    }


    Визначення інтерфейсу, створене чисто віртуальними членами класу,
    включається у верхню частину файлу опису реалізації (хоча його можна помістити
    в опис класу або окремий. h файл), так що член класу
    m_pILevelGetter типу ILevelGetter* "Знає" свої методи.
    Визначення інтерфейсу наведено нижче:

     //———————————————— ——————
    interface ILevelGetter : public IUnknown
    {
    public:
    virtual HRESULT STDMETHODCALLTYPE GetLowestPossibleSafeLevel (long *
    plLowestSafeLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetHighestPossibleSafeLevel (long *
    plLowestSafeLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetCurrentLevel (long * plLowestSafeLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetTextMessage (BSTR * pbstrMessage) = 0;
    };

    Тепер ми готові відкомпілювати, злінкувати і запустити програму. Якщо ви
    запустили додаток, ви можете клацнути по будь-якої кнопці, щоб переключити
    COM компоненти, або дозволити таймеру перемикати їх автоматично кожні чотири
    секунди. І тільки тепер Річі Річ зможе спокійно летіти на своєму вертольоті і
    стежити за рівнем води у своєму акваріумі.


    Частина 2: Успадкування класів та успадкування інтерфейсів


    У першій частині статті ми показали значимість незмінності інтерфейсів і
    продемонстрували, як розробник може побудувати додаток, яке може
    легко замінювати компоненти, якщо розроблений інтерфейс. А що, якщо інтерфейс
    існуючого COM-сервера має сотні методів? У прикладі з першої частини ми
    зробили це простим клонуванням інтерфейсу IlevelGetter, Оскільки він
    містив тільки чотири методи. Спробуйте за допомогою OLE/COM Object Viewer
    переглянути деякі інші бібліотеки типів на вашому комп'ютері. Як ви можете
    переконатися, багато компонентів мають інтерфейси з вельми значною кількістю
    методів. Клонування інтерфейсів, які реалізують сотні методів, з метою
    змінити всього лише декілька з них було б дуже обтяжливо.


    Правила COM свідчать, що якщо ви наслідуєте інтерфейс з існуючого
    інтерфейсу, вам необхідно реалізовувати всі методи, оскільки опису
    інтерфейсів містять суто віртуальні функції. Те ж правило, яке
    забезпечує взаємозамінність деталей машин, може обернутися для
    розробників важким, непотрібним тягарем!


    А що, якщо ви змогли б наслідувати інтерфейси без необхідності повторно
    описувати реалізацію всіх методів? Що якщо б ви могли створити компонент,
    успадкувати інтерфейси та функціональне призначення і переробити
    функціональність на свій розсуд? Сьогодні це не можна зробити за допомогою COM
    об'єктів, розроблених поза вашої організації. Однак, якщо розробники у вашій
    організації використовують мову програмування, що підтримує спадкування і
    поліморфізм, типу Visual C + +, ви це дійсно зробите. Насправді, як
    ми покажемо, MFC дозволяє зробити це значно легше.


    У корені MFC є CCmdTarget. CCmdTarget – Це не тільки базовий
    клас для message-map архітектури, він також містить Dispatch плани, які
    впливають на інтерфейси, такі як IDispatch і IUnknown. Кожен
    прямий нащадок CCmdTarget, Створений за допомогою Class Wizard, містить ці
    інтерфейси зі своїми власними CLSID. CCmdTarget – Один з основних
    робочих класів і базовий клас для таких "повсякденних" MFC класів, як
    CView, CWinApp, CDocument, CWnd і CFrameWnd.
    Відповідно, кожен похідний клас від CCmdTarget може
    реалізовувати власні CLSID і інтерфейси.


    Приклад, який ми збираємося розглянути, покаже спадкування інтерфейсів
    шляхом утворення нових C + + похідних класів від CCmdTarget. У нашому
    базовому класі ми реалізуємо інтерфейс з методами, які викликають віртуальні
    функції членів C + + класу. Наш похідний клас замінить деякі з відібраних
    віртуальних функцій. Що особливо важливо, замість реалізації успадкованого класу в
    тієї ж DLL, ми створимо окрему DLL зі своїм власним CLSID. Найбільш
    ефективно успадковувати реалізацію інтерфейсу від одного коду в інший без
    переписування вихідного інтерфейсу.


    Давайте почнемо з перегляду коду в проекті BaseLevelGetterDLL.
    BaseLevelGetterDLL є типовою MFC DLL. Вона була створена за допомогою
    AppWizard як "regular DLL using the shared MFC DLL". Вона також підтримує
    автоматику (automation). Завершивши роботу з AppWizard, отримуємо
    BaseLevelGetterExport.h, А BASE_LEVEL_GETTER_DLL виявляється
    включеної як preprocessor definition в Project | Settings | C++.
    BaseLevelGetterExport.H і діалог Project | Settings наведені
    нижче.

    //BaseLevelGetterExport.h
    #ifndef BASE_LEVEL_GETTER_EXPORT_DLL_H
    #define BASE_LEVEL_GETTER_EXPORT_DLL_H

    #if defined(BASE_LEVEL_GETTER_DLL)
    #define BASE_LEVEL_GETTER_EXPORT __declspec(dllexport)
    #else
    #define BASE_LEVEL_GETTER_EXPORT __declspec(dllimport)
    #endif

    #endif //BASE_LEVEL_GETTER_EXPORT_DLL_H



     


    Визначивши BASE_LEVEL_GETTER_DLL, Ми можемо створювати класи та
    експортувати їх з нашої DLL.


    Наступним кроком буде створення C + + класу, який містить наші інтерфейси.
    За допомогою Class Wizard кількома натисканнями кнопки миші ми створимо клас,
    успадкованих від CCmdTarget. Виділивши Createable by type ID в
    діалозі New Class, ми створимо наш новий клас з макросом
    IMPLEMENT_OLECREATE, Присвоюється клас його власні CLSID і
    інтерфейс IDispatch.



    Звертаючись до BaseLevelGetter.CPP, Ми бачимо CLSID:

    //Here is our CLSID
    // {C20EA055-F61C-11D0-A25F-000000000000}
    IMPLEMENT_OLECREATE (BaseLevelGetter, "BaseLevelGetterDLL.BaseLevelGetter",
    0xc20ea055, 0xf61c, 0x11d0, 0xa2, 0x5f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)

    І інтерфейс під назвою IbaseLevelGetter типу IDispatch:

    // {C20EA054-F61C-11D0-A25F-000000000000}
    static const IID IID_IBaseLevelGetter =
    {0xc20ea054, 0xf61c, 0x11d0, {0xa2, 0x5f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};

    BEGIN_INTERFACE_MAP(BaseLevelGetter, CCmdTarget)
    INTERFACE_PART (BaseLevelGetter, IID_IBaseLevelGetter, Dispatch)
    END_INTERFACE_MAP()


    Замість того, щоб працювати з інтерфейсом, наданих за замовчуванням Class
    Wizard, ми збираємося додати наш власний інтерфейс, щоб показати як
    легко додавати інтерфейси в класи-нащадки від CCmdTarget. Перше, що ми
    повинні зробити, – це описати наші інтерфейси. Визначення інтерфейсу завжди
    однаково. Кожен інтерфейс повинен мати IID і IUnknown як
    основний інтерфейс де-небудь у своїй ієрархії. Також необхідно реалізувати три
    методу IUnknown. У ILevelGetter.H ми використовуємо GUIDGEN.EXE
    (Знаходиться в Program FilesDevStudioVCBin) для створення унікального IID для
    нашого інтерфейсу успадковуємо інтерфейс від IUnknown. Додатково до трьох
    віртуальним функцій IUnknown ми додали ще 4 віртуальні функції,
    які будуть реалізовані в нашому COM об'єкті. Нижче наведено повний код
    ILevelGetter.H.

    #ifndef ILEVELGETTER_H
    #define ILEVELGETTER_H

    // {BCB53641-F630-11d0-A25F-000000000000}
    static const IID IID_ILevelGetter =
    {0xbcb53641, 0xf630, 0x11d0, {0xa2, 0x5f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};

    interface ILevelGetter : public IUnknown
    {
    //first add the three always required methods
    virtual HRESULT STDMETHODCALLTYPE
    QueryInterface(REFIID riid, LPVOID* ppvObj) = 0;
    virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
    virtual ULONG STDMETHODCALLTYPE Release() = 0;
    //now add methods for this custom interface
    virtual HRESULT STDMETHODCALLTYPE
    GetCurrentLevel(long* plCurrentLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE
    GetHighestPossibleSafeLevel(long* plHighestSafeLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE
    GetLowestPossibleSafeLevel(long* plLowestSafeLevel) = 0;
    virtual HRESULT STDMETHODCALLTYPE
    GetTextMessage(BSTR* ppbstrMessage) = 0;
    };


    Наступним кроком буде визначення методів інтерфейсу в
    BaseLevelGetter.H. У верхній частині BaseLevelGetter.H додамо
    директиву include для опису нашого інтерфейсу як це показано нижче:

    #include "ILevelGetter.h"

    Як тільки ми включили ILevelGetter.H, Ми можемо додати наші методи
    інтерфейсу, використовуючи макрос BEGIN_INTERFACE_PART. У підсумку
    BEGIN_INTERFACE_MACRO створює вкладений клас типу XLevelGetter і
    член класу m_xLevelGetter в BaseLevelGetter. (Більш докладний
    опис макросу BEGIN_INTERFACE_PART дивись MFC Technical Note 38.)
    Кожен метод в інтерфейсі оголошується в макросі так само, як якщо б ніякого
    макросу не було. Можна переконатися, що оголошення методу в ILevelGetter.H
    такі ж як і у версії з використанням ATL.

    BEGIN_INTERFACE_PART(LevelGetter, ILevelGetter)
    STDMETHOD(GetCurrentLevel) (long* plCurrentLevel);
    STDMETHOD (GetHighestPossibleSafeLevel) (long * plHighestSafeLevel);
    STDMETHOD (GetLowestPossibleSafeLevel) (long * plLowestSafeLevel);
    STDMETHOD(GetTextMessage) (BSTR* ppbstrMessage);
    END_INTERFACE_PART(LevelGetter)

    Оскільки наша мета полягає в ефективному спадкуванні інтерфейсів з
    одного джерела в іншій без необхідності повторного опису реалізації всіх
    методів, ми збираємося додати чотири віртуальних функції в наш клас. Кожна
    віртуальна функція буде відповідати методу в інтерфейсі
    ILevelGetter. У прикладі ці методи описані в нижній частині class
    declaration відразу після макросу BEGIN_INTERFACE_PART.

    //since the class can be dynamically created
    //these virtual functions cannot be pure
    virtual long GetCurrentLevel();
    virtual long GetHighestSafeLevel();
    virtual long GetLowestSafeLevel();
    virtual CString GetMessage();

    Зазначимо, що оскільки наш клас-нащадок від CCmdTarget використовує
    DECLARE_DYNCREATE, Ці функції не можуть бути чисто віртуальними.


    Останнє, що залишилося зробити, – оголосити наш клас "exportable". Для цього
    нам необхідно всього лише включити наш опис експорту в опис класу. Це
    виглядає так:

    #include "BaseLevelGetterExport.h"
    class BASE_LEVEL_GETTER_EXPORT BaseLevelGetter: public CCmdTarget
    {

    Реалізація нашого інтерфейсу також проста. Перше, що потрібно зробити, – це
    додати підтримку нашому новому інтерфейсу ILevelGetter. Загальне правило
    полягає в додаванні макросу INTERFACE_PART між
    BEGIN_INTERFACE_PART і END_INTERFACE_PART для кожного
    підтримуваного інтерфейсу. У BaseLevelGetter.CPP це робиться
    доповненням наступного рядка:

     INTERFACE_PART (BaseLevelGetter, IID_ILevelGetter, LevelGetter)

    Так що повний опис INTERFACE_PART виглядає наступним чином:

    BEGIN_INTERFACE_MAP(BaseLevelGetter, CCmdTarget)
    INTERFACE_PART (BaseLevelGetter, IID_IBaseLevelGetter, Dispatch)
    INTERFACE_PART (BaseLevelGetter, IID_ILevelGetter, LevelGetter)
    END_INTERFACE_MAP()

    Далі ми описуємо реалізацію методів ILevelGetter. Перші три методи,
    які повинні бути реалізовані, – це QueryInterface, AddRef і
    Release з IUnknown. Ці методи показані нижче.

     //———————————————— ————————
    HRESULT FAR EXPORT BaseLevelGetter:: XLevelGetter:: QueryInterface
    (
    REFIID iid,
    LPVOID* ppvObj
    )
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    return (HRESULT) pThis-> ExternalQueryInterface (& iid, ppvObj);
    }

    //———————————————— ————————-
    ULONG FAR EXPORT BaseLevelGetter::XLevelGetter::AddRef()
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    return (ULONG) pThis->ExternalAddRef();
    }

    //———————————————— ————————-
    ULONG FAR EXPORT BaseLevelGetter::XLevelGetter::Release()
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    return (ULONG) pThis->ExternalRelease();
    }


    Чотири методи ILevelGetter реалізуються досить просто. Замість
    фактичного виконання обробки, кожен метод викликає свою пов'язану функцію
    через покажчик pThis. Насправді це вимагає деяких додаткових
    пояснень. Якщо ви подивитеся на визначення макросу
    BEGIN_INTERFACE_PART(…) (Файл … MFCincludeAFXDISP.H), ви зверніть
    увагу, що цей макрос є вкладеним описом класу. Макрос робить
    вкладений клас(У нашому випадку, XLevelGetter) Похідним
    від інтерфейсу (ILevelGetter в нашому прикладі) і оголошує його в межах
    існуючого класу (BaseLevelGetter).


    Макрос END_INTERFACE_PART(…) завершує "внутрішнє" опис класу
    XLevelGetter і оголошує змінну члена цього класу
    m_xLevelGetter. Оскільки m_xLevelGetter є членом класу
    BaseLevelGetter, Ми могли б деякими складними арифметичними
    операціями над покажчиками передати від thisоб'єкта
    XLevelGetter в thisоб'єкта, що містить
    BaseLevelGetter. Проте бібліотека MFC містить інший макрос,
    виконує те ж саме. Він називається METHOD_PROLOGUE_EX_, І в нашому
    конкретному випадку він створить змінну BaseLevelGetter* pThis. Ви
    можете використовувати pThis для доступу до public членам і методам
    "Зовнішнього" класу BaseLevelGetter, Включаючи віртуальні (поліморфні)
    функції. Виклик віртуальних функцій у "зовнішньому" класі, фактично, призводить до
    спадкуванню інтерфейсу. Зверніть увагу, що віртуальні функції
    BaseLevelGetter повертають безглузді значення і містять коментарі,
    щоб дозволити розробникам, що створює похідні класи, переписати ці
    функції.


    Інший спосіб показати віртуальне відношення, можливо значно більше
    зручний для читання, – це "вказати власника об'єкта" (set an owner object) у
    класі XLevelGetter (Клас, створений макросом
    BEGIN_INTERFACE_PART). Усередині макросу BEGIN_INTERFACE_PART
    (BaseLevelGetter.H) Ми додаємо дві функції, і член класу виглядає
    наступним чином:

     XLevelGetter () {m_pOwner = NULL;} / / constructor sets member to NULL
    void SetOwner (BaseLevelGetter * pOwner) {m_pOwner = pOwner;} / / set the member
    BaseLevelGetter* m_pOwner; //class member

    Усередині конструктора BaseLevelGetter ми викликаємо
    XLevelGetter::SetOwner. Як згадувалося вище, макрос
    BEGIN_INTERFACE_PART додає в BaseLevelGetter член
    класу m_xLevelGetter, Який представляє LevelGetter. У
    конструкторі BaseLevelGetter ми викликаємо:

    m_xLevelGetter.SetOwner( this );

    що привласнює m_pOnwer значення дійсного об'єкта.


    Нижче показано реалізацію чотирьох методів ILevelGetter і чотирьох
    асоційованих віртуальних функцій BaseLevelGetter. Інші два методу
    (GetLowestPossibleSafeLevel і GetTextMessage) Реалізовані за
    принципом використання "власника об'єкта".

     //———————————————— ————————
    STDMETHODIMP BaseLevelGetter::XLevelGetter::GetCurrentLevel
    (
    long* plCurrentLevel
    )
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    //call outer object”s GetCurrentLevel
    //whether this class or a derived class
    *plCurrentLevel = pThis->GetCurrentLevel();
    return S_OK;
    }

    //———————————————— ————————-
    STDMETHODIMP BaseLevelGetter:: XLevelGetter:: GetHighestPossibleSafeLevel
    (
    long* plHighestSafeLevel
    )
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    //call outer object”s GetHighestSafeLevel
    //whether this class or a derived class
    *plHighestSafeLevel = pThis->GetHighestSafeLevel();
    return S_OK;
    }

    //———————————————— ————————-
    STDMETHODIMP BaseLevelGetter:: XLevelGetter:: GetLowestPossibleSafeLevel
    (
    long* plLowestSafeLevel
    )
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    //call outer object”s GetLowestSafeLevel
    //whether this class or a derived class
    if( m_pOnwer != NULL)
    {
    *plLowestSafeLevel = m_pOwner->GetHighestSafeLevel();
    }
    else
    {
    ASSERT(FALSE);
    }
    return S_OK;
    }

    //———————————————— ————————
    STDMETHODIMP BaseLevelGetter::XLevelGetter::GetTextMessage
    (
    BSTR* ppbstrMessage
    )
    {
    METHOD_PROLOGUE_EX_(BaseLevelGetter, LevelGetter)
    //call outer object”s GetMessage
    //whether this class or a derived class
    CString sMessage;
    If( m_pOwner != NULL )
    {
    sMessage = m_pOwner->GetMessage();
    }
    else
    {
    ASSERT(FALSE);
    }
    *ppbstrMessage = sMessage.AllocSysString();
    return S_OK;
    }

    //———————————————— ———————
    long BaseLevelGetter::GetCurrentLevel()
    {
    TRACE("Derived classes should override!");
    return -1;
    }

    //———————————————— ———————
    long BaseLevelGetter::GetHighestSafeLevel()
    {
    TRACE("Derived classes should override!");
    return -1;
    }

    //———————————————— ———————
    long BaseLevelGetter::GetLowestSafeLevel()
    {
    TRACE("Derived classes should override!");
    return -1;
    }

    //———————————————— ———————
    CString BaseLevelGetter::GetMessage()
    {
    TRACE("Derived classes should override!");
    return "BaseLevelGetter";
    }


    Скомпілюйте і Слінки додаток. Як тільки DLL створена, скопіюйте її в
    каталог WindowsSystem (WINNTSystem32 для Windows NT).


    Важливо: Оскільки ми будемо використовувати інтерфейс ILevelGetter
    з BaseLevelGetter, Не забудьте після приміщення цієї DLL в
    відповідний каталог зареєструвати її за допомогою RegSvr32. Якщо б ми
    використовували BaseLevelGetter як абстрактний базовий клас (тобто
    віртуальні функції BaseLevelGetter повинні були б бути перевизначені) і
    при цьому, можливо, вдалося б уникнути помилок в реалізації, тоді не було б
    необхідності реєструвати COM об'єкт за допомогою RegSvr32.

    Щоб побудувати COM об'єкт, який реалізує інтерфейс ILevelGetter,
    але не вимагає перевизначення всіх методів, ми створюємо COM DLL точно так само, як
    BaseLevelGetterDLL: Ми створюємо MFC AppWizard DLL, яка підтримує
    automation, і додаємо клас, який є нащадком CCmdTarget. Приклад
    містить проект HotTubLevelGetterDLL з класом HotTubLevelGetter
    нащадком від CmdTarget, Який створюється через діалог New Class в Class
    Wizard, як показано нижче.



    Далі додаємо BaseLevelGetterDLL в дорогу include, вказавши його як
    каталог Additional Include на закладці Project | Settings | C/C++ , Як
    показано нижче.



    І лінкуем BaseLevelGetterDLL.lib, Додаючи її як Library Module на
    закладці Project | Settings | Link.



    Завершивши всі установки проекту, виконаємо наступні п'ять кроків для повного
    завершення створення COM DLL plug-in.


    1. Відкрити HotTubLevelGetter.H і замінити всі instances з
    CCmdTarget на BaseLevelGetter (Існує єдина instance
    CCmdTarget в HotTubLevelGetter.H).



    2. Додати BaseLevelGetter.H як include:

    #include <BaseLevelGetter.h>

    class HotTubLevelGetter : public BaseLevelGetter
    {


    3. Переписати віртуальні функції BaseLevelGetter як це потрібно. У
    прикладі оголошуються дві наступні віртуальні функції:

     virtual CString GetMessage () {return "HotTubLevelGetter";}
    virtual long GetCurrentLevel( ) { return -2; }

    4. Відкрити HotTubLevelGetter.CPP і замінити всі instances з
    CCmdTarget на BaseLevelGetter (Існує п'ять instances
    CCmdTarget в HotTubLevelGetter.CPP).


    5. Виконати збирання та компонування. Не забудьте зареєструвати вашу COM DLL
    через RegSvr32.


    Перш ніж продемонструвати роботу COM plug-in на клієнті, давайте
    подивимося що ми побудували. Класи BaseLevelGetter і
    HotTubLevelGetter обидва є нащадками CCmdTarget. Коли ми
    створювали HotTubLevelGetter, Ми вказали Class Wizard успадковувати його від
    CCmdTarget. Нагадаємо, що кожен клас, створений Class Wizard як прямий
    нащадок CCmdTarget, підтримує власні CLSID і інтерфейс
    IDispatch. Коли ми змінюємо базовий клас HotTubLevelGetter з
    CCmdTarget на BaseLevelGetter, HotTubLevelGetter успадковує
    віртуальні методи BaseLevelGetter.


    Коли клієнтові необхідний доступ до HotTubLevelGetter, Він виконує
    звичайний CoCreateInstance(…) – Передаючи CLSID HotTubLevelGetter
    і IID_ILevelGetter, І викликаючи методи ILevelGetter. Коли
    виконується якийсь метод, наприклад, GetCurrentLevel,
    METHOD_PROLOGUE_EX_ бере значення pThis з offset table, і
    pThis дійсно вказує на instance HotTubLevelGetter. Те ж
    річ відбувається, коли ми використовуємо m_pOwner (Він також вказує на
    instance HotTubLevelGetter); Це трохи легше для розуміння через те,
    що ми можемо спостерігати як виконується метод m_xLevelGetter.SetOwner( this
    )
    . Давайте подивимося на клієнтське додаток і встановимо деякі точки
    переривання.


    Відкрийте LevelViewer в папці LevelViewer2. Цей проект майже
    ідентичний першому варіанту LevelViewer. OnFish позиціонується в
    BaseLevelGetter, А OnGas – У HotTubLevelGetter, Як
    показано далі.

     //———————————————— ———–
    void CLevelViewerDlg::OnFish() //mapped to BaseLevelGetter
    {
    m_sLastCalled = _T("CheckedFish");
    CLSID clsid;
    HRESULT hRes = AfxGetClassIDFromString ("BaseLevelGetterDLL.BaseLevelGetter", & clsid);
    if(SUCCEEDED(hRes))
    SetNewData(clsid, IID_ILevelGetter);
    }

    //————————————————————
    void CLevelViewerDlg::OnGas() //mapped to HotTubLevelGetter
    {
    m_sLastCalled = _T("CheckedGas");
    CLSID clsid;
    HRESULT hRes = AfxGetClassIDFromString ("HotTubLevelGetterDLL.HotTubLevelGetter",
    &clsid);
    if(SUCCEEDED(hRes))
    SetNewData(clsid, IID_ILevelGetter);
    }


    Обидві функції викликають SetNewData, Передаючи CLSID, створений
    Class Wizard, і IID_ILevelGetter, Описаний в ILevelGetter.H і
    включений уLevelViewerDlg.H.



    Зауваження: на закладці C + +, категорія Preprocessor, Додайте
    ..BaseLevelGetterDLL в якості додаткового include
    каталогу.


    SetNewData працює точно також, як і раніше. Побудуйте і Слінки
    додаток, але перед запуском поставте точки переривання на якому-небудь одному
    або на всіх методах інтерфейсу як показано нижче.



    Коли виконання зупиниться на точці переривання, скористайтеся режимом
    "Step Into" (по клавіші F8 або F11 в залежності від встановленої системи) і
    покроковим проходом (F10) дійдіть до рядка з покажчиком об'єкта pThis або
    m_pOwner. Перевірте значення. У залежності від того, підключив таймер
    HotTubLevelGetter або BaseLevelGetter, pThis (Або
    m_pOnwer) Будуть вказувати на правильний об'єкт.




    Як ви бачили, COM plug-ins – досить потужний метод розробки додатків,
    який може використовуватися в реальних ситуаціях, наприклад, такий як описана
    нижче.


    Медична страхова компанія, яка займається розробкою замовлених
    планів страхування для великих компаній, приходить до вас за порадою щодо
    розробки нової Windows системи. Кожного разу, коли вони додають новий план,
    вони повинні розробити нову логіку обробки або внести невелике виправлення в
    обробну логіку для існуючого плану, але не повинні переглядати
    кожну частину функціонального призначення. Але оскільки вони весь час додають
    плани у свою систему, модифікація, перевизначення реалізації або клонування
    апробованої вихідної програми непрактично, оскільки це загрожує втратою
    цілісності працюючого коду, незалежно від акуратності програмістів.


    Як розробник COM на C + +, ви розумієте потреба в замінних
    компонентах, які підтримують поліморфізм. Розглянемо наступне: інтерфейс
    IBasePlan, Що входить в клас BasePlan, реалізує 100 методів
    інтерфейсу. Вимоги плану ABC включають модифікацію реалізації 50 методів у
    інтерфейсі IBasePlan. Вимоги плану XYZ включають модифікацію
    реалізації 51 методу в інтерфейсі IBasePlan, Але 50 з них точно такі ж,
    як для плану ABC. Замість повного визначення реалізації для кожного COM
    об'єкта, ви призначаєте в BasePlan 100 віртуальних функцій члена C + +
    класу, по одній для кожного методу в інтерфейсі IBasePlan , Як
    наведеному вище прикладі.


    Оскільки у вас є асоційовані віртуальні функції в класі
    BasePlan, Ієрархія класу для плану XYZ така:


    1. class BASE_PLAN_EXPORT BasePlan : public CCmdTarget


    Реалізує IBasePlan, 100 методів інтерфейсу і 100 асоційованих
    віртуальних функцій члена C + + класу.


    2. class ABC_PLAN_EXPORT ABCPlan : public BasePlan


    Успадковується від BasePlan, Використовує 50 віртуальних функцій члена C + +
    класу в BasePlan і заміщає 50 віртуальних функцій BasePlan.


    3. class XYZPlan : public ABCPlan


    Успадковується від ABCPlan, Використовує 49 віртуальних функцій члена C + +
    класу в BasePlan, Використовує 50 віртуальних функцій члена C + + класу
    в ABCPlan і заміщає 1 віртуальну функцію BasePlan.


    Кожен компонент створюється як окремий binary і COM об'єкт. Кожен з них
    має окремий CLSID і, завдяки структурі спадкування, реалізує інтерфейс
    IBasePlan. Застосовуючи AppWizard і Class Wizard, ви можете завершити
    реалізацію плану XYZ протягом декількох хвилин без будь-якого зачіпання COM
    компонентів базового класу. Всі бібліотеки COM DLL розміщуються на тому ж
    комп'ютері, і якщо ви використовуєте компонентні категорії або інші аналогічні
    методи реєстрації, додаток клієнта знайде план XYZ, як тільки він буде
    зареєстрований RegSvr32.

    Схожі статті:


    Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

    Коментарів поки що немає.

    Ваш отзыв

    Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

    *

    *