Расширение системного контекстного меню Windows Расширение оболочки — удивительно мощный элемент Windows, позволяющий разработчику создавать максимально комфортные и удобные для пользователя приложения. Ты постоянно встречаешься с разными его проявлениями — например, с новыми пунктами контекстного меню: "Проверить" — у антивируса, "Extract" — у WinRAR, новые тулбары в панели задач и т.д. В этом материале я покажу, как легко с помощью Delphi создать свои расширения, добавив пункт "Зашифровать" к контектному меню всех файлов. [COM-объекты] Начнем мы, пожалуй, с того, что любое расширение оболочки реализуется с помощью COM-объекта. COM или Component Object Model (Модель Многокомпонентных Объектов) является основой для технологий ActiveX и OLE. COM определяет API и двоичные стандарты для связи объектов, не зависящих от языка программирования или платформы. COM-объект имеет один или несколько интерфейсов, которые, по сути, представляют собой таблицы функций, связанных с этим объектом. COM определяет стандарты для расположения в памяти функций объектов — они располагаются в виртуальных таблицах. Описание каждой виртуальной таблицы в языке программирования называется интерфейсом. Все COM-интерфейсы неясно выведены из интерфейса IUnknown, который в модуле System определен так: type IUnknown = interface ['{00000000-0000-0000-C000-000000000046}'] function QueryInterface (const IID: TGUID; out Obj): Integer; stdcall; function _AddRef : Integer; stdcall; function _Release: Integer; stdcall; end; Встречаются следующие расширения оболочки: * Обработчики контекстных меню — реализуются двумя интерфейсами: IContextMenu и IShellExtInit. Позволяют добавлять новые пункты в контекстное меню файловых объектов оболочки. * Обработчики перемещений — реализуются интерфейсом ICopyHook. Они позволяют контролировать и отменять копирование, перемещение, удаление и переименование. * Обработчики перетаскивания правой кнопкой мыши — реализуются, как и обработчики контекстных меню, двумя интерфейсами: IContextMenu и IShellExtInit, но они добавляют новый пункт в контекстное меню, которое появляется при перетаскивании объекта в новое место, с помощью правой кнопки мыши. * Обработчики страниц свойств — реализуются интерфейсами IShellPropSheetExt и IShellExtInit. Позволяют добавлять новые страницы в диалоговые окна свойств файлов. * Обработчики пиктограмм — реализуются интерфейсами IExtractIcon и IPersistFile. Этот обработчик позволяет присваивать одному типу файлов различные пиктограммы. * Обработчики цели — реализуются интерфейсами IDropTarget и IPersistFile. Определяют действия оболочки при перетаскивании одного объекта оболочки на другой. Нам же сегодня потребуется только обработчик контекстных меню. Для того чтобы добавить свой пункт в меню, необходимо создать COM-объект. Он будет реализован в виде динамически подключаемой библиотеки, в основе которой лежат два интерфейса: IShellExtinit и IContextMenu. Прежде всего, после вызова контекстного меню, обработчик должен быть инициализирован. Делается это при помощи интерфейса IShellExtinit, у которого только один метод Initialize. Сразу после инициализации происходит вызов TContextMenu.QueryContextMenu (Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): hResult – он добавляет новый пункт в меню. Параметры этого метода означают: * Menu — дескриптор системного меню. * IndexMenu — номер строки меню, в которую следует вставить пункт. * IdCmdFirst,IdCmdLast — диапазон допустимых значений для идентификаторов вставляемых пунктов меню. * uFlags — набор флагов. Далее идет вызов метода TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HRESULT – этот метод предназначен для получения подсказки для конкретной команды меню. Параметры: * idCmd – идентификатор пункта меню, соответствует IdCmdFirst. * uType - запрашивает тип информации: GCS_VERB или GCS_HELPTEXT. * pwReserved – забронирован. * pszName – определяет буфер-строку. * cchMax – определяет размер буфера. Когда происходит нажатие нашего пункта, то вызывается ContextMenu.InvokeCommand, в котором мы опишем, что должно происходить. Параметром этого метода является лишь запись типа TCMInvokeCommandInfo. Вот поля записи: * cbSize – определяет размер структуры (sizeof (TCMInvokeCommandInfo)) * hwnd – определяет окно, которое будет владельцем всех окон. * fMask – определяет, заданы или нет параметры dwHotkey/hicon. * lpVerb – определяет вызываемую команду. * lpParameters – параметры (опция). * lpDirectory – рабочая папка (опция). * nShow – флаг передаваемый ShowWindow (SW_*). * dwHotKey – горячая клавиша, ассоциированная с приложением после вызова (опция). * hIcon – определяет иконку (опция). * hMonitor – определяет монитор по умолчанию (опция). Мы рассмотрели все методы двух интерфейсов, необходимых нам непосредственно для создания COM-объекта, но чтобы он заработал, нам нужно его еще зарегистрировать. Для этого нужно создать такие значения в реестре: 1. HKEY_CLASSES_ROOT\CLSID\{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}- регистрирует наш COM-сервер. 2. HKEY_CLASSES_ROOT\*\shellex\ContextMenuHandlers\ContMenu\CLSID, вместо CLSID – наш номер. Эта запись указывает на то, какие типы файлов будет вызывать наш обработчик (в данном случае * - для любого файла). 3. HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved – это значение разрешает использовать нашу DLL и погружает ее в ОЗУ сразу же после первого вызова. [шифруем] Я не стал заморачиваться с изучением сложных криптоалгоритмов и решил, что отпимальнее всего будет просто заксорить файл. Алгоритм шифрующей процедуры в этом случае нарисовался такой: открываем файл, считываем один байт, ксорим его и записываем на то же место. В итоге мы получим файл той же длины, с тем же названием, но байты в нем будут совсем другими. Чтобы получить исходный файл, нам нужно просто проксорить его по новой. Да, кстати, так как мы будем все это делать для нетипизированных файлов, то для записи/чтения нужно использовать процедуры BlockRead и BlockWrite. Они объявляются так: Procedure BlockRead(var F: File; var Buf; Count: Integer[; var Result: Integer]); Procedure BlockWrite(var F: File; var Buf; Count: Integer[; var Result: Integer]); F – переменная нетипизированного файла, Buf – переменная, где хранится прочитанные данные, Count – количество записей, которые нужно считать (в нашем случае 1), Result означает, сколько на самом деле записей было прочитано/записано. Впрочем, все лучше увидеть на примере: procedure Shifr (FFileName: string); // FFileName – это имя файла, которое будем шифровать var F: File; BRead, BWritten, TotalRead : Integer; Buf: Byte; begin AssignFile(F,FFileName); Reset(F,1); // открываем для чтения/записи try TotalRead:=0; // количество всех прочитанных байт repeat // считываем в Buf 1 байт из F BlockRead(F,Buf, 1, BRead); if BRead > 0 then begin // ставим указатель перед последним прочитанным байтом seek(F,TotalRead); Buf:=Buf xor 7; // ксорим семеркой BlockWrite(F,Buf, BRead, BWritten); // записываем уже зашифрованный байт if BRead <> BWritten then // если количествово прочитанных байт <> количествуву записанных raise Exception.Create('Ошибка при шифровании!') else begin TotalRead:= TotalRead + BRead; end; end; until BRead=0; finally CloseFile(F); end; Реализуем все прочитанное Теперь мы разобрали все необходимое, поэтому я предлагаю открыть пример расширения контекстного меню из поставки Delphi (Borland\Delphi7\Demos\ActiveX\ShellExt\contmenu.dpr) и отредактировать его. Для начала в Class_ContextMenu изменим значение TGUID на любое другое. В TContextMenu.QueryContextMenu пункт InsertMenu 'Compile...' меняем на название нашего пункта, то есть "Зашифровать". Далее идет GetCompilerPath. Эту функцию благополучно удаляем, и на ее место вставляем нашу — Shifr. Как известно, TContextMenu.InvokeCommand выполняется после нажатия на наш пункт в меню, поэтому мы здесь напишем только Shifr (FFileName), а остальное удалим :). Ну, с TContextMenu.GetCommandString тоже все ясно, так что оставляем без изменений. А вот TContextMenuFactory.UpdateRegistry нужно подправить: просто все пути в реестре меняем на свои. Вот и все, делать больше нечего... теперь компилируем и получившуюся DLL кидаем, например, в C:\WINDOWS\system32. А что дальше? А дальше запускаем cmd, набираем Regsvr32 C:\WINDOWS\system32\ContMenu.dll — и наше расширение готово к работе! Для того чтобы отменить регистрацию, достаточно выполнить Regsvr32 /u C:\WINDOWS\system32\ContMenu.dll. Открываем любую папку, щелкаем по любому файлу — и любуемся новым пунктом в меню! Вот таким несложным образом можно добавить свой пункт в любой тип файлов. А если хочется и иконку добавить рядом с пунктом (как у WINRAR), то для этого нужно использовать не только IContextMenu, но и IContextMenu3. Вот, собственно, и все. Если что-то непонятно, то пиши на мыло. Уникальные идентификаторы GUID представляет некоторое 128-разрядное целое число, используемое в технологии COM для уникальной идентификации интерфейсов. GUID генерируется при помощи API-функции CoCreateGUID(), а алгоритм генерации нового GUID основывается на комбинации следующей информации: текущая дата и время, частота процессора, номер сетевой карты. Если на компьютере установлена сетевая карта, то сгенерированный на этом компьютере GUID будет действительно уникальным, так как уникальность сетевой карты гарантируется встроенным в нее глобальным идентификатором (ID). Если же на компьютере нет сетевой карты, то ее номер можно заменить другим, синтезировав его с помощью параметров другого установленного в компьютере оборудования.
Источник: http://xakep.ru |