::Главная страница :: С++/Си :: Статьи {Путь}
Введение в COM
Часть 2

Автор: Michael Dunn
Перевод: Илья Простакишин
Источник: The Code Project

Базовый интерфейс - IUnknown

Каждый COM-интерфейс наследуется от интерфейса IUnknown. Имя выбрано не совсем удачно, поскольку этот интерфейс не является "неизвестным" (unknown). Это имя всего лишь означает, что если вы имеете указатель на интерфейс COM-объекта IUnknown, то вы не можете знать, какой объект им владеет (реализует), поскольку интерфейс IUnknown есть в каждом COM-объекте.

IUnknown включает три метода:

  1. AddRef() - заставляет COM-объект увеличивать (инкрементировать) свой счетчик обращений. Вы должны использовать этот метод, если была сделана копия указателя на интерфейс и нужно обеспечить возможность использования двух указателей - копии и оригинала. Мы не будем использовать метод AddRef() в этой статье, т.к. для рассматриваемых здесь задач он не нужен.

     

  2. Release() - сообщает COM-объекту о необходимости уменьшения (декремента) счетчика обращений. Смотрите предыдущий пример, чтобы понять, как нужно использовать Release().

     

  3. QueryInterface() - запрашивает указатель на интерфейс COM-объекта. Используется если CO-класс содержит не один, а несколько интерфейсов.

Вы уже видели пример использования Release(), но как же действует QueryInterface()? Когда вы создаете COM-объект с помощью CoCreateInstance(), вы получаете указатель на интерфейс. Если COM-объект включает более одного интерфейса (не считая IUnknown), вы должны использовать метод QueryInterface() для получения дополнительных указателей на интерфейсы, которые вам нужны. Посмотрим на прототип QueryInterface():

HRESULT IUnknown::QueryInterface (
    REFIID iid,
    void** ppv );

Значения параметров:

iid
IID интерфейса, который вам нужен.
ppv
Адрес указателя на интерфейс. QueryInterface() возвращает указатель на интерфейс через этот параметр, если не произошло никаких ошибок.

Продолжим наш пример с ярлыком. CO-класс для создания ярлыков включает интерфейсы IShellLink и IPersistFile. Если у вас уже есть указатель на IShellLink - pISL, то вы можете запросить интерфейс IPersistFile у COM-объекта с помощью следующего кода:

HRESULT hr;
IPersistFile* pIPF;

    hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );

Затем вы тестируете hr с помощью макроса SUCCEEDED. Это нужно, чтобы узнать, сработал ли метод QueryInterface(). Если все нормально, то можно использовать новый указатель pIPF, так же как и любой другой интерфейсный указатель. Затем вам нужно вызвать метод pIPF->Release() для сообщения COM-объекту, что вы закончили работу с интерфейсом и он вам больше не нужен.

Обратите внимание - Обработка строк

Я хочу остановиться на некоторых моментах, касающихся работы со строками при написании программ в COM.

Всякий раз, когда метод COM возвращает строку, он делает это, используя формат Unicode. Unicode это таблица символов, также как и ASCII, только все символы в ней занимают 2 байта (в ANSI - один байт). Если вы хотите получить строку в более удобном виде, то ее нужно преобразовать в тип TCHAR.

TCHAR и функции, начинающиеся с _t (например, _tcscpy()) были разработаны для управления строками Unicode и ANSI с использованием одинакового исходного кода. Наверняка, вы раньше писали программы с использованием ANSI-строк и ANSI-функций, поэтому далее в этой статье я буду обращаться к типу char, вместо TCHAR, чтобы лишний раз вас не смущать. Однако, вы должны знать, что есть такой тип - TCHAR, хотя бы для того, чтобы не задавать лишних вопросов, когда встретите его в программах, написанных другими разработчиками.

Когда вы получаете строку из метода COM, вы можете преобразовать ее в строку char одним из следующих способов:

  1. Вызвать функцию API WideCharToMultiByte().

     

  2. Вызвать функцию CRT wcstombs().

     

  3. Использовать конструктор CString или оператор присваивания (только в MFC).

     

  4. Использовать макрос преобразования ATL.
Особенности Unicode

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

    wcout << wszSomeString;

Однако, имейте ввиду, что wcout предполагает, что все "входящие" строки имеют формат Unicode, поэтому если вы имеете любую "нормальную" строку, то для вывода нужно использовать std::cout. Если вы используете строковые литералы, для перевода в Unicode ставьте перед ними символ L, например:

    wcout << L"The Oracle says..." << endl << wszOracleResponse;

Если вы используете строки Unicode, вы должны знать о следующих ограничениях:

Объединим все вместе - Примеры Программ

Здесь приведены два примера, иллюстрирующие концепции COM, которые обсуждались ранее в этой статье.

Использование объекта COM с одним интерфейсом

Первый пример показывает, как можно использвать объект COM, содержащий единственный интерфейс. Это простейший случай из тех, которые вам могут встретиться. Программа использует содержащийся в оболочке CO-класс Active Desktop для получения имени файла "обоев", которые установлены в данный момент. Чтобы этот код был работоспособен, вам может потребоваться установить Active Desktop.

Мы должны осуществить следующие шаги:

  1. Инициализировать библиотеку COM.

     

  2. Создать COM-объект, используемый для взаимодействия с Active Desktop и получить интерфейс IActiveDesktop.

     

  3. Вызвать метод COM-объекта GetWallpaper().

     

  4. Если GetWallpaper() завершился успешно, вывести имя файла "обоев" на экран.

     

  5. Освободить интерфейс.

     

  6. Разинициализировать библиотеку COM.

 

WCHAR   wszWallpaper [MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop* pIAD;

    // 1. Инициализация библиотеки COM (заставляем Windows загрузить библиотеки DLL). Обычно
    // вам нужно делать это в функции InitInstance() или подобной ей. В MFC-приложениях
    // можно также использовать функцию AfxOleInit().
    CoInitialize ( NULL );

    // 2. Создаем COM-объект, используя CO-класс Active Desktop, поставляемый оболочкой.
    // Четвертый параметр сообщает COM какой именно интерфейс нам нужен (IActiveDesktop).
    hr = CoCreateInstance ( CLSID_ActiveDesktop,
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            IID_IActiveDesktop,
                            (void**) &pIAD );

    if ( SUCCEEDED(hr) )
        {
        // 3. Если COM-объект был создан, то вызываем его метод GetWallpaper().
        hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );

        if ( SUCCEEDED(hr) )
            {
            // 4. Если GetWallpaper() завершился успешно, выводим полученное имя файла.
            // Заметьте, что я использую wcout для отображения Unicode-строки wszWallpaper.
            // wcout является Unicode-эквивалентом cout.
            wcout << L"Wallpaper path is:\n    " << wszWallpaper << endl << endl;
            }
        else
            {
            cout << _T("GetWallpaper() failed.") << endl << endl;
            }

        // 5. Освобождаем интерфейс.
        pIAD->Release();
        }
    else
        {
        cout << _T("CoCreateInstance() failed.") << endl << endl;
        }

    // 6. Разинициализируем библиотеку COM. В приложениях MFC этого не требуется -
    // MFC делает это автоматически.
    CoUninitialize();

В этом примере я использовал std::wcout для отображения строки Unicode wszWallpaper.

Использование COM-объекта, включающего несколько интерфейсов

Второй пример показывает, как можно использовать QueryInterface() для получения единственного интерфейса COM-объекта. В этом примере используется CO-класс Shell Link, содержащийся в оболочке, для создания ярлыка для файла "обоев", имя которого мы получили в предыдущем примере.

Программа состоит из следующих шагов:

  1. Инициализация библиотеки COM.

     

  2. Создание объекта COM, используемого для создания ярлыков, и получение интерфейса IShellLink.

     

  3. Вызов метода SetPath() интерфейса IShellLink.

     

  4. Вызов метода QueryInterface() объекта COM и получение интерфейса IPersistFile.

     

  5. Вызов метода Save() интерфейса IPersistFile.

     

  6. Освобождение интерфейсов.

     

  7. Разинициализация библиотеки COM.
CString       sWallpaper = wszWallpaper;  // Конвертация пути к "обоям" в ANSI
IShellLink*   pISL;
IPersistFile* pIPF;

    // 1. Инициализация библиотеки COM (заставляем Windows загрузить библиотеки DLL). Обычно
    // вам нужно делать это в функции InitInstance() или подобной ей. В MFC-приложениях
    // можно также использовать функцию AfxOleInit().
    CoInitialize ( NULL );

    // 2. Создание объекта COM с использованием CO-класса Shell Link, поставляемого оболочкой.
    // 4-й параметр указывает на то, какой интерфейс нам нужен (IShellLink).
    hr = CoCreateInstance ( CLSID_ShellLink,
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            IID_IShellLink,
                            (void**) &pISL );

    if ( SUCCEEDED(hr) )
        {
        // 3. Устанавливаем путь, на который будет указывать ярлык (к файлу "обоев").
        hr = pISL->SetPath ( sWallpaper );

        if ( SUCCEEDED(hr) )
            {
            // 4. Получение второго интерфейса (IPersistFile) от объекта COM.
            hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );

            if ( SUCCEEDED(hr) )
                {
                // 5. Вызов метода Save() для сохранения ярлыка в файл. Первый параметр
                // является строкой Unicode.
                hr = pIPF->Save ( L"C:\\wallpaper.lnk", FALSE );

                // 6a. Освобождение интерфейса IPersistFile.
                pIPF->Release();
                }
            }

        // 6b. Освобождение интерфейса IShellLink.
        pISL->Release();
        }

    // Где-то здесь должен быть код для обработки ошибок.

    // 7. Разинициализация библиотеки COM.  В приложениях MFC этого делать
    // не нужно, т.к. MFC справляется с этим сама.
    CoUninitialize();
Литература

Essential COM, Don Box, ISBN 0-201-63446-5.

MFC Internals, George Shepherd and Scot Wingo, ISBN 0-201-40721-3.

Beginning ATL 3 COM Programming, Richard Grimes, ISBN 1-861001-20-7.

Тематические ссылки
Ваша ссылка Ваша ссылка

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