Отправляет email-рассылки с помощью сервиса Sendsay

Программирование на Visual С++

  Все выпуски  

Программирование на Visual С++ - No.29 (Введение в COM - часть 2)


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

ПРОГРАММИРОВАНИЕ НА VISUAL C++

Выпуск No. 29 от 24 декабря 2000 г.

Здравствуйте, уважаемые подписчики!

Рад снова приветствовать вас на страницах рассылки. В этом выпуске вас ожидает вторая часть статьи "Введение в COM" и, конечно же, ответы на вопрос из предыдущего выпуска и кое-что еще.

/ / / / СТАТЬЯ / / / / / / / / / / / / / / / / / / /

Введение в 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, вы должны знать о следующих ограничениях:

  • С этими строками вы должны использовать функции вида wcsXXX(), например wcslen().

  • За редким исключением, вы не должны передавать строки Unicode функциям Windows API в ОС Windows 9x. Чтобы обеспечить переносимость кода между платформами 9x и NT, вы должны использовать типы TCHAR, как это описано в MSDN.
Объединим все вместе - Примеры Программ

Здесь приведены два примера, иллюстрирующие концепции 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.

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

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

|A1 Например так - в конструкторе главного окна приложения зарегистрировать HotKey:

m_HK = GlobalAddAtom("alttab"); // DWORD m_HK;
RegisterHotKey(GetSafeHwnd(), m_HK, MOD_ALT, VK_TAB);

а в деструкторе не забыть его разрегистрировать:

UnregisterHotKey(GetSafeHwnd(), m_HK);

так как никакого обработчика для этого HotKey мы не делаем, то соответственно и происходить по нажатию Alt-Tab ничего не будет.

- Алексей Кирюшкин

|A2 По материалам http://msdn.microsoft.com/msdnmag/issues/0700/Win32/Win320700.asp

В WinNT (начиная с Windows NTR 4.0 Service Pack 3) существует возможность использовать "low-level" hook на клавиатуру WH_ KEYBOARD_LL для отключения комбинаций Ctrl+Esc, Alt+Tab, Alt+Esc.

Для данной данной функии установлен лимит времени: Система возвращается в нормальное состояние через промежуток времени определяемый параметром LowLevelHooksTimeout в HKEY_CURRENT_USER\Control Panel\Desktop время указывается в милисекундах.

- Владимир Згурский

|A3 Это делается очень по-разному в различных системах от Microsoft.

В Windows 9x можно использовать трюк, опсанный в MSDN - вызвать функцию SystemParametersInfo с недокументированным параметром. В данном случае им можно пользоваться смело: Микрософт больше не будет вносить изменений в архитектуру Win9x. Чтобы отключить Alt+Tab, Ctrl+Alt+Del и т. д., нужно написать:

int prev;
SystemParametersInfo(SPI_SCREENSAVERRUNNING, TRUE, &prev, 0);

Выполняя этот код, мы сообщаем системе, что работает скрин сейвер. А когда он работает, переключение задач запрещено. Чтобы включить стандартные комбинации снова, используйте:

int prev;
SystemParametersInfo(SPI_SCREENSAVERRUNNING, FALSE, &prev, 0);

Внимание: если этого не сделать, переключение задач будет невозможно даже после завершения работы вашего приложения!

Перейдём к Windows NT/2000. Там трюк со скрин сейвером не работает, но зато есть низкоуровневые хуки для мыши и клавиатуры (обычные хуки не перехватывают системные комбинации клавиш). Установив глобальный низкоуровневый хук на клавиатуру, можно "съесть" все системные нажатия (кроме Ctrl+Alt+Del). Для этого в ответ на приход таких нажатий функция хука должна вернуть единицу.

Как известно, хуки устанавливаются функцией SetWindowsHookEx. В нашем случае требуется глобальный хук, а значит, его код придётся размещать в DLL. DLL может выглядеть примерно так.

#define _WIN32_WINNT    0x0500
#include <windows.h>

static HINSTANCE hInstance;
static HHOOK     hHook;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    hInstance = (HINSTANCE)hModule;
    return TRUE;
}

LRESULT CALLBACK KeyboardProc(INT nCode, WPARAM wParam, LPARAM lParam);

extern "C" __declspec(dllexportvoid HookKeyboard()
{
   hHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyboardProc,
hInstance, 0);
}

extern "C" __declspec(dllexportvoid UnhookKeyboard()
{
   UnhookWindowsHookEx(hHook);
}

LRESULT CALLBACK KeyboardProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
    KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT *) lParam;
    BOOL bControlKeyDown = 0;

    if(nCode == HC_ACTION)
    {
        bControlKeyDown = GetAsyncKeyState (VK_CONTROL) >>((sizeof(SHORT) * 8) - 1);

        // Проверяем CTRL+ESC
        if (pkbhs->vkCode == VK_ESCAPE && bControlKeyDown)
            return 1;

        // Проверяем ALT+TAB
        if (pkbhs->vkCode == VK_TAB && pkbhs->flags & LLKHF_ALTDOWN)
            return 1;

        // Проверяем ALT+ESC
        if (pkbhs->vkCode == VK_ESCAPE && pkbhs->flags & LLKHF_ALTDOWN)
            return 1;
    }

    return CallNextHookEx (hHook, nCode, wParam, lParam);
}

Чтобы воспользоваться этой DLL, загрузите её любым способом, а затем вызывайте HookKeyboard, чтобы перехватывать комбинации клавиш, и UnhookKeyboard, чтобы прекратить перехват.

В ранних версиях NT низкоуровневых хуков не было. В MSDN утверждается, что там от Alt+Tab там можно избавиться с помощью перерегистрации глобального акселератора на ту же комбинацию (посредством RegisterHotKey), но испытать это средство мне не удалось (нет под рукой NT3.51 или NT4.0 с SP 2 и ниже). Ctrl+Esc там не блокируется.

Для полноты картины упомяну ещё одно непровереное средство, с помощью которого можно обезвредить Ctrl+Alt+Del под Windows NT/2000. Для этого нужно написать собственную GINA DLL. Если кого-нибудь интересуют подробности, сделайте в MSDN поиск по строке "GINA".

- Александр Шаргин

/ / / / ОБРАТНАЯ СВЯЗЬ / / / / / / / / / / / / / / /

Уважаемый Алекс.
Читая Вашу статью о DCOM я прочел:
"Строго говоря, COM не является спецификацией, привязанной к Win32. Теоретически, можно портировать ваши COM-объекты в Unix или любые другие ОС. Однако, я никогда не видел, чтобы COM применялась где-то за пределами сферы влияния Microsoft."
Могу подсказать ОС использующую COM/DCOM не из семейства Windows. Как ни странно это VxWorks, где COM/DCOM существует в виде одного из компонент ядра и обеспечивает все, что может быть положено на концепцию этой ОС.
Например из-за ограничений ОС (там по сути только один процесс с общей памятью, но со многими потоками-задачами) серверы могут быть только INPROC. Не поддержан (пока что) IDispatch, массивы в VARIANT. Зато теперь можно использовать DCOM-распределенные системы на основе смеси Windows и VxWorks, что очень удобно для управления realtime системами.
У Уважением

- Алексей Трошин

На вопрос из выпуска No.27 о пунктирной рамке вокруг кнопки:

Предложенный Александром Шаргиным вариант с тулбаром врядли можно признать удовлетворительным. Диалог не получит сообщение от тулбара да и программное создание кнопки... Можно, конечно, но... :-( . Наиболее приемлемый выход - использование самопрорисовывающихся элементов управления. Достоинство этого метода - нарисовать можно всё, что угодно! :-))). А в вопросе Максима Чучуйко есть ещё подвопрос: А должна ли кнопка вообще получать фокус?.

В общем, плоскую кнопку, не получающую фокус совсем сделать достаточно просто:
1) Создаём класс CFlatButton: public CButton ;
2) Добавляем переменные:
protected:
BOOL bMouseCaptured;
CWnd* pOldFocus;
В конструкторе инициализируем:
bMouseCaptured =FALSE;
pOldFocus =NULL;
3) Добавляем методы:

protected:
void CFlatButton::SetOldFocus()
{
// Закомментировать тело метода, если кнопка может получать фокус.
        if(pOldFocus)
                pOldFocus->SetFocus();
        pOldFocus =NULL;
}

Добавляем обработчики сообщений:

void CFlatButton::OnSetFocus(CWnd* pOldWnd)
{
        CButton::OnSetFocus(pOldWnd);
        if(!pOldFocus)  // Дабы не было проблем с модальными окнами, вызываемыми по нажатию этой кнопки.
                pOldFocus =pOldWnd;
}

void CFlatButton::OnLButtonUp(UINT nFlags, CPoint point)
{
        CButton::OnLButtonUp(nFlags, point);

        CRect rectBtn;
        GetClientRect(rectBtn);

        if(rectBtn.PtInRect(point) && GetCapture() !=this)
        {
                bMouseCaptured =TRUE;
                SetCapture();
                Invalidate(FALSE);
        }
        SetOldFocus();
}

void CFlatButton::OnMouseMove(UINT nFlags, CPoint point)
{
        CRect rectBtn;
        GetClientRect(rectBtn);

        if(rectBtn.PtInRect(point))
        {
                BOOL bNeedUpdate =FALSE;
                if(!bMouseCaptured)
                        bNeedUpdate =TRUE;

                bMouseCaptured =TRUE;
                SetCapture();

                if(bNeedUpdate)
                        Invalidate(FALSE);
        }
        else
        {
                bMouseCaptured =FALSE;
                ReleaseCapture();
                SetOldFocus();
                Invalidate(FALSE);
        }
        CButton::OnMouseMove(nFlags, point);
}

И, самое интересное... :-))) Перекрываем виртуальный метод:

void CFlatButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
        // Test WS_TABSTOP
        ASSERT(!(GetStyle() & WS_TABSTOP));



        CDC* pDC =CDC::FromHandle(lpDIS->hDC);
        CRect rectAll;
        GetClientRect(rectAll);
        CString text;
        GetWindowText(text);
        int save =pDC->SaveDC();
        CRect rectText(rectAll);
        rectText.DeflateRect(2,2);

        CBrush bkBr(GetSysColor(COLOR_3DFACE));
        pDC->FillRect(rectAll,&bkBr);

        UINT state = lpDIS->itemState;
        if (state & ODS_SELECTED)
        {  // Нажатое состояние
                rectText.OffsetRect(1,1);
                pDC->DrawEdge(rectAll,BDR_SUNKENOUTER,BF_RECT);
        }
        else
        {
                if(bMouseCaptured)
                {
                        pDC->DrawEdge(rectAll,BDR_RAISEDINNER,BF_RECT);
                }
        }
        pDC->DrawText(text, rectText, DT_SINGLELINE|DT_VCENTER|DT_CENTER|DT_TOP);
        pDC->RestoreDC(save);
}

Использование: очень просто. Ставим на шаблоне диалога кнопку, убираем стиль WS_TABSTOP, ставим стиль WS_OWNERDRAW. В ClassWizard'е сопоставляем ей переменную типа CButton, затем тип переменной вручную меняем на CFlatButton. И всё. Далее - как с обычной кнопкой. У меня ( VC++ 5.0 ) - работает.

- Дмитрий Сулима

/ / / В ПОИСКАХ ИСТИНЫ / / / / / / / / / / / / / /

Q| Как включать в проект незарегистрированный компонент ActiveX? Вернее он на моей машине зарегистрирован, а на другой нет, и в результате этого программа на той машине вообще не запускается. - Сергей Лобачев

   Ответить на вопрос

/ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /

Это все на сегодня. Удачи вам!

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

Предыдущие выпуски     Статистика рассылки


RLE Banner Network

http://subscribe.ru/
E-mail: ask@subscribe.ru
Поиск

В избранное