Создание группы программ (продолжение)

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

Для того, чтобы создать группу, мы должны всего лишь создать каталог; для того, чтобы создать элемент — скопировать файл в каталог или создать ярлык на существующую программу. Что такое ярлык? Ярлык — это файл с расширением .LNK, в котором хранится информация о каком-то другом файле. Для операционной системы ярлык олицетворяет собой файл, на который он ссылается.

Для чего применяются ярлыки? Для того, чтобы вам не нужно было хранить на своём диске несколько одинаковых файлов. Например, программа WinZip во время инсталляции помещает себя на рабочий стол, в меню Пуск и в меню Программы. Что, вы думаете, файл winzip.exe хранится на диске в трёх экземплярах? Отнюдь.

Учтите, что ярлык занимает на диске где-то 250–400 байт, поэтому для файлов меньшего размера ярлыки создавать бессмысленно.

С этого момента я объявляю ярлыки новой парадигмой создания групп! :) Звучит громко… Нам осталось только выяснить, как это делается. :) Итак, группы менеджера программ доступны нам через кнопку Пуск подменю Программы. В действительности, все они являются подкаталогами, одного из каталогов Windows (это может быть, например, C:\Windows\Главное меню\Программы. Путь к этому каталогу Проводник хранит в реестре (где именно найти эти данные, рассматривалось в одной из предыдущих статей цикла).
procedure ReadGroups(Strings: TStrings);
var
ARegistry: TRegistry;
Programs: String;
SearchRec: TSearchRec;
FindResult: Integer;
begin
Strings.Clear;
// Находим каталог
ARegistry := TRegistry.Create;
with ARegistry do
begin
RootKey := HKEY_CURRENT_USER;
if OpenKey(‘\Software\Microsoft\Windows\CurrentVersion\Explorer\ Shell Folders’, False) then
begin
Programs := ReadString(‘Programs’);
CloseKey;
end
else
Programs := »;
Free;
end;
if (Length(Programs) > 0) and (Programs[Length(Programs)] <> ‘\’) then
Programs := Programs + ‘\’;
// Читаем содержимое каталога
FindResult := FindFirst(Programs + ‘*.*’, faDirectory, SearchRec);
while FindResult = 0 do
begin
with SearchRec do
if (Name <> ‘.’) and (Name <> ‘..’) and (Attr and faDirectory <> 0) then
Strings.Add(Name);
FindResult := FindNext(SearchRec);
end;
FindClose(SearchRec);
end;

Точно таким же макаром можно прочитать содержимое любой группы. Достаточно очевидно делаются такие вещи, как создать группу, удалить группу, переименовать (!) группу и так далее. Более того, теперь вы можете создавать группы в меню Пуск и на рабочем столе (в статье о системном реестре рассказано как найти путь к этим каталогам). Мы также можем воспользоваться функцией SHGetSpecialFolderLocation для того, чтобы выяснить, где находится тот или иной каталог Проводника.
function GetSpecialFolderLocation(nFolder: Integer): String;
var
ppidl: PItemIDList;
Malloc: IMalloc;
szPath: array[0..MAX_PATH - 1] of Char;
begin
SHGetSpecialFolderLocation(Handle, nFolder, ppidl);
SHGetMalloc(Malloc);
SHGetPathFromIDList(ppidl, szPath);
Malloc.Free(ppidl);
Malloc := nil;
Result := String(szPath);
end;
См. описание функции SHGetSpecialFolderLocation для того, чтобы узнать, какие константы можно использовать в качестве nFolder.

Что нам осталось узнать? Как получить информацию о ярлыке и как создать ярлык. Обе эти операции удобно делать с помощью интерфейса IShellLink, предоставляемого нам Проводником. Об интерфейсах (и связанных с ними COM-объектах) говорить можно долго, однако, для наших целей это не нужно. Нам достаточно знать, что интерфейсы очень напоминают обычне классы, за несколькими исключениями. Вот, например:
const
MAX_DESCRIPTION = 100;
var
Bitmap: TBitmap;
ShellLink: IShellLink;
Description: array[0..MAX_DESCRIPTION - 1] of Char;
begin
// Создаём обычный объект
Bitmap := TBitamp.Create;
// Создаём COM-объект
CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IID_IShellLinkA, ShellLink);
. . .
// Используем обычный объект
Bitmap.Width := 100;
// Используем COM-объект
ShellLink.GetDescription(Description, MAX_DESCRIPTION);
. . .
// Удаляем обычный объект
Bitmap.Free;
// Удаляем COM-объект
ShellLink := nil;
end;
Итак, для того, чтобы создать COM-объект в Дельфи, вы должны вызвать функцию CoCreateInstance, передав её в качестве параметров несколько странного вида констант. COM-объекты автоматически удаляются по завершении процедуры, в которой они были созданы. Тем не менее, я предпочитаю явное указание того факта, что COM-объект должен быть уничтожен:
ShellLink := nil;
Теперь приведу пример, показывающий, как можно создать ярлык на рабочем столе.
var
Desktop: String;
ShellLink: IShellLink;
hRes: HRESULT;
PersistFile: IPersistFile;
begin
// Находим каталог
Desktop := GetSpecialFolderLocation(CSIDL_DESKTOPDIRECTORY);
if (Length(Desktop) > 0) and (Desktop[Length(Desktop)] <> ‘\’) then
Desktop := Desktop + ‘\’;
CoInitialize(nil);
hRes := CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER,
IID_IShellLinkA, ShellLink);
if SUCCEEDED(hRes) then
begin
ShellLink.SetPath(PChar(Application.ExeName));
ShellLink.SetDescription(‘Вот моя программа!’);
hRes := ShellLink.QueryInterface(IID_IPersistFile, PersistFile);
if SUCCEEDED(hRes) then
begin
PersistFile.Save(PWideChar(WideString(Desktop + ‘Моя программа.lnk’)), True);
PersistFile := nil;
end;
ShellLink := nil;
end;
CoUninitialize;
end;
Подробнее узнать о том, что можно делать с помощью интерфейса IShellLink вы можете в win32.hlp (ключевое слово — IShellLink :) Вызовы функций CoInitialize и CoUninitialize обеспечивают работу COM, всегда вызывайте первую из них перед работой, а вторую — после работы.

Для работы этих примеров вам потребуется включить (uses) в свой модуль файлы Windows, ShlObj, ActiveX, ShellApi. По недосмотру, или по какой-то другой причине, Инпрайз не внесла константу IID_IPersistFile ни в один из этих файлов. Эту константу можно найти в модуле Ole2, однако этот модуль оставлен в 3-ей и 4-ой версиях Дельфи только в целях совместимости со 2-ой версией, где COM-интерфейсы были реализованы по другому. Я сам уже запутался, честно говоря, объясняя, что здеь к чему :) Если в двух словах, скопируйте эти строчки из модуля Ole2.pas в свою программу и расслабтесь:
const
IID_IPersistFile: TGUID = (
D1:$0000010B;D2:$0000;D3:$0000;D4:
($C0,$00,$00,$00,$00,$00,$00,$46));

Напоследок остановимся на том, какой же метод создания групп лучше? Давайте рассмотрим достоинства и недостатки этих методов.

Менеджер программ достался нам в наследство от 16-битных версий Windows. Он капризен и работает неторопливо. Помимо всего прочего, с помощью него нельзя помещать ярлыки на рабочий стол и в меню Пуск.

С другой стороны, интерфейсы проводника более современны, дают больший контроль за происходящим, работают быстрее… И помимо прочего, заставляют программиста осваивать новую технологии (а если программист ленив, ему такие стимулы необходимы, как воздух :)

И тем не менее — для создания групп в меню Программы я рекомендую вам пользоваться менеджером программ.

Почему?

Потому что менеджер программ, помимо того, что создаёт все необходимые каталоги и ярлыки, отражает изменения в файлах с расширением .GRP. Может оказаться, что некоторые программы (старые) работают с файлами групп, и, следовательно, некорректно будут работать с вашими ярлыками.

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

Деинсталляция

Итак, поговорим немного о деинсталляции.

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

Программа инсталляции должна вести журнал инсталляции, в который должны быть занесены все действия, производимые этой программой: создание разделов в реестре, секций в .INI-файлах, копирование, переименование, регистрация ActiveX-компонентов и многое другое.

Программа деинсталляции может, основываясь на этом журнале, произвести деинсталляцию продукта.

Но это всё вещи очевидные, как и многое другое в деинсталляции. Мы можем для каждого действия программы инсталляции указать действие, которое должно происходить при деинсталляции. Создать каталог Directory. Удалить каталог Directory.
Копировать файл Source в Target. Удалить файл Target.
Копировать разделяемый файл Sourcr в Target. Увеличить счётчик инсталляций на 1, если он уже существует или присвоить ему 1 в противном случае. Уменьшить счётчик инсталляций на 1. Если он равен 0, то удалить файл Target
Создать раздел Key в реестре. Удалить раздел Key в реестре.
Создать параметр Value в реестре. Удалить параметр Value в реестре.
Изменить значение параметра Value с Old на New. Записать в Value значение Old.
Создать новый INI-файл. Удалить INI-файл.
Создать секцию в INI-файле. Удалить секцию в INI-файле.
Записать параметр в секцию INI-файла. Если параметр уже существует, сохранить его содержимое. Если в журнале сохранено предыдущее содержимое параметра — записываем его. В противном случае удаляем параметр из секции.

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

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

В статье, посвящённой системному реестру, рассказано, как поместить свою программу в список программ деинсталляции (Панель управления/Установка и удаление программ).

Что ещё? Самое главное — вы намучаетесь с удалением самой программы деинсталляции с жёткого диска. Дело в том, что программу невозможно удалить до тех пор, пока она запущена — Windows закрывает к ней доступ. Что делать? Если мы посмотрим, как с этой ситуацией справляются распространённые инсталляторы (например, InstallShield и Wise), то увидим, что они оставляют программу деинсталляции на диске. Она становится разделяемым ресурсом, частью операционной системы (помещается в каталог Windows). Например, у меня на диске находятся C:\WINDOWS\UNINST16.EXE, C:\WINDOWS\UNINST.EXE (InstallShield) и C:\WINDOWS\UNWISE.EXE (Wise). Это достаточно корректное решение, поскольку этими инсталляторами пользуются многие программы. Мы можем сделать то же самое, изменив имя программы деинсталляции (uninst и unwise уже заняты :)

Мы также можем скопировать программу деинсталляции во временный каталог и запустить её оттуда. Она, конечно, не будет уничтожена, однако при следующей чистке временного каталога, пользователь её удалит. Какие проблемы могут возникнуть на этом пути? Обратите внимание, что программу деинсталляции мы должны будем скопировать во временный каталог только во время инсталляции — в ином случае пользователь может удалить её значительно раньше, чем она ему потребуется для деинсталляции (а откуда он знает, что это за файл?). Значит, вариант может быть таким: скопировать программу, запусить её из нового каталога, а текущую копию завершить. Здесь как раз и находится проблема: как только наша программа будет завершена, фокус будет передан назад в окно Установка и удаление программ, а мы как раз начнём запрашивать пользователя, действительно ли он согласен удалить продукт с машины…

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

Наконец, мы можем удалить-таки наш единственный EXE-файл автоматически. Для того, чтобы понять, как это делается, достаточно вспомнить о разделе RunOnce системного реестра.
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\ CurrentVersion\RunOnce]
SomeName=»C:\WINDOWS\COMMAND.COM /C DEL C:\TEMP\DEINST.EXE»
При следующей перезагрузке компьютера будет вызван командый процессор, который и удалит ненужный EXE-файл. Затем параметр SomeName будет автоматически удалён из раздела RunOnce.

Командным процессором может быть не только command.com, но и cmd.exe (в NT), 4dos.com, 4nt.exe и другие программы. Переменная окружения COMSPEC содержит полный путь к текущему командному процессору
function GetCommand: String; var szCommandPath: array[0..MAX_PATH - 1] of Char; begin GetEnvironmentVariable(‘COMSPEC’, szCommandPath, MAX_PATH); Result := String(szCommandPath); end;

Вот, видимо, и всё.

Ноябрь 17, 2007 — Filed under: Delphi
Метки: , ,

Создание группы программ

Менеджер программ (Program Manager) знаком многим из тех, кто работал в Windows ещё в незапамятные времена. Сейчас, когда в Windows появился Проводник, менеджер программ ушёл на второй план и исользуется сейчас, в основном, с одной-единственной целью — создавать группы программ во время инсталляции. :)

Менеджер программ может быть сервером DDE-соединения. Если вы ничего не знаете о DDE — не беспокойтесь: всё, что нам потребуется, вы прочитаете в этой статье ниже.

Для работы с DDE-серверами мы будем использовать компонент DdeClientConv, который находится на закладке System панели компонентов.

Что такое DDE? DDE (Dynamic Data Excahnge) — это устаревшая технология, которая использовалась давным давно для передачи данных между программами (сейчас для этих целей используется OLE). Передача данных в DDE производится через соединения. Программа-клиент устанавливает соединение с программой-сервером, передаёт/принимает данные и разрывает соединение.

Применительно к нашей задаче, менеджер программ будет выступать в роли DDE-сервера, а программа инсталляции — в роли DDE-клиента. DDE предоставляет двухуровневую схему связи с сервером: для её установления клиенту необходимо указать имя сервера и имя сервиса (topic), который клиент хочет у сервера запросить. Для больших и толстых серверов, имя сервиса является насущной необходимость, для маленьких и простых — непонятным параметром, необходимость которого очень трудно объяснить.

Менеджер программ — простая и маленькая программа, поэтому имя сервиса у неё совпадает с именем сервера. Поместим на форму компонент DdeClientConv и настроим его в инспекторе объектов.
Свойства DDEService и DDETopic должны быть установлены в PROGMAN. Свойство ConnectionMode переключите в ddeManual —устанавливать и разрывать соединение мы будем вручную. Почему? Одним из правил, которых я придерживаюсь, является правило не кушать ресурсы без необходимости. Я создаю объекты в программах сразу перед тем, как они мне понадобятся; выделяю память непосредственно перед операторами, её использующими; и открываю файл, только тогда, когда уже готов его читать. Зачастую. :) Именно поэтому открывать соединение с менеджером программ я предлагаю только тогда, когда вся остальная работа уже выполнена.

Хорошо. Будем считать, это было лирическое отступление и вернёмся к нашим баранам. Может оказаться, что у нас под рукой нет подходящей формы для того, чтобы поместить на неё DdeClientConv. В этом случае можно создавать компонент DdeClientConv вручную:

var
DdeClientConv: TDdeClientConv;
begin
DdeClientConv := TDdeClientConv.Create(nil);
with DdeClientConv do
begin
ConnectMode := ddeManual;
DDEService := ‘PROGMAN’;
DDETopic := ‘PROGMAN’;
. . .
Free;
end;
end;

Перед тем, как что-то сделать с сервером, вы должны вызвать метод OpenLink (установить соединение), а после работы — CloseLink (соответственно, разорвать).

Помимо приёма и передачи данных, DDE-сервер может выполнять команды клиента. Всё очень просто: мы передаём серверу строку, говорим, что это команда — и он её исполняет.

Методы ExecuteMacro или ExecuteMacroLines компонента DdeClientConv передают серверу команду (и команды, соответственно) для выполнения. Команды менеджера программ заключаются в квадратные скобки, и выглядят приблизительно так: [CreateGroup("SomeTools",0)] [ShowGroup("Стандартные")] [AddItem("c:\windows\notepad.exe")].

Эти команды мы подробнее рассмотрим ниже, а перед этим остановимся ещё на одном важном моменте работы с менеджером программ — получении информации о текущем состоянии дел. Что нас может интересовать? Какие группы уже созданы, какие элементы находятся в этих группах…

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

Пример показывает, как можно прочитать список групп из менеджера программ: var
GroupList: TStringList;
begin
GroupList := TStringList.Create;
. . .
GroupList.Text := String(DdeClientConv.RequestData(‘Groups’));
. . .
GroupList.Free;
end;
После выполнения этой строчки, в GroupList появится список доступных групп.
Примечание:
Если вы работаете под NT, возможно, вас волнует вопрос, как прочитать список групп из общедостуной части меню Программы. Для этого необходимо воспользоваться чем-то, что не является менеджером программ. Как это сделать, я расскажу чуть ниже.

Прочитав список групп, мы можем прочитать содержимое любой группы из этого списка (например, первой): var
GroupList: TStringList;
ItemList: TStringList;
begin
GroupList := TStringList.Create;
ItemList := TStringList.Create;
. . .
GroupList.Text := String(DdeClientConv.RequestData(‘Groups’));
ItemList.Text := String(DdeClientConv.RequestData(PChar(GroupList[0])));
. . .
ItemList.Free;
GroupList.Free;
end;

Менеджер программ возвращает в первой строке название группы (в кавычках), имя файла группы (с расширением .GRP) и количество элементов в группе. Всё это разделяется запятыми. Далее идёт информация об элементах: командная строка (в кавычках), каталог по умолчанию, иконка, позиция в группе (по горизонтали и вертикали), номер иконки, клавиши быстрого доступа (в целочисленном виде) и флаг «свернуть при запуске».

Вот содержимое группы Стандартные: «Стандартные»,C:\WINDOWS\ГЛАВНО~1\ ПРОГРА~1\СТАНДА~1,7,1
«Таблица символов»,»C:\WINDOWS\CHARMAP.EXE»,, C:\WINDOWS\CHARMAP.EXE,416,32,0,0,0
«Текстовый редактор WordPad»,»C:\PROGRA~1\ACCESS~1\WORDPAD.EXE»,, C:\PROGRA~1\ACCESS~1\WORDPAD.EXE,352,32,0,0,0
«Просмотр рисунков»,»C:\WINDOWS\WANGIMG.EXE»,, C:\WINDOWS\WANGIMG.EXE,288,32,0,0,0
«Калькулятор»,»C:\WINDOWS\CALC.EXE»,, C:\WINDOWS\CALC.EXE,224,32,0,0,0
«Графический редактор Paint»,»C:\PROGRA~1\ ACCESS~1\MSPAINT.EXE»,, C:\PROGRA~1\ACCESS~1\MSPAINT.EXE,160,32,0,0,0
«Интерактивная регистрация»,»C:\WINDOWS\ SYSTEM\REGWIZ.EXE /i software\microsoft\windows\currentversion»,, C:\WINDOWS\SYSTEM\REGWIZ.EXE,96,32,0,0,0
«Блокнот»,»C:\WINDOWS\NOTEPAD.EXE»,, C:\WINDOWS\NOTEPAD.EXE,32,32,0,0,0

По большому счёту, нам эта информация не потребуется. Но, может быть, вы захотите заменить один из элементов группы во время инсталляции — в этом случае, конечно, надо знать, что изменять.

Далее я опишу способ создать группу и элемент в группе. var
Commands: TStringList;
begin
. . .
Commands := TStringList.Create;
Commands.Add(‘[CreateGroup("Нестандартные",0)]‘);
Commands.Add(‘[ShowGroup("Нестандартные",1,0)]‘);
Commands.Add(‘[AddItem("c:\windows\notepad.exe","Блокнот")]‘);
DdeClientConv.ExecuteMacroLines(Commands, False);
Commands.Free;
. . .
end;
В приведённом примере мы создаём группу Нестандартные (в пику группе Стандартные). Далее, мы показываем её (без этого добавление элемента не будет работать — ох, как я с этим намучился в своё время!). Наконец, добавляем Блокнот в эту группу, присваивая ему имя «Блокнот» (оригинально, не правда ли?). Важным здесь является то, что, во-первых, команды менеджеру программ заключаются в квадратные скобки, и, во-вторых, параметры команды, содержащие пробелы и прочие нестандартные символы, заключаются в кавычки. Ну, и, если вы вызываете несколько команд сразу, пользуйтесь методом ExecuteMacroLines вместо нескольких вызовов ExecuteMacro.

Полный список команд плюс описание вы найдёте в файле win32.hlp. Здесь я приведу только их список, чтобы вы знали, что искать: CreateGroup, ShowGroup, DeleteGroup, Reload, AddItem, ReplaceItem, DeleteItem и ExitProgman.

Далее мы перейдём ко второму способу работать с группами программ — через интерфейсы, предоставляемые Проводником.

Ноябрь 11, 2007 — Filed under: Delphi
Метки: ,