::Главная страница :: С++/Си :: Статьи

Введение в 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 - это метод разделения двоичного кода между разными приложениями, написанными на разных языках программирования. Это не совсем то, что обеспечивает С++, а именно повторное использование исходного кода. 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++:

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

Теперь, между этими двумя стадиями - создания и удаления объекта - вы, естественно, должны использовать этот объект. Когда вы создаете 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();
        }
Тематические ссылки
Ваша ссылка Ваша ссылка

Обмен кнопками, ведение статистики, реклама.