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

Пошаговое руководство по написанию расширения

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

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

Что представляют собой расширения оболочки?
Что должно делать расширение контекстного меню ?
Использование AppWizard.
Интерфейс инициализации
Интерфейс взаимодействия с контекстным меню
Модификация контекстного меню
Отображение подсказки в строке состояния
Выполнение выбора пользователя
Регистрация расширения оболочки
Отладка расширения оболочки
Как все это выглядит?
Продолжение следует...

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

Расширение оболочки (shell extension) - это COM объект, который добавляет некоторый вид функциональности к оболочке Windows - проводнику (Explorer). Все типы расширений присутствуют в оболочке, однако слишком мало сопровождающей информации о том, что они из себя представляют и как написать свои собственные расширения. Я рекомендую отличную книгу Dino Esposito "Visual C++ Windows Shell Programming", если вы хотите получить обзор многих аспектов оболочки Windows. Для тех, у кого нет книги или кого интересуют только конкретные расширения, я написал это руководство. Оно поразит вас или по крайней мере обеспечит хорошее понимание того, как писать собственные расширения. Это руководство предполагает, что вы знакомы с основами COM и ATL.

Часть I является введением в расширения оболочки, а также содержит пример расширения контекстного меню.

Что представляют собой расширения оболочки?

Это понятие включает в себя две части: оболочка и расширение. Оболочка (Shell) - это проводник Windows (Explorer), а расширение (extension) - код, который вы пишете, чтобы получить управление проводником в определенный момент (например, при щелчке правой кнопкой мышки на doc-файле). Таким образом, расширение оболочки - это COM объект, который добавляет некоторые особености к проводнику.

Расширение оболочки является внутризадачным (in-process) сервером, который реализует несколько интерфейсов, обрабатывающих связи с проводником. ATL - легчайший путь быстро создать расширение и запустить его, без нее вам пришлось бы много раз вставлять код QueryInterface() и AddRef(). С помощью ATL намного легче отлаживать код расширения в Windows NT и 2000. Как именно это делается, я объясню позже.

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

Типы Когда загружаются Что делают
Обработчик контекстного меню Правый щелчок на файле или папке. В версиях оболочки 4.71+ также правый щелчок на фоне окна, отображающего каталог. Добавляет пункты к контекстному меню.
Обработчик страниц свойств При отображении свойств файлов Добавляет страницы свойств
Обработчик перетаскивания Пользователь перетаскивает файлы правой кнопкой мыши в окно каталога или на рабочий стол Добавляет пункты к контекстному меню, появляющемуся при отпускании кнопки.
Обработчик сбрасывания Пользователь перетаскивает и сбрасывает объекты на файл Любое желаемое действие
Всплывающие описания Указатель мыши находится над файлом или другим объектом Возвращает строку, которая отображается во всплывающей подсказке.

Теперь вам наверно интересно, как выглядят расширения в проводнике. Если у вас есть инсталлированный WinZip (а у кого его нет?), вы можете увидеть много типов расширений. Одно из них - обработчик контекстного меню. Вот что WinZip 8 добавляет к контекстному меню для сжатых файлов:


WinZip содержит код, который добавляет пункты в меню, обеспечивает подсказку (текст, который появляется в строке состояния проводника), и выполняет нужные действия, когда пользователь выбирает одну из команд WinZip. WinZip также содержит обработчик перетаскивания. Этот тип расширения очень похож на расширение контекстного меню, но он загружается, когда пользователь перетаскивает файл, используя правую кнопку мыши. Вот что этот обработчик добавляет к контекстному меню:


Существует много других типов и Microsoft продолжает добавлять их в каждой новой версии Windows. Сейчас мы рассмотрим расширения контекстного меню, поскольку они легко и просто пишутся и в результате красиво смотрятся (одно удовольствие!).

Прежде чем начать кодирование, несколько указаний, которые облегчат нам работу. После загрузки расширение остается на некоторе время в памяти, что делает невозможным повторную загрузку. Чтобы иметь возможность часто перегружать расширение в оболочке, создадим ключ в реестре:

HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

и установим начальное значение = 1. В Windows 9x это самый лучший выход.

В Windows NT/2000 откроем раздел:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer

создадим параметр типа DWORD, назовем его DesktopProcess и установим значение = 1.

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

Как делать отладку в 9x я объясню немного позже.

Что должно делать расширение контекстного меню ?

Давайте создадим и запустим расширение, которое просто выводит сообщение о том, что оно загружено и работает. Мы привяжем его к TXT-файлам, так что наше расширение будет вызываться, когда пользователь щелкнет правой кнопкой мыши на текстовом файле.

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

Я еще не рассказал вам, как использовать интерфейсы расширений оболочки? Не беспокойтесь, я объясню, когда дойду до этого. Это лучше усвоится, если будет сопровождаться примерами. Я объясню понятия и буду сопровождать их кодом примера. Можно было-бы объяснить все сначала, а затем дать код, но я нахожу, что это тяжелее для понимания.

В любом случае запускайте MS VC и мы начнем.

Запустите AppWizard и создайте новый ATL COM проект. Назовем его SimpleExt. Сохраните все установки по-умолчанию и нажмите "Finish". Теперь у нас есть пустой ATL- проект, из которого будет создана DLL, но нам необходимо добавить в него наш COM объект - расширение оболочки.

В дереве ClassView щелкните правой кнопкой на пункте SimpleExt Classes и выберите "New ATL Object". В "ATL Object" - мастере на первой странице уже отмечено "Simple Object", поэтому просто идите дальше. На второй панели введите SimpleShlExt в поле Short Name и щелкните OK. (Други редактируемые поля на панели будут заполнены автоматически). Созданный класс CSimpleShlExt содержит основной код COM - объекта. Мы добавим наш код к этому классу.

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

Когда наше расширение загружается, проводник вызывает функцию QueryInterface(), чтобы получить указатель на интерфейс IShellExtInit. Этот интерфейс имеет только один метод, Initialize():

HRESULT IShellExtInit::Initialize (
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID );

Проводник использует этот метод, чтобы давать нам различную информацию. pidlFolder - это PIDL (pointer to an ID list) папки, содержащей активизированные файлы. (PIDL - это структура данных, которая однозначно идентифицирует любой объект в оболочке, независимо от того, объект это файловой системы или нет). pDataOdj - указатель на интерфейс IDataObject, с помощью которого мы извлекаем имена активизированных файлов. hProgID открывает HKEY, который мы используем, чтобы получить доступ к ключу реестра, содержащему регистрационные данные нашей DLL. Для нашего расширения необходимо использовать параметр pDataObj.

Чтобы добавить IShellExtInit к нашему проекту, откройте SimpleShExt.h и введите выделенные строки:

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

class ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()

COM_MAP- это то, как ATL осуществляет QueryInterface().

Затем в декларации класса добавьте прототип Initialize(). Нам также необходим буфер, чтобы сохранить имя файла:

protected:
    TCHAR m_szFile [MAX_PATH];

public:
    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

В SimpleShlExt.cpp добавьте определение функции:

HRESULT CSimpleShlExt::Initialize ( 
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID )

В ней мы получим имя файла, отмеченного правым щелчком мыши, и покажем его в выводимом сообщении. Если файлов больше одного, мы могли бы получить доступ к ним ко всем через указатель pDataObj, но, поскольку мы хотим упростить работу мы получим только имя первого из них.

Имя файла сохранено в том же самом формате, в каком оно использовалось, когда вы переносили файл, используя стиль WS_EX_ACCEPTFILES. Это означает, что мы можем получить имена файлов используя то же API - DragQueryFile(). Начнем писать функцию, получающую доступ к данным, содержащимся в IDataObject:

{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP     hDrop;

    // Look for CF_HDROP data in the data object.
    if ( FAILED( pDataObj->GetData ( &fmt, &stg )))
        {
        // Nope! Return an "invalid argument" error back to Explorer.
                return E_INVALIDARG;
        }

    // Get a pointer to the actual data.
    hDrop = (HDROP) GlobalLock ( stg.hGlobal );

    // Make sure it worked.
    if ( NULL == hDrop )
        {
        return E_INVALIDARG;
        }

Заметим, что крайне важно избежать любых ошибок, особенно с указателями. поскольку наше расширение запускается в пространстве процесса проводника, и, если мы ошибемся, то разрушим проводник. В Win 9х такая ошибка приводит к необходимости перезагружать компьютер.

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

// Sanity check – make sure there is at least one filename.
UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );

    if ( 0 == uNumFiles )
        {
        GlobalUnlock ( stg.hGlobal );
        ReleaseStgMedium ( &stg );
        return E_INVALIDARG;
        }

HRESULT hr = S_OK;

    // Get the name of the first file and store it in our member variable m_szFile.
    if ( 0 == DragQueryFile ( hDrop, 0, m_szFile, MAX_PATH ))
        {
        hr = E_INVALIDARG;
        }

    GlobalUnlock ( stg.hGlobal );
    ReleaseStgMedium ( &stg );

    return hr;
}

Если мы возвратим E_INVALIDARG, проводник не вызовет наше расширение повторно при правом щелчке мыши. Если мы возвратим S_OK, проводник вызовет QueryInterface() еще раз и получит указатель на другой интерфейс, который мы добавим - IContextMenu.

Интерфейс взаимодействия с контекстным меню

Как только проводник инициализировал наше расширение, он вызовет методы IContextMenu, позволяющие добавить пункты в меню, обеспечивающие подсказку в строке состояния и выполняющие выбор пользователя.

Добавление IContextMenu к нашему расширению подобно добавлению IShellExtInit. Откроем SimpleShlExt.h и добавим выделенные строки:

class ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit,
    public IContextMenu
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
    COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()

А затем добавим прототипы методов IContextMenu:

public:
    // IContextMenu
    STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
    STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT);

Модификация контекстного меню

IContextMenu имеет 3 метода. Первый из них, QueryContextMenu(), позволяет модифицировать меню. Вот его прототип:

HRESULT IContextMenu::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags );

hmenu это дескриптор контекстного меню. uMenuIndex - позиция, с которой мы должны начать добавлять свои пункты. uidFirstCmd и uidLastCmd - интервал идентификаторов команд, которые мы можем использовать для наших пунктов меню. uFlags указывает, почему проводник вызывает QueryContextMenu(), но об этом позже.

Описания возвращаемого значения различны в различных источниках. В книге Dino Esposito говорится, что это число пунктов меню, добавленных QueryContextMenu(). Библиотека MSDN, поступающая с VC6, говорит, что это ID последнего пункта меню, который мы добавим, плюс 1. Поздние on-line документы MSDN говорят:

"Установите значение кода (возвращаемого через HRESULT) равным значению самого большего идентификатора команды, плюс 1. Например, idCmdFirst установлен в 5 и вы добавляете к меню 3 пункта с ID = 5, 7 и 8. Возвращаемое значение должно быть MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)."

Я следовал объяснениям Dino в коде, написанном мною. Его метод получения возвращаемого значения эквивалентен методу on-line MSDN до тех пор, пока вы считаете количество пунктов вашего меню с помощью uidFirstCmd и прибавляете 1 для каждого добавленного пункта. Наше простое расширение будет иметь только один пункт, поэтому функция QueryContextMenu() совсем проста:

HRESULT CSimpleShlExt::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags )
{
    // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
    if ( uFlags & CMF_DEFAULTONLY )
        {
        return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
        }

    InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item") );

    return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}

Первое, что мы делаем - это проверяем uFlags. Вы можете найти полный список флагов в MSDN, но для контекстного меню важно только одно значение - CMF_DEFAULTONLY. Этот флаг сообщает расширениям, что добавляются только пункты меню по-умолчанию. Расширение ничего не должно добавлять, если этот флаг присутствует. Вот почему мы немедленно возвращаем 0, если выставлен это флаг. Если этого флага нет, мы модифицируем меню (используя hmenu), и возвращаем 1, чтобы сообщить оболочке, что мы добавили 1 пункт меню.

Отображение подсказки в строке состояния

Следующий метод IContextMenu, который может быть вызван, это GetCommandString(). Если пользователь щелкает правой кнопкой на текстовом файле в окне проводника (в правом окне, если режим двухоконный), или укажет текстовый файл, а затем щелкнет на пункте меню File, то в строке состояния появится подсказка. Наша GetCommandString() возвратит строку, отображаемую в подсказке.

Прототип GetCommandString():

HRESULT IContextMenu::GetCommandString (
    UINT idCmd,
    UINT uFlags,
    UINT *pwReserved,
    LPSTR pszName,
    UINT cchMax );

где idCmd - начинающийся с 0 счетчик, который указывает, какой пункт меню отмечен. Поскольку у нас только один пункт меню, idCmd всегда будет =0. Но если у нас есть изначально, скажем, 3 пункта меню, то idCmd может быть равен 0, 1 или 2. uFlags - другая группа флагов, которую я опишу позже. pwReserved можно проигнорировать. pszName - указатель на собственный буфер оболочки, где мы сохраним строку подсказки, которая будет высвечиваться. cchMax - это размер буфера. Возвращаемое функцией значение - одна из обычных констант HRESULT - S_OK или E_FAIL.

GetCommandString() также может быть вызвана, чтобы получить "действие" ("verb") от пункта меню. "Действие" - независимая от языка строка, которая идентифицирует операцию, производимую с файлом. Документы по ShellExecute() расскажут об объекте "действие" больше и это тема для другой статьи, но сокращенная версия такова: "действия" могут быть внесены в список в реестре ("open", "print") или могут быть созданы расширением динамически . Это позволяет вызывать расширение при выполнении ShellExecute().

Во всяком случае, причина по которой я обо всем этом упомянул состоит в том, что нам нужно определить, для чего вызывается GetCommandString(). Если проводник требует строку подсказки, мы дадим ее. Если проводник потребует "действие" мы проигнорируем его запрос. Это как раз тот момент, когда параметр uFlags становится значимым. Если в параметре uFlags установлен бит GCS_HELPTEXT то проводник запрашивает подсказку. Если установлен бит GCS_UNICODE , мы должны возвратить строку в UNICODE.

Код нашей GetCommandString() выглядит так:

#include <atlconv.h>  // for ATL string conversion macros

HRESULT CSimpleShlExt::GetCommandString (
    UINT  idCmd,
    UINT  uFlags,
    UINT* pwReserved,
    LPSTR pszName,
    UINT  cchMax )
{
    USES_CONVERSION;

    // Check idCmd, it must be 0 since we have only one menu item.
    if ( 0 != idCmd )
        return E_INVALIDARG;

    // If Explorer is asking for a help string, copy our string into the
    // supplied buffer.
    if ( uFlags & GCS_HELPTEXT )
        {
        LPCTSTR szText = _T("This is the simple shell extension's help");

        if ( uFlags & GCS_UNICODE )
            {
            // We need to cast pszName to a Unicode string, and then use the
            // Unicode string copy API.
            lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax );
            }
        else
            {
            // Use the ANSI string copy API to return the help string.
            lstrcpynA ( pszName, T2CA(szText), cchMax );
            }

        return S_OK;
        }

    return E_INVALIDARG;
}

Здесь нет ничего фантастического. Я просто преобразовываю строку в подходящий набор символов. Если вы никогда не использовали ATL макросов конвертирования, вы определенно должны прочитать о них, т.к. они намного облегчают жизнь, когда необходимо передать строку UNICODE COM-методам и OLE-функциям. Я использую T2CW и T2CA в коде, приведенном выше, чтобы преобразовать строку в UNICODE и ANSI соответственно. Макрос USES_CONVERSION в начале функции объявляет локальную переменную, которая используется в конверсионных макросах.

Заметим одну важную вещь: lstrcpyn() гарантирует, что строка назначения будет завершена нулем. В этом ее отличие от CRT-функции strncpy(), которая не добавляет завершающий 0, если длина исходной строки больше или равна cchMax. Я предлагаю всегда использовать lstrcpyn(), и вам не нужно будет проверять конец строки после каждого вызова strncpy(), чтобы убедиться, что она закрыта нулем.

Выполнение выбора пользователя

Последний метод в IContextMenu - это InvokeCommand(). Это метод вызывается, когда пользователь щелкнет на пункте меню, который мы добавили. Прототип для InvokeCommand():

HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

Структура CMINVOKECOMMANDINFO содержит целую тонну информации. Но для наших целях важно позаботиться только о lpVerb и hwnd. lpVerb имеет двойное назначение - это может быть имя "действия" (см.выше), или индекс, сообщающий нам, какой из пунктов нашего меню был отмечен щелчком. hwnd - дескриптор окна проводника, в котором пользователь вызвал наше расширение.

Поскольку у нас только один пункт меню, мы осуществим проверку lpVerb. Если это 0, мы будем знать, что это наш пункт меню был отмечен. Самая простая вещь, которую я смог придумать - это высвечивать сообщение о том, что делает этот код. Сообщение показывает имя отмеченного файла и это докажет нам, что все действительно работает.

HRESULT CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
    // If lpVerb really points to a string, ignore this function call and bail out.
    if ( 0 != HIWORD( pCmdInfo->lpVerb ))
        return E_INVALIDARG;

    // Get the command index - the only valid one is 0.
    switch ( LOWORD( pCmdInfo->lpVerb ))
        {
        case 0:
            {
            TCHAR szMsg [MAX_PATH + 32];

            wsprintf ( szMsg, _T("The selected file was:\n\n%s"), m_szFile );

            MessageBox ( pCmdInfo->hwnd, szMsg, _T("SimpleShlExt"),
                         MB_ICONINFORMATION );

            return S_OK;
            }
        break;

        default:
            return E_INVALIDARG;
        break;
        }
}

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

Итак, мы осуществили все интерфейсы, но как сделать, чтобы проводник мог использовать наше расширение? ATL автоматически генерирует код, который регистрирует нашу DLL как COM сервер, но это всего лишь позволит другим приложениям использовать нашу DLL. Для того чтобы сообщить проводнику, что наше расширение существует, мы должны зарегистрировать его в секции, которая содержит информацию о TXT-файлах:

HKEY_CLASSES_ROOT\txtfile

Под этим ключом укажем ключ ShellEx , содержащий список расширений оболочки, вызываемых для TXT-файлов. Под ключом ShellEx ключ ContextMenuHandlers содержит список расширений контекстного меню. Каждое расширение создает свой субключ под ключом ContextMenuHandlers и устанавливает значение этого ключа по своему GUID. Например, для нашего простого расширения, создадим ключ:

HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

и установим его default значение согласно нашему GUID:"{5E2121EE-0300-11D4-8D3B-444553540000}".

Однако вы не должны писать любой код, чтобы это сделать. Посмотрите список файлов и найдите файл SimpleShlExt.rgs. Это текстовый файл предназначен для ATL и указывает, какие ключи регистрации добавлять, когда сервер регистрируется, и какие удалять, когда сервер разрегистрируется.

Вот как выглядят данные для регистрации:

HKCR
{
    NoRemove txtfile
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove SimpleShlExt = s '{5E2121EE-0300-11D4-8D3B-444553540000}'
            }
        }
    }
}

Каждая строка является именем ключа реестра. HKCR - сокращение для HKEY_CLASSES_ROOT. Ключевое слово NoRemove означает, что ключ не должен быть удален, когда сервер разрегистрируется. Последняя строка немного более сложная. Ключевое слово ForceRemove означает, что если ключ существует, он будет удален прежде, чем новый ключ записан. Рассмотрим остальную часть строки. Символ s означает, что подстрока, указанная после него будет сохранена, как значение по умолчанию для ключа SimpleShlExt.

Рассмотрим подробнее этот момент. Ключ, под которым мы зарегистрировали наше расширение - это HKCR\txtfile. Однако имя "txtfile" не является постоянным или предрешенным. Если вы посмотрите в HKCR\.txt, где было сохранено имя, то убедитесь, что это значение этого ключа по умолчанию. Это имеет два побочных эффекта:

Мне это кажется проектным недостатком. Я думаю, Microsoft думает также, т.к. недавно созданы расширения, подобные QueryInfo, зарегистрированные непосредственно под ключом .txt.

ОК. Конец статьи. Последняя деталь о регистрации. В Windows NT/2000 мы также должны внести наше расширение в список "одобренных" расширений. Если мы этого не сделаем, наше расширение не будет доступно пользователю, который не имеет прав администратора. Этот список находится в

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell 
  Extensions\Approved

В этом ключе мы создаем string переменную, чье имя - это наш GUID. Содержание строки может быть любым. Код, выполняющий это действие, входит в наши функции DllRegisterServer() и DllUnregisterServer() . Я не буду приводить его здесь, т.к. он слишком прост, но вы должны найти его в демонстрационном проекте.

Отладка расширения оболочки

В конечном счете вы напишете расширение, которое может быть далеко не простым, и вам придется его отлаживать. Откройте установки вашего проекта и на закладке Debug укажите полный путь к проводнику в окне "Remote executable path and file name", например "C:\windows\explorer.exe". Если вы используете Windows 2000 и установили DesktopProcess как указывалось выше, нажмите F5, откроется окно проводника и вы сможете начать отладку. Пока вы будете делать свою работу, вы не будете испытывать проблем с пересозданием DLL, т.к. когда вы закроете это окно, ваше расширение будет выгружено.

В Windows 9x вы должны отключить оболочку перед запуском отладчика. Щелкните Start и затем ShutDown. Держите нажатыми Ctrl+Alt+Shift и щелкните Cancel. Это отключит проводник и вы увидите, что панель задач исчезла. Вы можете вернуться в VC и нажать F5, чтобы начать отладку.

Для остановки сеанса отладки нажмите Shift+F5, чтобы закрыть проводник. Когда отладка будет завершена, вы можете запустить проводник с коммандной строки и нормально загрузить оболочку.

Как все это выглядит?

Так выглядит контекстное меню после добавления нашего пункта:


Наш пункт здесь!

Так выглядит строка статуса с выведенной подсказкой:


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


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

Во второй части будет рассмотрено новое расширение контекстного меню, которое покажет, как действовать в случае нескольких отмеченных файлов.


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