[Статья] Создание расширения для Windows Explorer при помощи C++ Builder

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Dobby007, 28 Mar 2009.

  1. Dobby007

    Dobby007 Elder - Старейшина

    Joined:
    7 Sep 2008
    Messages:
    52
    Likes Received:
    16
    Reputations:
    1
    [СТАТЬЯ] Создание расширения для Windows Explorer при помощи C++ Builder

    [ИНТРО]
    Привет всем. Сегодня я расскажу вам об программном расширении оболочки Форточек при помощи C++ Builder’а. Сразу говорю, что это моя первая статья. Так что не обессудьте если что-то не совсем правильно будет написано.
    А теперь немного истории. Решил я написать данную статью, так как сам просидел очень долгое время, собирая по кусочкам информацию, разбросанную по всем уголкам интернета. Может быть и сильно громко сказано, но это в действительности так и было. Итак, начнем-с, пожалуй…

    [ЧТО НАМ ПОНАДОБИТСЯ]
    Ну во-первых конечно же сама установленная среда C++ Builder. Во-вторых, прямые (желательно длинные) руки и здравомыслящая голова :). А, в третьих, упорство и терпение. Вот пожалуй и весь набор начинающего программера…

    [ЧЕГО МЫ ХОТИМ ДОБИТЬСЯ]
    Именно в данной статье мы рассмотрим простой пример создания расширения для Эксплорера, дополняющий некоторые возможности к контекстному меню, появляющегося при клике на определенный файл, как это показано на рисунке:
    [​IMG]

    [ПОДГОТОВКА]
    Итак, запускаем С++ Builder. В-принципе не играет особой роли какой он версии (только будет некоторые различия в интерфейсе). Но я использую Codegear C++ Builder 2007.
    [​IMG]
    Нажимаем File->New->Other->ActiveX->ActiveX Library. Мы увидим уже стандартный уже написанный за нас код, являющийся основой для любого ActiveX-контрола. Теперь снова заходим File->New->Other->ActiveX. Сейчас здесь уже появились дополнительные пункты. Но нам из них нужен только Com Object. Выбираем его из списка и видим следующее диалоговое окно:
    [​IMG]
    Вводим в качестве CoClassName MyFirstContextMenu. Вы конечно можете другое имя, но, чтобы не было каких-либо дальнейших вопросов по написанию кода, рекомендую написать там все же именно это.
    Теперь сохраняем проект в любую папку и идем дальше.

    [Main.h (по другому не назовешь :) )]
    [​IMG]
    Немного оглянувшись по файлам проекта, заходим Main.h и в самый нижний паблик добавляем следующий код:
    Code:
    public:
    	STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);
    	STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
    	STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
    	STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT);
    Что же сие означает? Это обыкновенное объявление функций обязательных для создания расширения. Рассмотрим каждую из них по-подробней…
    Initialize – инициализирует наше расширение. Из нее мы можем прочитать такие параметры имя файла, дополнительные флаги, переданные нам для дальнейшего их использования нами.
    QueryContextMenu – возвращает проводнику наши созданные пункты меню.
    InvokeCommand – задает команду для каждого пункта меню.
    GetCommandString – возвращает провднику строку-подсказку, которая затем появляется в строке состояния при наведении курсора на какой-либо пункт меню.


    Теперь поднимемся немного выше и под строчкой COM_INTERFACE_ENTRY(IMyFirstContextMenu) пишем:
    Code:
      COM_INTERFACE_ENTRY(IShellExtInit)
      COM_INTERFACE_ENTRY(IContextMenu)
    А в самом объявлении класса изменяем код на следующий:
    Code:
    class ATL_NO_VTABLE TMyFirstContextMenuImpl :
      public CComObjectRootEx<CComSingleThreadModel>,
      public CComCoClass<TMyFirstContextMenuImpl, &CLSID_MyFirstContextMenu>,
      public IMyFirstContextMenu,
      public IShellExtInit,
      public IContextMenu

    Эти строчки сообщают проводнику, что «эта DLL есть не что иное, как расширение контекстного меню». Также необходимо заиклудить shlobj.h:
    #include <shlobj.h>
    Во избежание лишних проблем и ошибок типа Multiple Declaration заходим в Project->Options и добавляем в Conditional Defines дефайн NO_WIN32_LEAN_AND_MEAN.
    [​IMG]
    Ну с Main.h мы вроде как закончили пока что. Теперь переходим к редактированию не менее важного файла Main.cpp ^)…

    [Main.cpp]
    Ну как обычно не забываем сохранять проект… А то… мало ли что?... ?
    Открываем Main.cpp и сначала инклудим файлы:
    Code:
    #include "stdio.h"
    #include <windows.h>
    #include <ComServ.hpp>
    #include <ComObj.hpp>
    #include <ActiveX.hpp>
    #include <ShlObj.h>
    #include <ShlObj.hpp>
    #include <Menus.hpp>
    #include <ShellAPI.hpp>
    #include <SysUtils.hpp>
    #include <registry.hpp>
    #include <atlconv.h>
    Все это стандартные файлы, которые описания не требуют, кроме atlconv.h. Данный файлик нужен нам для дальнейшей конвертации строк в юникод. Иначе «подсказка» в строке состояния будет отображаться криво и крякозябрами.
    Теперь нужно прописать метод initialize. Начнем мы именно с нее, так как эта функция является первой, на которую ссылается эксплорер при загрузке DLL.


    Code:
    UINT uNumFiles=0; // количество переданных файлов
    char szFile[MAX_PATH];
    TStringList *sel_files = new TStringList; // «массив» содержащий пути к файлам
    HResult _stdcall TMyFirstContextMenuImpl::Initialize(LPCITEMIDLIST pidlFolder,LPDATAOBJECT pDataObj,HKEY hProgID)
    {
    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };
    HDROP     hDrop;
    	if ( FAILED( pDataObj->GetData ( &fmt, &stg )))
            {
    				return E_INVALIDARG;
            }
    	hDrop = (HDROP) GlobalLock ( stg.hGlobal );
    	if ( NULL == hDrop )
            {
            return E_INVALIDARG;
    		}
    uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );
           if ( 0 == uNumFiles )
            {
            GlobalUnlock ( stg.hGlobal );
            ReleaseStgMedium ( &stg );
            return E_INVALIDARG;
          }
    …
    В этой части кода идут проверки (на передачу неправильных аргументов, на нулевое количество выделенных файлов в проводнике и т.д.), а также «запоминание» файлов для дальнейшего их добавления в sel_files. Следует отметить, что переменная uNumFiles и содержит это самое количество выделенных файлов.
    Code:
    …
    HRESULT hr = S_OK;
    for (UINT i = 0; i < uNumFiles; i++) {
    if ( 0 == DragQueryFile ( hDrop, i, szFile, MAX_PATH ))
            {
    		hr = E_INVALIDARG;
    		break;
            }
    sel_files->Add(szFile);
    }
        GlobalUnlock ( stg.hGlobal );
        ReleaseStgMedium ( &stg );
        return hr;
    }
    Здесь особо ничего, я так думаю, объяснять не надо. Просто цикл, который поочередно добавляет пути к файлам в наш «массив» из szFile, которая в свою очередь заполняется вызовом DragQueryFile. Перед добавлением в массив также можно реализовать проверку на «фшивость», так сказать. Ну т.е. действительно ли был выделен тот или иной файл, а уж затем и добавлять файл в список «правильных».
    С инициализацией закончили. Теперь перейдем к построению нашего меню. Но для начала рассмотрим формат функции InsertMenu.
    Вот как она объявлена в winuser.h:
    Code:
    InsertMenuW(
        __in HMENU hMenu,
        __in UINT uPosition,
        __in UINT uFlags,
        __in UINT_PTR uIDNewItem,
        __in_opt LPCWSTR lpNewItem);
    Аргумент hMenu указывает на структуру HMENU, которая содержит наши «айтемы», их типы, флаги и т.п. uPosition указывает на позицию того или иного пункта, т.е. номер пункта в меню. Переменная uFlags содержит в себе флаги для определенного пункта меню. Флагов может быть несколько (отделяются он с помощью “|”), но обычно это MF_STRING | MF_BYPOSITION. Именно MF_BYPOSITION указывает обработчику , что пункт надо вставить под номером uPosition. uIDNewItem содержит идентификатор пункта меню который мы задаем сами для себя. Это необходимо, чтобы потом узнать куда кликнул юзверь. lpNewItem содержит название нашего добавляемого пункта.
    Ну этого хватит для общего ознакомления с данной функцией. Если же есть желание «поизучать» и узнать более конкретные вещи о ней , то тебе прямая дорога на мсдн: . Теперь напишем долгожданную функ...: by_dobby007_for_antichat.ru Bye-bye… ;)
     
    #1 Dobby007, 28 Mar 2009
    Last edited: 30 Mar 2009
    6 people like this.
  2. _Kris_

    _Kris_ Elder - Старейшина

    Joined:
    22 Jul 2008
    Messages:
    53
    Likes Received:
    31
    Reputations:
    5
    Молодец.

    Перенесите пожалуйста в раздел мини - статьи имхо достойная статья.
     
  3. Dobby007

    Dobby007 Elder - Старейшина

    Joined:
    7 Sep 2008
    Messages:
    52
    Likes Received:
    16
    Reputations:
    1
    Да я сам там сначала хотел написать. Но че-то мне показалась большая она сильно - для мини-статьи :D
    Не знаю, вам решать конечно...
     
    1 person likes this.