• СТАТЬЯ Введение в COM Часть 1
  • Программирование на Visual C++

    Выпуск №28 от 17 декабря 2000 г.

    Всем привет!

    Сегодня вас ждет сюрприз. Сегодня в рассылке впервые появится статья по COM. Мне приходило очень много просьб по этому поводу, часто выражалось недовольство тем, что слишком много внимания уделяется MFC. Типичный пример – в рубрике "Обратная связь". Так что по зрелом размышлении я решил начать понемногу освещать в рассылке и тему COM. В связи с этим, тем кто никогда раньше с COM не сталкивался (а думаю среди подписчиков такие есть), предлагаю вам прочитать статью, знакомящую вас с этой технологией.

    СТАТЬЯ

    Введение в COM

    Часть 1

    Автор: michael dunn

    Перевод: Илья Простакишин

    Источник: The Code Project

    Предмет данной статьи

    Я написал это руководство для программистов, которые начинают осваивать COM и нуждаются в помощи для понимания основ этой концепции. Статья содержит краткое введение в спецификацию COM, объясняет основные термины и описывает примеры использования существующих компонентов COM. Прошу обратить внимание, что эта статья не научит вас тому, как писать собственные объекты и интерфейсы.

    Введение

    COM (Component Object Model – Объектно-Компонентная Модель) – одно из трехбуквенных сокращений, которые сегодня очень часто используются в Windows (вспомните – API, MFC, OLE, ATL). Множество новых технологий, разрабатывающихся постоянно, базируется на COM. Документация просто пестрит терминами типа COM object (объект COM), interface (интерфейс), server (сервер), и так далее, но везде почему-то предполагается, что вы уже знакомы с тем, как COM работает и как ее использовать.

    Эта статья является вводной для начинающих, она описывает основные используемые механизмы, а также показывает как использовать объекты COM, поставляемые со стороны (особенно, оболочкой Windows). После знакомства со статьей вы сможете использовать COM-объекты, как встроенные в Windows, так и предоставляемые третьими лицами.

    Я предполагаю, что вы уже являетесь специалистом в C++. Я частично использую в своих примерах MFC и ATL, и в этих случаях смысл кода будет поясняться, на случай, если вы не знакомы с этими библиотеками. […]

    Что такое COM?

    COM – это метод разделения двоичного кода между разными приложениями, написанными на разных языках программирования. Это не совсем то, что обеспечивает C++, а именно повторное использование исходного кода. ATL – хороший пример такого подхода. Отлаженный исходный код может повторно использоваться и нормально работать только в C++. При этом существует возможность коллизий между именами, не говоря уже о неприятностях при наличии множества копий одинакового кода в ваших проектах.

    Windows позволяет разделять код между приложениями с помощью библиотек DLL. Я не раскрою большого секрета, если скажу, что все функции Windows содержатся в различных внешних библиотеках – kernel32.dll, user32.dll и т.д., которые доступны любому Windows – приложению, и более того, должны им использоваться. Но DLL расчитаны на использование только посредством интерфейса С или языков, понимающих стандарты вызова языка C. Таким образом, реализация языка программирования является барьером между создаваемым приложением и уже реализованными процедурами, содержащимися внутри DLL-библиотеки.

    В MFC был введен новый механизм разделения двоичного кода – библиотеки расширения MFC (MFC extension DLLs). Но это еще более ограниченный метод, т.к. вы можете использовать его только в приложениях, созданных на основе библиотеки MFC.

    COM решает все эти проблемы. Делается это посредством введения двоичного стандарта. При этом спецификация COM требует, чтобы двоичные модули (DLL и EXE) компилировались в соответствие со специфической структурой, которая декларируется этим стандартом. Стандарт также в точности определяет, каким образом COM-объекты должны быть организованы в памяти. Вдобавок, двоичная структура не должна быть зависима от особенностей языка программирования (как, например, стандарта описаний имен в C++). Все это нужно для того, чтобы облегчить доступ к модулю приложения, созданного на любом языке программирования. Двоичный стандарт возлагает "бремя" совместимости на "плечи" компилятора, облегчая задачу вам, как создателю компонентов, и другим людям, которые будут пользоваться вашими компонентами.

    Структура расположения COM-объектов в памяти очень похожа на модель, которая используется в C++ виртуальными функциями, поэтому многие компоненты COM создаются с использованием языка C++. Однако, здесь важно заметить, что язык, на котором вы пишите, не имеет значения, поскольку результат можно использовать в будущем с любыми языками программирования.

    Строго говоря, COM не является спецификацией, привязанной к Win32. Теоретически, можно портировать ваши COM-объекты в Unix или любые другие ОС. Однако, я никогда не видел, чтобы COM применялась где-то за пределами сферы влияния Microsoft.

    Основные определения

    Начнем двигаться снизу-вверх. Итак, интерфейс (interface) – это простая группа функций. Эти функции, в свою очередь, называются методами (methods). Имена интерфейсов начинаются с буквы I, например IShellLink. В терминологии C++ интерфейс представляет собой абстрактный базовый класс, содержащий только чистые виртуальные функции (pure virtual functions).

    Интерфейсы могут наследоваться (inherit) от других интерфейсов. Наследование работает также, как и одиночное наследование в C++. Множественное наследование для интерфейсов не применяется.

    CO-класс (coclass) (сокращение от component object class) содержится в dll или exe и включает код одного или нескольких интерфейсов. Говорят, что CO-класс поддерживает или реализует (implement) эти интерфейсы. Объект COM (COM object) – это экземпляр CO-класса в памяти. Заметьте, что "класс" COM – это не тоже самое, что "класс" C++, хотя часто бывает, что класс COM реализуется посредством класса C++.

    Сервер COM (COM server) – это двоичный файл (DLL или EXE), содержащий один или несколько CO-классов.

    Регистрация (registration) – это процесс создания записей в реестре, которые сообщают Windows о том, где можно найти определенный сервер COM. Дерегистрация (unregistration) наоборот – удаление этих данных из реестра.

    GUID (рифмуется с "fluid" – "жидкий, текучий", сокращение от globally unique identifier – Глобальный Уникальный Идентификатор) – это 128-битный номер, который используется COM для идентификации различных элементов. Каждый интерфейс и CO-класс имеет GUID. Коллизии между именами невозможны, поскольку каждый GUID абсолютно уникален и повторение GUID очень маловероятно (если вы используете для их создания функции COM API). Вы также можете иногда встретить термин UUID (сокращение от universally unique identifier). uuid и guid это практически одно и тоже.

    ID класса (class ID) или CLSID – это GUID, которым обозначается CO-класс. В свою очередь, ID интерфейса (interface ID) , или IID – это GUID, обозначающий интерфейс.

    Существует две причины, по которым идентификаторы GUID так широко используются в COM:

    1. GUID это всего лишь число. Любой язык программирования может оперировать им. 

    2. Каждый GUID, создаваемый на любой машине, уникален (если создан правильно). Следовательно, два COM-разработчика не могут использовать одни и те же GUID. Это решает проблему по выделению уникальных GUID и устраняет необходимость в специальном центре по выделению GUID (как, например, при регистрации доменов в Internet).

    HRESULT – это целочисленный тип, который используется COM для возврата кодов ошибок или кодов завершения. Не смотря на то, что имя типа начинается с префикса H, он (этот тип) не является дескриптором. Переменная типа HRESULT способна участвовать в любых логических операциях языка C, например != и ==.

    Наконец, Библиотека COM (COM library) – это часть операционной системы, с которой вы взаимодействуете, когда делаете что-либо с элементами COM. Часто библиотека COM называется просто "COM" и иногда это приводит к некоторой путанице.

    Работа с объектами COM

    Каждый язык реализует операции с объектами по-разному. Например, в C++ вы создаете объекты на стеке, либо с помощью new динамически выделяете для них место в "куче". Поскольку COM должна быть нейтральна к языку, библиотека COM включает свои собственные средства управления объектами. Сравним управление объектами в COM и C++:

    Создание нового объекта

    • В C++ используется оператор new, либо объект создается на стеке. 

    • В COM вызывается специальная API-функция библиотеки COM.

    Удаление объектов

    • В C++ используется оператор delete, либо объект удаляется автоматически при выходе из области видимости. 

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

    Теперь, между этими двумя стадиями – создания и удаления объекта – вы, естественно, должны использовать этот объект. Когда вы создаете COM-объект, вы сообщаете библиотеке COM, какой интерфейс вам нужен. Если объект был успешно создан, библиотека COM возвращает указатель на запрашиваемый интерфейс. С его помощью вы можете вызывать методы этого интерфейса, также как при использовании обычного объекта C++.

    Создание объекта COM

    Для создания COM-объекта и получения интерфейса из этого объекта (напомню, что COM-объект может содержать несколько интерфейсов) вы должны вызвать библиотечную функцию CoCreateInstance(). Прототип CoCreateInstance():

     

    HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID* ppv);

    Описание параметров:

    rclsid CLSID CO-класса. Например, вы можете передать CLSID_ShellLink при создании COM-объекта, который используется для создания ярлыков.
    pUnkOuter Этот параметр используется только при агрегации COM-объектов, когда берется существующий CO-класс и в него добавляются новые методы. Для наших целей мы должны передать NULL для указания на то, что агрегация использоваться не будет.
    dwClsContext Указывает на тип COM-сервера. В этой статье будет использоваться простейший тип сервера – in-process DLL, поэтому в качестве параметра будет передаваться константа CLSCTX_INPROC_SERVER. Предостережение: не используйте CLSCTX_ALL (она установлена в ATL по умолчанию), т.к. это может привести к ошибке в системах Windows 95, где не инсталлирован DCOM.
    riid Это IID интерфейса, который вы хотите получить. Например, вы должны передать IID_IShellLink для получения указателя на интерфейс IShellLink.
    ppv Адрес указателя на интерфейс. Библиотека COM возвращает указатель на запрашиваемый интерфейс через этот параметр.

    Когда вы вызываете CoCreateInstance(), она находит CLSID в реестре, считывает данные о расположении сервера, загружает сервер в память и создает экземпляр CO-класса, который вы запрашивали.

    Вот пример, в котором создается объект CLSID_ShellLink и запрашивается указатель на интерфейс IShellLink, которым владеет этот COM-объект.

    HRESULT hr;

    IShellLink* pISL;

    hr = CoCreateInstance (CLSID_ShellLink,  // CLSID CO-класса

          NULL, // агрегация не используется

          CLSCTX_INPROC_SERVER, // тип сервера

          IID_IShellLink, // IID интерфейса

          (void**)&pISL); // Указатель на наш интерфейсный указатель

    if (SUCCEEDED(hr)) {

     // Здесь можно вызывать методы, используя pISL.

    } else {

     // Невозможно создать объект COM. hr присвоен код ошибки.

    }

    В начале мы объявляем переменную типа HRESULT для хранения значения, возвращаемого CoCreateInstance() и указатель на IShellLink. Затем мы вызываем CoCreateInstance() для создания нового COM-объекта. Макрос SUCCEEDED возвращает TRUE, если hr хранит код успешного завершения, или FALSE, если hr содержит код ошибки. Есть также похожий макрос – FAILED, который проверяет значение на предмет соответствия коду ошибки (т.е. делает все наоборот).

    Удаление COM-объекта

    Как уже было сказано ранее, вам не надо освобождать COM-объекты – достаточно сообщить им, что они больше не нужны. Интерфейс IUnknown, являющийся прародителем всех COM-объектов, содержит метод Release(). Вы должны вызвать этот метод для того, чтобы сообщить COM-объекту, что вы в нем более не нуждаетесь. Однажды вызвав Release(), вы больше нигде не сможете использовать указатель на интерфейс, т.к. COM-объект может исчезнуть из памяти в любое время.

    Продолжим предыдущий пример, добавив команду удаления объекта:

    // Создаем COM-объект как раньше и…

    if (SUCCEEDED(hr)) {

     // Вызов методов интерфейса через pISL.

     // Сообщим COM-объекту о том, что он нам больше не нужен.

     pISL->Release();

    }

    Интерфейс IUnknown будет детально рассмотрен в следующем разделе.

    [Продолжение следует]

    ВОПРОС-ОТВЕТ 

    Q 1. Есть окно нестандартной формы (например, круглое). Но рамка, появляющаяся вокруг него при перемещении, – строго прямоугольной формы. Как избавиться от такой рамки вообще? Или, может быть, ее можно сделать тоже произвольной формы (по контуру окна)?

    2. Как избавиться от пунктирной рамки на кнопке, имеющей фокус? Для кнопки, сделанной из красивого рисунка, такая рамка выглядит лишней…

    (Максим Чучуйко )

    A 1. Избавиться от рамки можно так. Как известно, в Windows существует настройка, определяющая двигаются ли окна целиком или двигается только рамка, а окно переносится на новое место после отпускания кнопки мыши. Менять эту настройку можно либо через панель управления, либо программно – с помощью функции SystemParametersInfo. Таким образом, нужно включить режим перетаскивания окна целиком, когда наше окно начинают перемещать, и вернуть его в первоначальное положение после того, как перемещение закончено.

    О том, что перемещение начинается, окно узнаёт по сообщению WM_SYSCOMMAND (с параметром SC_MOVE). Когда перемещение завершается, окно получает ещё одно сообщение – WM_EXITSIZEMOVE. Обработчики могут выглядеть так: 

    void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam) {

     if ((nID & 0xFFF0) == SC_MOVE) {

      SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, &m_bDrag, 0);

      SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, TRUE, 0, 0);

     }

     CFrameWnd::OnSysCommand(nID, lParam);

    }


    LRESULT CMainFrame::OnExitSizeMove(WPARAM wParam, LPARAM lParam) {

     if(m_bDrag != -1) {

      SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, m_bDrag, 0, 0);

      m_bDrag = -1;

     }

     return Default();

    }
     

    Переменную m_bDrag типа int следует добавить с класс главного окна и инициализировать значением -1 в конструкторе.

    Обратите внимание, что ClassWizard не умеет вставлять обработчик WM_EXITSIZEMOVE — придётся сделать это вручную, используя макрос ON_MESSAGE.

    2. Сперва порекомендую метод, который широко применяют парни из Microsoft – использовать вместо кнопки Tool bar с одной-единственной кнопкой. Нужно только установить такому тулбару стили CCS_NOPARENTALIGN и CCS_NORESIZE, чтобы он не прижимался к верхней кромке окна, а оставался там, где мы его разместили. Этот же способ, кстати, можно использовать, если в приложении требуется "нормальная" плоская кнопка. 

    Ну а если такой способ не подходит, остаётся прибегнуть к custom draw. Это не должно быть проблемой, так как изображение для кнопки уже нарисовано – осталось добавить к нему выпуклую/вдавленную кромку.

    (Александр Шаргин)
    ОБРАТНАЯ СВЯЗЬ 

    Я уже давно получаю вашу подписку. Она мне очень нравится. Но у меня всё время возникает вопрос когда я читаю очередной номер подписки. Почему почти все выпуски так или иначе посвещены MFC? Даже если тема к примеру ODBC, то примеры всё равно на MFC? Я не имею ничего против MFC, но сам последний раз писал на ней уже очень давно потому-что MFC больше всё-же desktop-UI-ориентированная. То чем я занимаюсь и надеюсь не только я. Написанием COM, COM+ компонентов с UI обычно на ASP. Компоненты я пишу на ATL с STL, с доступом к базам данных через OLE DB/ADO. По ATL/STL/COM/COM+/OLE DB/ADO довольно мало материала в подписке. Почему? Неужели подавляющее большинство подписчиков пишет только на MFC?

    (Vladislav Loidap )
    В ПОИСКАХ ИСТИНЫ

    Q. Как в Win9x и WinNT заблокировать клавиши WIN, Alt+Tab, Ctrl+Esc etc.?

    (Mike Krasnik )

    А на сегодня это все… До скорого! 

    (Алекс Jenter jenter@mail.ru) (Красноярск, 2000.)







    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх