Программирование на Delphi - обмен опытом / Советы для написания программ-инсталляторов

© Зайцев Олег 1998-2004
Лучшая портативная техника. Плееры Камеры Телефоны Компьютеры
Покупателям, пришедшим на www.porta.ru по этой ссылке - дополнительная скидка 1%
Железо | Система | WEB | Компоненты | Графика | Ссылки | Мультимедиа | Сети | Прочее | Реестр | Литература

Статистика

Советы для написания программ-инсталляторов

Рекомендую:
Главная страница \ Системное программирование \ Советы для написания программ-инсталляторов

  • Советы для написания программ-инсталляторов

    Советы для написания программ-инсталляторов

    Регистрация программ в меню Пуск Windows 95 * * Задать вопрос Наверх

    Подобная проблема возникает при создании инсталляторов и деинсталляторов. Наиболее простой и гибкий путь - использование DDE. При этом посылаются запросы к PROGMAN. Для этого необходимо поместить на форму компонент для посылки DDE запросов - объект типа TDdeClientConv. Для определенности назовем его DDEClient. Затем добавим метод для запросов к PROGMAN:

    Function TForm2.ProgmanCommand(Command:string):boolean;
    var
     macrocmd:array[0..88] of char;
    begin
     DDEClient.SetLink('PROGMAN','PROGMAN');
     // Устанавливаем связь по DDE
     DDEClient.OpenLink; 
     // Подготавливаем ASCIIZ строку
     strPCopy(macrocmd,'['+Command+']'); 
     ProgmanCommand :=DDEClient.ExecuteMacro(MacroCmd,false);
     // Закрываем связь по DDE
     DDEClient.CloseLink; 
    end;
    

    При вызове ProgmanCommand возвращает true, если посылка макроса была успешна. Система команд (основных) приведена ниже:
    Create(Имя группы, путь к GRP файлу)
    Создать группу с именем "Имя группы", причем в нем могут быть пробелы и знаки препинания. Путь к GRP файлу можно не указывать, тогда он создастся в каталоге Windows.
    Delete(Имя группы)
    Удалить группу с именем "Имя группы"
    ShowGroup(Имя группы, состояние)
    Показать группу в окне, причем состояние - число, определяющее параметры окна:
    1-нормальное состояние + активация
    2-миним.+ активация
    3-макс. + активация
    4-нормальное состояние
    5-Активация
    AddItem(командная строка, имя раздела, путь к иконке, индекс иконки (с 0), Xpos,Ypos, рабочий каталог, HotKey, Mimimize)
    Добавить раздел к активной группе. В командной строке, имени размера и путях допустимы пробелы, Xpos и Ypos - координаты иконки в окне, лучше их не задавать, тогда PROGMAN использует значения по умолчанию для свободного места. HotKey - виртуальный код горячей клавиши. Mimimize - тип запуска, 0-в обычном окне, <>0 - в минимизированном.
    DeleteItem(имя раздела)
    Удалить раздел с указанным именем в активной группе
    Пример использования:
    ProgmanCommand('CreateGroup(Комплекс программ для каталогизации литературы,)');
    ProgmanCommand('AddItem('+path+'vbase.hlp,Справка по VBase,'+ path +' vbase.hlp, 0, , , '+ path + ',,)');
    где path - строка типа String, содержащая полный путь к каталогу ('C:\Catalog\');

    Как скопировать все файлы вместе с подкаталогами * * Задать вопрос Наверх

    uses ShellApi;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      OpStruc: TSHFileOpStruct;
      frombuf, tobuf: Array [0..128] of Char;
    Begin
     FillChar( frombuf, Sizeof(frombuf), 0 );
     FillChar( tobuf, Sizeof(tobuf), 0 );
     StrPCopy( frombuf, 'h:\hook\*.*' );
     StrPCopy( tobuf, 'd:\temp\brief' );
     With OpStruc DO Begin
      Wnd:= Handle;
      wFunc:= FO_COPY;
      pFrom:= @frombuf;
      pTo:=@tobuf;
      fFlags:= FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;
      fAnyOperationsAborted:= False;
      hNameMappings:= Nil;
      lpszProgressTitle:= Nil;
     end;
     ShFileOperation( OpStruc );
    end;
    

    Удаление каталога со всем содержимым * * Задать вопрос Наверх

    // Удалить каталог со всем содержимым
    function DeleteDir(Dir  : string)  : boolean;
    Var
     Found  : integer;
     SearchRec : TSearchRec;
    begin
      result:=false;
      if IOResult<>0 then ;
      ChDir(Dir);
      if IOResult<>0 then begin
       ShowMessage('Не могу войти в каталог: '+Dir); exit;
      end;
      Found := FindFirst('*.*', faAnyFile, SearchRec);
      while Found = 0 do
      begin
       if (SearchRec.Name<>'.')and(SearchRec.Name<>'..') then
        if (SearchRec.Attr and faDirectory)<>0 then begin
         if not DeleteDir(SearchRec.Name) then exit;
        end else
         if not DeleteFile(SearchRec.Name) then begin
          ShowMessage('Не могу удалить файл: '+SearchRec.Name); exit;
         end;
        Found := FindNext(SearchRec);
      end;
      FindClose(SearchRec);
      ChDir('..'); RmDir(Dir);
      result:=IOResult=0;
    end;
    

    Определение базовой системной информации. * * Задать вопрос Наверх
    Часто при создании систем привязки программ к компьютеру или окон типа System Info или About Box необходимо определить данные о пользователе и о системе. Это можно сделать следующим образом (из примеров по Delphi - программа COA):

    Procedure GetInfo;
    Var
     WinVer, WinFlags : LongInt;    // Версия Windows и флаги
     hInstUser, Fmt   : Word;       // Дескриптор
     Buffer : Array[0..30] of Char;	// Буфер под ASCIIZ строку
    begin
     // Открыли библиотеку User
     hInstUser := LoadLibrary('USER');	  
     LoadString(hInstUser, 514, Buffer, 30);
     // Имя пользователя
     LabelUserName.Caption := StrPas(Buffer); 
     LoadString(hInstUser, 515, Buffer, 30);
     FreeLibrary(hInstUser);
     // Компания
     LabelCompName.Caption := StrPas(Buffer);
     WinVer := GetVersion;
     // Версия Windows
     LabelWinVer.Caption := Format('Windows %u.%.2u',
            [LoByte(LoWord(WinVer)), HiByte(LoWord(WinVer))]);
     // Версия DOS
     LabelDosVer.Caption := Format('DOS %u.%.2u',
            [HiByte(HiWord(WinVer)), LoByte(HiWord(WinVer))]);
     WinFlags := GetWinFlags;
     // Режим
     IF WinFlags AND WF_ENHANCED > 0 THEN
       LabelWinMode.Caption := '386 Enhanced Mode' 
     ELSE IF WinFlags AND WF_PMODE > 0 THEN
       LabelWinMode.Caption := 'Standard Mode'
     ELSE LabelWinMode.Caption := 'Real Mode';
     // Сопроцессор
     IF WinFlags AND WF_80x87 > 0 THEN 
      ValueMathCo.Caption := 'Present'
     ELSE ValueMathCo.Caption := 'Absent';
    
     // Свободно ресурсов
     Fmt := GetFreeSystemResources(GFSR_SYSTEMRESOURCES);
     ValueFSRs.Caption := Format('%d%% Free', [Fmt1]); 
     // Свободно памяти
     ValueMemory.Caption := FormatFloat(',#######', MemAvail DIV 1024) + ' KB Free';
    end;
    

    Как проинсталлировать свои шрифты? * * Задать вопрос Наверх

    Добавить шрифт (файл .fon, .fot, .fnt, .ttf) в систему можно следующим образом:

    {$IFDEF WIN32}
      AddFontResource( PChar( my_font_PathName { AnsiString } ) );
    {$ELSE}
    var
     ss  : array [ 0..255 ] of Char;
     AddFontResource ( StrPCopy ( ss, my_font_PathName ));
    {$ENDIF}
     SendMessage ( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 );
    
    Убрать его по окончании работы:
    
    {$IFDEF WIN32}
      RemoveFontResource ( PChar(my_font_PathName) );
    {$ELSE}
      RemoveFontResource ( StrPCopy ( ss, my_font_PathName ));
    {$ENDIF}
      SendMessage ( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 );
    

    При этом не требуется никаких перезагрузок, после добавления шрифт сразу можно использовать. my_font_PathName : string ( не string[nn] для D2+) - содержит полный путь с именем и расширением необходимого фонта. После удаления фонта форточки о нем забывают. Если его не удалить, он (кажется) так и останется проинсталенным, во всяком случае, я это не проверял.

    Вставить какую-нибудь программу (или данные) внутрь EXE файла * * Задать вопрос Наверх

    1. Пишем в блокноте RC-файл, куда прописываем все нужные нам программы, например:

    ARJ EXEFILE C:\ARHIVERS\ARJ.EXE

    2. Компилируем его в ресурс при помощи Brcc32.exe. Получаем RES-файл.
    3. Далее в тексте нашей программы:

    implementation
    {$R *.DFM}
    {$R test.res} //Это наш RES-файл
    
    // Процедура для извлечения ресурса в указанный файл
    procedure ExtractRes(ResType, ResName, ResNewName : String);
    var
      Res : TResourceStream;
    begin
      Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
      Res.SavetoFile(ResNewName);
      Res.Free;
    end;
    
    procedure TForm1.BitBtn1Click(Sender: TObject);
    begin
     // Записывает в текущую папку arj.exe
     ExtractRes('EXEFILE', 'ARJ', 'ARJ.EXE');
    end;
    

    Как написать очень маленький инсталлятор ? * * Задать вопрос Наверх
    Мне понравился следующий вариант: главное приложение само выполняет функции инсталлятора. Первоначально файл называется Setup.exe. При запуске под этим именем приложение устанавливает себя, после установки программа переименовывает себя и перестает быть инсталлятором.
    Пример:

     Application.Initialize;
     if UpperCase(ExtractFileName(Application.ExeName))='SETUP.EXE' 
      then Application.CreateForm(TSetupForm, SetupForm) // форма инсталлятора
      else Application.CreateForm(TMainForm, MainForm);  // форма основной программы
     Application.Run;
    
    Вполне очевидно, что вместо переименования можно запускать программу с различными ключами, например /INSTALL и /UNINSTALL. Я очень часто пользуюсь таким приемом, особенно в тех случаях, когда проект состоит из одного файла

    Включение и выключение устройств ввода/вывода из программы на Delphi * * Задать вопрос Наверх
    Решение для Delphi 1
    Иногда может возникнуть необходимость в выключении на время устройств ввода - клавиатуры и мыши. Например, это неплохо сделать на время выполнения кода системы защиты от копирования, в играх, или в качестве "наказания" при запуске программы по истечению срока ее бесплатного использования ... . Однако наилучшее ее применение - отключение клавиатуры и мыши на время работы демонстрационки, основанной на воспроизведении записанных заранее перемещений мышки и клавиатурного ввода. Это элементарно сделать при помощи API:
    EnableHardwareInput(Enable:boolean): boolean;
    Enable - требуемое состояние устройств ввода (True - включены, false - выключены). Если ввод заблокирован, то его можно разблокировать вручную - нажать Ctrl+Alt+Del, при появлении меню "Завершение работы программы" ввод разблокируется.
    Еще раз подчеркиваю, что это работает только в 16-ти разрядной D1. Исследования в отладчике показали, что функция по сути ничего не делает, только устанавливает некий флаг в памяти, явно отвечающий за блокировку клавиатуры/мыши.

    Решение для Delphi 2+
    По сложно объяснимым причинам фирма Microsoft удалила функцию EnableHardwareInput из 32-рарядных реализаций Windows и, следовательно, EnableHardwareInput стала недоступной в D2+. Однако научные изыскания (в ядре Windows при помощи отладчика) помогли мне найти ее аналог. Он не документирован в справке Borland, но кажется есть в последнем MSDN
    Procedure BlockInput(ABlockInput : boolean); stdcall; external 'USER32.DLL';
    Вызов данной функции c параметром true блокирует клавиатуру и мышь, с параметром false - разблокирует). Как и в случае с EnableHardwareInput блокировка снимается при нажатии Ctrl+Alt+Del.

    Как программно создать ярлык? * * Задать вопрос Наверх
    Создать ярлык можно при помощи данной функции:

    uses ShlObj, ComObj, ActiveX;
      
    procedure CreateLink(const PathObj, PathLink, Desc, Param: string);
    var
     IObject : IUnknown;
     SLink   : IShellLink;
     PFile   : IPersistFile;
    begin
     IObject := CreateComObject(CLSID_ShellLink);
     SLink   := IObject as IShellLink;
     PFile   := IObject as IPersistFile;
     with SLink do begin
      SetArguments(PChar(Param));
      SetDescription(PChar(Desc));
      SetPath(PChar(PathObj));
     end;
     PFile.Save(PWChar(WideString(PathLink)), FALSE);
    end;
    

    Наиболее распространенная задача - создание ярлыка на рабочем столе. Для этого необходимо определить полный путь к системной папке Windows Desctop через реестр  и передать его в качестве параметра PathLink.

    Оповещение приложения (или всей системы) о изменении WIN.INI * * Задать вопрос Наверх
    При изменении WIN.INI (например, изменении настроек хранителя экрана) необходимо уведомить систему (или конкретное приложение) о том, что WIN.INI изменен. Это можно сделать при помощи передачи приложению сообщения WM_WININICHANGE SendMessage(HANDLE, WM_WININICHANGE, 0, PCHAR(SECT_NAME)); При этом HANDLE равен или HANDLE приложения, или HWND_BROADCAST - рассылка всем приложениям. SECT_NAME задает имя секции WIN.INI, в которой произошли изменения. Если указать пустую строку (#0), то считается, что изменялись все секции, что естественно увеличивает время обработки и нагрузку на систему

    Как удалить самого себя ?? * * Задать вопрос Наверх
    Широко известна проблема, связанная с тем, что невозможно удалить запущенный EXE файл. Следовательно, вознакает проблема при написании деинсталлятора - он удалит файлы программы, но кто удалит его (сам себя он удалить не может). На самом деле у данной проблемы есть два решения:
    Решение при помощи BAT файла
    1. Создаем в любой папке BAT файл del_prg.bat следующего содержания

    @echo off
    :del_loop
    del [полное имя и путь к EXE файлу]
    if exist [полное имя и путь к EXE файлу] goto del_loop
    del [полное имя bat файла]
    
    2. Запускаем его
    3. Завершаем работу EXE файла. BAT файл будет крутится по циклу до тех пор, пока ему не удатся удалить EXE файл. Затем он самоуничтожится - этому ничто не препятствует, т.к. bat файлыв могут стирать сами себя без проблем

    Решение при помощи реестра
    1. Создаем ключ в ветви реестра HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce с любым именем, например del_self и значением del [полное имя и путь к EXE файлу]
    2. Просим пользователя перезагрузить компьютер (или делаем это принудительно). Команда из ключа сработает при следующей загрузке и будет автоматически удалена из реестра

    Как проверить, откуда идет инсталляция (с дискеты, HDD, CD или сети) * * Задать вопрос Наверх
    Данный вопрос бывает актуален при написании инсталлятора - в ряде случаев необходимо разрешать инсталляцию только с CD диска или блокировать запуск инсталлятора из сети.
    Проблема решается следующим образом
    1. При помощи ExtractFileDrive(Application.ExeName) определяем букву диска, с которого запущена программа (возвращаемое значение содержит букву с двоеточием после него, например "E:")
    2. При помощи примера, описанного в разделе "Железо\Как определить тип диска" определяем тип диска и принимаем решение, допустим ли запуск

    Программная перезагрузка Windows * * Задать вопрос Наверх
    Программная презагрузка Windows возможна при помощи функции API ExitWindows:
    function ExitWindows(dwReserved: DWORD; Code: Word): BOOL;
    Первый и второй параметр зарезервированы и должны быть равны 0. В Delphi6 вместо вызова функции API вызывается следующий код:

    function ExitWindows(dwReserved: DWORD; Code: Word): BOOL;
    begin
      Result := ExitWindowsEx(EWX_LOGOFF, 0);
    end;
    
    При этом функция ExitWindowsEx имеет вид:
    function ExitWindowsEx(uFlags: UINT; dwReserved: DWORD): BOOL; stdcall;
    
    Второй параметр заверзервирован, а вот первый определяет реакцию на вызов функции и содержит битовые флаги: Пример вызова, приводящего к немедленному завершению работы системы:
     ExitWindowsEx(EEWX_FORCE or EWX_POWEROFF or EWX_SHUTDOWN, 0);
    


    © Зайцев Олег, "Программирование на Delphi - обмен опытом" 1999-2004. При использовании любых материалов данного сайта необходимо указывать источник информации. Дата обновления: 22.11.2004. Сайт размещен на хостинге AGAVA - Хостинг от AGAVA.ru