Как добавить свой шаг к инсталятору

  !   Данная информация предназначена только только для IT-специалистов по системной интеграции модулей БИОСОФТ-М. (см. Руководства пользователя к программным продуктам)

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

Для применения в 

- инсталяторе игр Stabip

- интеграции Disins со стандартной инсталляцией (для инсталляции файлов Хелпа, Атласа, и т.п. в едином стиле)

      Интеграция Disins инсталлятором UV

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

Сюда не относится

- вопросы возможности реализации инсталлятора без Pex в новых проектах

- интеграция с Disins это отдельный топик

Туториал
На примере

Ниже подробно описан процесс добавлегия пары дополнительных шагов инсталляции к проекту.

Для данного примера предположим что проект называется 'Stabip' (заменить на название/префикс своего проекта!) а дополнительные шаги которые он реализует

I) Шаг с прогресс индикатором на котором выполняется последовательный запуск неких внешних экзешников

II) Чисто для демо еще один шаг на котором написано что операция завершена

     (не просто для демо как оказывается: выдавая ошибки во флоатеры важно оставить UI на экране чтобы юзер их увидел прежде чем инсталлер терминируется)

Классы

0) Реализовать некоторое количество дополнительных шагов может любой UV проект однакорегистрация этих щагов разрешена только главному проекту, являющемуся стартером процесса.

1) Создадим пакет, отвечающий за шаги инсталлятора и назовем его например 'Install'.

2) В Install/Impl реализуем колбак обслуживающий события на нашем шагеинсталляции.

class CStabipInstallPhaseForGamesExe : public CAppInstallPhaseBaseIfaceGp

Создаем в нем все виртуальные функции из CAppInstallPhaseBaseIfaceGp.

Они позволят нам в том числе

- отслеживать нажатие кнопок [Back] [Next]

- вставлять свое UI и оформление в UI данного шага инсталятора

     sloc OnGetInstallPhaseNameForUi()
     res OnGetInstallPhaseIcon()
     void OnInitInstallPhase(rAppInstallPhaseControl)
     void OnHandleInstallPhaseEntered()
     void OnHandleInstallPhaseLeft()
     bool OnConfirmLeaveToNextPhase()
     bool OnConfirmLeaveToPrevPhase()
     ref<UiContainer> OnNewUiInstallPhaseMain()

(см. текущую версию для точного списка!)

3) Обращаем внимание, что инициализации шага получает ссылку на CAppInstallPhaseControlIfaceGp.

Через этот объект можно вызывать инсталлятор обратно из приложения.

(см. также GetAppInstallPhaseControl() - та же ссылка)

UI

4) Создаем UI которое мы хотим вставить в инсталлятор.

В общем случае может быть правильней создать отдельные классы обслуживающие эти UI но в данном примере добавим два Ui просто к классу нашего колбака

- UiStabipInstallPhaseForGamesExeRun - для шага запускающего экзешники

- UiStabipInstallPhaseForGamesExeDone - для аутро сообшения

В других приложениях UI разных шагов может обслуживать и одно Ui если UI сходные. Это не важно и внутреннее дело приложения как самоорганизоваться. Что важно - это вернуть данное UI колбаку инсталлера. В данном примере так:

ref<UiContainer> CStabipInstallPhaseForGamesExe::OnNewUiInstallPhaseMain()
 {
     return 
         !x_bGamesInstallExeComplete?
             NewUiStabipInstallPhaseForGamesExeRun()
             :
             NewUiStabipInstallPhaseForGamesExeDone();
 }

(в более сложном случае это будет выбор по type<> и т.п. - офтоп)

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

5) Для оформления заголовка шага определяем

sloc CStabipInstallPhaseForGamesExe::OnGetInstallPhaseNameForUi()
 {
     return 
         Enru_VL(
             "Setting up Stabip offline games",
             "Настройка оффлайн игр Stabip");
 }
 
 res CStabipInstallPhaseForGamesExe::OnGetInstallPhaseIcon()
 {
     return 
         IDB_............();
 }

6) Реализуем соответствующие UI учитывая следующее

- UI шага инсталлятора должно вписываться в размер окна инсталлятора который неподконтролен приложению

   -- этот размер будет менятся в будущих версиях

   -- инсталлятор обеспецивает прокрутку для невлезающего содержимого но не надо ее провоцировать!

- длинный существенный текст надо располагать с переносом слов

                 rUi->SetWrapText(true);
                 rUi->SetPreferredX(0);
                 rUi->SetMaxX(oo);

- длинный несущественный текст (пути к фалам прогресс индикатора) можно просто урезать

                 rUi->SetPreferredX(0);
                 rUi->SetMaxX(oo);
Глобальная регистрация

7) Регистрируем наш шаг в инсталляторе

 7.0) Создаем инкапсулятор глобальных вопросов инсталляции проекта в Install/CStabipInstallIface/Impl

 7.1) Как глобальный объект используемый стартером приложения добавлям инкапсулятор инсталляции в Loader

class CStabipLoaderIface : public object
 {
 
 // Attributes
 
     // Install steps
     ref<CStabipInstallIface> x_rStabipInstall,
             auto(Get);

 7.2) Переопределяем CStabipProject::OnInitStandaloneFromInstaller()

   Только из этой функции разрешено вызывать регистрацию шагов инсталлятора.

   Это исключает возможность для загруженых приложением модулей напихать своих шагов в неопределенной последовательности без контроля со стороны главного стартера и гарантирует что добавление шагов произойдет вовремя до появления UI инсталлера.

 7.3) Публикуем функцию регистрации шагов и вызываем ее:

void CStabipProject::OnInitStandaloneFromInstaller()
 {
     // Unpack games in installer
     x_rStabipLoader->
         x_rStabipInstall->
             PrepareInstallGamesForStandaloneGameStarter();
 }

 7.4) Реализуем собственно саму регистрацию нашего шага в глобальном регистралище

          CAppProcessIfaceGp::GGetThisAppProcess()->x_rAppInstall->AddNewInstallPhase(rMyCallback)

void CStabipInstallImpl::OnPrepareInstallGamesForStandaloneGameStarter()
 {
     rASSERT(!_m_bOneTimeInitOk);
     _m_bOneTimeInitOk = true;
 
     // installing?
     if failed(
         CAppProcessIfaceGp::GGetThisAppProcess()->
             x_rAppInstall->IsInstallingNow())
     {
         return;
     }
 
     // app is running in a role reqiuring game unpack?
     if (!CStabipProject::GGetGlobalInstance().
             x_rStabipLoader->
                 GetStabipAppType()->
                     IsOfflineGameInstallRequired())
     {
         return;
     }
 
     // in global install controller
     CAppProcessIfaceGp::GGetThisAppProcess()->
         x_rAppInstall->
             AddNewInstallPhase(
                 m_rStabipInstallPhaseForGamesExe);
 }
Процедура инсталляции

8) Запускаем процесс требуемый на данном шаге в OnHandleInstallPhaseEntered()

void CStabipInstallPhaseForGamesExe::OnHandleInstallPhaseEntered()
 {
     x_rStabipInstallGameMap->StartInstallGames();
 }

9) Все немгновенные операции должны ваполнятся дискретно по таймеру.(Использование ParaService тоже возможно в сложных случаях).

Нельзя блокировать UI инсталятора надолго и нельзя прокачивать сообщения.

10) Нужно запретить переход на следующий и предыдущий шаги или отработать их как корректный пропуск опционально шага (врядли нам понадобится такого рода опциональность!)

Пока нельзя задизаблить кнопку можно вернуть false в колбак.

Или поднять задачу http://www.biosoft-m.ru/w/index.php?showtopic=10367&view=getnewpost

11) За кадром остались вопросы инициализации ptr для обращения из колбака к приложению, сама логика инсталляции нужных фалов, прогресс индикации и внутреннего переключения с одного суб-шага на другой которую реализует чисто само приложение (x_bGamesInstallExeComplete в данном примере).

Соответствие фаз инсталлера и шагов реализуемых приложением

В данном примере одному физическому шагу инсталлера описываемому нашим колбаком согласно нашей внутренней логике соответствует два разных шага. Хотя переход между неми происходит не по [Next] а автоматом. Возможна и иная реализация когда приложение регистрирует несколько независимых шагов реализуемым одним и тем же или несколькими независимыми классами колбаков. Независимая регистрация нескольких шагов естественно удобней если их обслуживают разные объекты или возможно даже разные модуля данного приложения. Но не стоит усложнять простые случаи где приложению проще объявить только один шаг обслуживаемый одним колбаком и самостоятельно менять его UI.

Пример как Disins реализует переход на свой второй внутренний шаг, эксплуатируюя кнопку [Next] стандартного визарда инсталляции. 

Колбак в Disins блокирует покидание Disins-ного шага инсталляции до тех пор пока работа не завершена. И вместо этого переключает свою логику на следующий шаг своего UI:

bool CDisinsAppInstallPhaseCallback::OnConfirmLeaveToNextPhase()
 {
     x_rDisins->
         GoToNextStep();
 
     return rDisins->IsInstallComplete();
 }

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

Notes

- Шаг назад сейчас запрещен в инсталлере безусловно.