Руководство полного идиота по написанию расширений оболочки - Часть III

Расширение оболочки, показывающее всплывающую подсказку о файлах

Автор: Michael Dunn
Перевод: Инна Кирюшкина
Алексей Кирюшкин

Источник: The Code Project
Опубликовано: 31.05.2001
Версия текста: 1.1

Расширение QueryInfo
Что должно делать это расширение?
Использование AppWizard
Интерфейс инициализации
Создание текста для тултипа
Регистрация расширения оболочки
Продолжение следует...

Демонстрационный проект

В первых двух частях руководства я объяснял, как написать расширение - обработчик контекстного меню. В части III мы рассмотрим другой тип расширений, я объясню, как использовать память, разделяемую с оболочкой и покажу, как использовать MFC вместе с ATL.

Часть III предполагает, что у вас есть базовый уровень знаний по расширениям оболочки (данный в части I), а также, что вы знакомы с MFC. Рассматриваемое в этой части расширение требует версии оболочки 4.71 или выше. Поэтому вы должны использовать Windows 98 или 2000, либо установить Active Desktop под Windows 95 или NT4.

Расширение QueryInfo

Active Desktop имеет одну новую особенность - когда вы перемещаете указатель мыши над объектом, появляется всплывающая подсказка - тултип, описывающая этот объект. Например, перемещение мыши над объектом "Мой компьютер" вызовет появление тултипа:


Другие объекты, такие как "Сетевое окружение" и "Панель управления", имеют аналогичные тултипы. Используя расширение QueryInfo мы можем внести свою собственную информацию в строку описания для объектов оболочки.

Замечание по поводу имени расширения - "QueryInfo". Это название придумано мною. Я так назвал расширение после применения интерфейса IQueryInfo. Это не официальное название. Я смотрел MSDN за октябрь 1999 года и не смог найти даже ссылку на этот тип расширений. Однако это расширение определенно предусмотрено, так как Microsoft Office использует их для своих типов файлов:


WinZip версии 8 также использует расширение QueryInfo для сжатых файлов:


Самая лучшая документация, которую я нашел - это статья Dino Esposito "Enhance Your User's Experience with New Infotip and Icon Overlay Shell Extensions" в журнале MSDN за март 2000 года.

Что должно делать это расширение?

Это расширение реализует быстрый просмотр текстовых файлов. Оно будет отображать в тултипе первую строку файла и его размер. Информация будет появляться во время перемещения курсора над TXT-файлами.

Использование AppWizard

Запустите AppWizard и создайте новый ATL COM проект. Назовем его TxtInfo. Поскольку в этот раз мы собираемся использовать MFC, нужно проверить переключатель Support MFC, он должен быть включен. Затем щелкните Finish. Чтобы добавить COM объект к DLL, перейдите в дерево просмотра классов, ClassView, щелкните правой кнопкой на пункте TxtInfo classes и укажите New ATL Object.

В мастере ATL объектов, на первой панели уже указан Simple Object, поэтому просто щелкните Next. Во второй панели, в поле редактирования Short Name введите краткое имя TxtInfoShlExt и щелкните OK. (Остальные поля заполняются автоматически.) Мы создали класс CTxtInfoShlExt, который содержит основной код для реализации объекта COM. Добавим свой код к этому классу.

Если вы внимательно посмотрите на дерево ClassView, то увидите, что появился новый класс, CTxtInfoApp, производный от CWinApp. Присутствие этого класса и глобальной переменной theApp делает библиотеку MFC доступной для нас, как будто мы пишем нормальную MFC DLL.

Интерфейс инициализации

Раньше, для предыдущих расширений, мы реализовывали интерфейс IShellExtInit, чтобы проводник мог инициализировать наши объекты. Для некоторых объектов оболочки используется другой инициализационный интерфейс - IPersistFile, и QueryInfo - одно из них. Чем отличаются эти интерфейсы? Если вы помните, IShellExtInit::Initialize() получает указатель на IDataObject, с помощью которого можно перечислить все выбранные файлы. IPersistFile используется для тех расширений, которые оперируют только одним файлом. Так как курсор в определенный момент времени может находиться только в области одного объекта, QueryInfo использует для инициализации IPersistFile.

Первым делом добавим IPersistFile в список интерфейсов, которые реализуются CTxtInfoShlExt. Откройте файл TxtInfoShlExt.h и добавьте выделенные строки:

#include <comdef.h>
#include <shlobj.h>

class ATL_NO_VTABLE CTxtInfoShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
    public IDispatchImpl<ITxtInfoShlExt, &IID_ITxtInfoShlExt, &LIBID_TXTINFOLib>,
    public IPersistFile
{
BEGIN_COM_MAP(CTxtInfoShlExt)
    COM_INTERFACE_ENTRY(ITxtInfoShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IPersistFile)
END_COM_MAP()

Нам также понадобится переменная для сохранения имени файла, которое передаст нам проводник:

protected:
    // ITxtInfoShlExt
    CString m_sFilename;

Обратите внимание, что мы можем использовать объекты MFC везде, где нам захочется.

В документации на IPersistFile описано много методов, но к счастью нам достаточно реализовать только Load(). Вот прототипы методов IPersistFile:

public:
    // IPersistFile
    STDMETHOD(GetClassID)(LPCLSID)      { return E_NOTIMPL; }
    STDMETHOD(IsDirty)()                { return E_NOTIMPL; }
    STDMETHOD(Load)(LPCOLESTR, DWORD);
    STDMETHOD(Save)(LPCOLESTR, BOOL)    { return E_NOTIMPL; }
    STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; }
    STDMETHOD(GetCurFile)(LPOLESTR*)    { return E_NOTIMPL; }

Все методы, кроме Load() возвращают E_NOTIMPL в знак того, что они не реализованы.

Скрашивает ситуацию также то, что в действительности метод Load() очень прост. Он сохраняет имя файла, переданное ему проводником. Это как раз тот файл, в области которого находится курсор.

HRESULT CTxtInfoShlExt::Load ( LPCOLESTR wszFilename, DWORD dwMode )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());    // init MFC

    // Let CString convert the filename to ANSI if necessary.
    m_sFilename = wszFilename;

    return S_OK;
}

Первая строка в начале функции необходима для корректной работы DLL. Поскольку наша DLL будет загружена не-MFC приложением, каждая экспортируемая функция, использующая MFC, должна инициализировать MFC вручную. Если вы не включите эту строку, многие MFC-функции (в основном функции, работающие с ресурсами) прекратят свою работу.

Имя файла сохраняется в m_sFilename для дальнейшего использования. Заметьте, какие я имею преимущества с оператором присваивания CString по конвертированию строки в ANSI, если DLL собирается как ANSI.

Создание текста для тултипа

После вызова Load() проводник вызывает QueryInterface() для получения другого интерфейса, IQueryInfo. Это приятный, простой интерфейс из двух методов, из которых в действительности используется только один. Снова откройте TxtInfoShlExt.h и добавьте выделенные строки:

class ATL_NO_VTABLE CTxtInfoShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>,
    public IDispatchImpl<ITxtInfoShlExt, &IID_ITxtInfoShlExt, &LIBID_TXTINFOLib>,
    public IPersistFile,
    public IQueryInfo
{
BEGIN_COM_MAP(CTxtInfoShlExt)
    COM_INTERFACE_ENTRY(ITxtInfoShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IPersistFile)
    COM_INTERFACE_ENTRY(IQueryInfo)
END_COM_MAP()

Затем добавьте прототипы для методов IQueryInfo:

// IQueryInfo
    STDMETHOD(GetInfoFlags)(DWORD*)     { return E_NOTIMPL; }
    STDMETHOD(GetInfoTip)(DWORD, LPWSTR*);

Метод GetInfoFlags() в данный момент не используется, поэтому мы возвращаем E_NOTIMPL. Метод GetInfoTip() передаст проводнику текст для отображения в тултипе. Начало несколько скучное:

HRESULT CTxtInfoShlExt::GetInfoTip (
    DWORD   dwFlags,
    LPWSTR* ppwszTip )
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());    // init MFC

    LPMALLOC   pMalloc;
    CStdioFile file;
    DWORD      dwFileSize;
    CString    sFirstLine;
    BOOL       bReadLine;
    CString    sTooltip;

    USES_CONVERSION;

Сначала мы вызываем AFX_MANAGE_STATE для инициализации MFC. Это нужно сделать в первую очередь, еще до объявления переменых, т.к. конструкторы будут вызывать MFC-функции.

dwFlags в данный момент не используется. ppwszTip - указатель на LPWSTR (указатель на строку UNICODE), который изначально будет указывать на буфер, который мы должны распределить. В первую очередь мы попытаемся открыть файл для чтения. Имя файла мы знаем, так как сохранили его в Load().

    if ( !file.Open ( m_sFilename , CFile::modeRead | CFile::shareDenyWrite ))
        return E_FAIL;

Теперь нам необходим указатель на интерфейс IMalloc, чтобы распределить буфер в разделяемой памяти оболочки. Этот указатель мы можем получить вызвав функцию SHGetMalloc():

    if ( FAILED( SHGetMalloc ( &pMalloc )))
        return E_FAIL;

Более подробно о IMalloc я расскажу позже. Дальше нам надо получить размер файла и первую строку:

    // Get the size of the file.
    dwFileSize = file.GetLength();

    // Read in the first line from the file.
    bReadLine = file.ReadString ( sFirstLine );

Если к файлу был получен доступ, и он не пустой, bReadLine примет значение TRUE. На следующем шаге мы создаем первую часть строки описания, которая отобразит размер файла:

    sTooltip.Format ( _T("File size: %lu"), dwFileSize );

Теперь, если мы смогли прочитать первую строку файла, добавляем ее к строке описания:

    if ( bReadLine )
        {
        sTooltip += _T("\n");
        sTooltip += sFirstLine;
        }

После того, как мы создадим строку описания, нужно поместить ее в буфер. Для этого используем IMalloc. Указатель, возвращаемый SHGetMalloc() - это копия интерфейса оболочки IMalloc. Вся память, которую мы выделяем с помощью этого интерфейса, находится в пространстве процесса оболочки, поэтому проводник имеет к ней доступ. Более того, оболочка может сама ее освободить. Так что мы размещаем буфер и забываем об этом. Оболочка сама освободит память сразу после окончания ее использования.

Мы должны помнить, что строка, которую мы передаем оболочке должна быть в UNICODE. Вот почему в выражении Alloc() (см. код ниже) есть умножение на sizeof(wchar_t). При выделении памяти просто для lstrlen(sToolTip) будет выделена только половина требуемого количества памяти.

    *ppwszTip = (LPWSTR) pMalloc->Alloc ( (1 + lstrlen(sTooltip)) * sizeof(wchar_t) );

    if ( NULL == *ppwszTip )
        {
        pMalloc->Release();
        return E_OUTOFMEMORY;
        }

    // Use the Unicode string copy function to put the tooltip text in the buffer.
    wcscpy ( *ppwszTip, T2COLE((LPCTSTR) sTooltip) );

И, наконец, нужно освободить интерфейс IMalloc, полученный ранее.

    pMalloc->Release();
        return S_OK;
}

Вот и все! Проводник берет строку в *ppwszTip и отображает ее в тултипе.


Регистрация расширения оболочки

Регистрация расширений QueryInfo немного отличается от регистрации расширений - обработчиков контекстного меню. Наше расширение регистрируется под ключом HKEY_CLASSES_ROOT. Имя ключа содержит расширение файла, которое мы будем обрабатывать. В нашем случае это .txt. То есть ключ HKCR\.txt. Вы вероятно считаете, что подключ ShellEx по идее должен походить на "TooltipHandlers". Ничего подобного! Ключ называется "{00021500-0000-0000-C000-000000000046}".

Я думаю, Microsoft пытается протащить некоторые расширения оболочки мимо нас! Если вы тщательно пороетесь в реестре, вы найдете и другие подключи ShellEx, именами которых являются GUID'ы. Выше указан GUID для IQueryInfo.

Итак, вот необходимый RGS скрипт для загрузки нашего расширения на TXT файлах:

HKCR
{
    NoRemove .txt
    {
        NoRemove shellex
        {
            NoRemove {00021500-0000-0000-C000-000000000046} = s '{F4D78AE1-05AB-11D4-8D3B-444553540000}'
        }
    }
}

Вы легко можете сделать загрузку расширения на других типах файлов. Нужно только продублировать указанный скрипт и везде изменить .txt на расширение, которое вам необходимо. К сожалению невозможно зарегистрировать расширение QueryInfo под ключами * или AllFileSystemObjects, чтобы оно загружалось на любых файлах.

Как и для предыдущих расширений, на NT и Win2000 нужно добавить наше расширение в список "одобренных" расширений. В функциях DllRegisterServer() и DllUnregisterServer() можно найти код, выполняющий эту операцию (см. демонстрационный проект).

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

В части IV мы возвратимся в мир контекстных меню и рассмотрим новый тип расширений - обработчик перетаскивания.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.