Приветствую всех читающих :)

Целевой хаб не соответствует содержанию статьи, но что поделать… — тематических по цели нет, а наиболее подходящий «С», т.к. читатели именно его с наибольшей лёгкостью смогут воспринять написанное.

Цель статьи


Вариант решение проблемы контроля за глобальными переменными при написании инсталлятора/деинсталлятора средствами NSIS.

Небольшое введение


При написании nsis-кода у программиста имеется возможность использовать глобальные переменные, область видимости которых — весь проект. Таковых всего 20: $0-$9 и $R0-$R9.
Использовать их весьма удобно, т.к. это позволяет не вводить дополнительные переменные, формально предназначенные для решения целевых задач. Я говорю, формально, т.к. любые переменные nsis имеют глобальную область видимости и если чем и отличаются, то только квалификатором const (говоря в терминах C/C++):

!define variable1 ''value 1''

Особенности:

  • статическая переменная глобальной области видимости;
  • Инициализируется при объявлении;
  • Способ обращения: ${variable1}

Var variable2

Особенности:

  • Динамическая переменная глобальной области видимости;
  • Объявляется где угодно по коду, но выше первого обращения;
  • Инициализируется сколько угодно раз, но только внутри функциональных блоков и макросов;
  • Способ обращения: $variable2

Таким образом, учитывая выше изложенные особенности становится понятно, что целенаправленное создание любых переменных кроме статических, сложно оправданно, т.к. глобальные переменные по-умолчанию уже существуют, количество их достаточное для удовлетворения любых нужд (а если нет, то нужно задавать «соответствующие» вопросы программисту) и любые иные, скорее всего, будут лишь засорять код.

Описание проблемы


Но вот беда, такой код всегда порождает ошибки:


Function F1
    StrCpy $0 "vladimir"
FunctionEnd

Function F2
    StrCpy $0 "alexei"
    Call F1
    ${If} $0 == "alexei" ; // ошибка $0 содержит "vladimir"
        ; // какая-то важная логика...
    ${EndIf}
FunctionEnd

И примеров подобного можно придумать массу, да и проблему сию умельцы программирования на nsis решали как могли:

  • Кто-то просто внимательно следил за всеми переменными (таким людям надо памятник поставить));
  • Кто-то, — главным образом создатели всевозможных плагинов, — каждый раз желая использовать какую-либо из представленных переменных делали её Push в стек, а после окончания использования возвращали первичное значение вызовом Pop;
  • А кто-то плодил переменные через Var, как было описано выше.

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

Здесь же я позволю себе предложить её(проблемы) универсальное решение.

Решение проблемы


Для начала, ссылка на NSISList — плагин вводящий возможность использования структуры данных типа «список», который нужно воспринимать аналогично тому, как воспринимается std::list на C++.

Вторым шагом исходный код механизма решения проблемы:


!verbose 3
!include NSISList.nsh	; для работы с массивами типа список*

/*
Как использовать Dump, пример:

!define F1 "!insertmacro _F1"
!macro _F1 [paramIn1 paramIn2 ... paramInN] [paramOut1 paramOut1 ... paramOutM]
    ${Dump}
    [Push ${paramIn1}
	Push ${paramIn2}
	...
	Push ${paramInN}]
    !ifdef __UNINSTALL__
        Call un.F1
    !else
        Call F1
    !endif
    ${Undump}
    [Pop ${paramOut1}
	Pop ${paramOut2}
	...
	Pop ${paramOutM}]
!macroend

!macro Func_F1 un
    Function ${un}F1
        ; код функции...
        ; В данном случае можно абсолютно безопано использовать переменные $0-$9, $R0-$R9.
        ; Их использование не повлияет на работу иных функций.
    FunctionEnd
!macroend
!insertmacro Func_F1 ""
!insertmacro Func_F1 "un."

*/

/** Инициализирует логику Dump */
!define InitDump "!insertmacro _InitDump"
!macro _InitDump
    !ifdef __UNINSTALL__
        Call un.InitDump
    !else
        Call InitDump
    !endif
!macroend

!macro Func_InitDump un
    Function ${un}InitDump
        # переменные $0-$9
        ${List.Create} d0
        ${List.Create} d1
        ${List.Create} d2
        ${List.Create} d3
        ${List.Create} d4
        ${List.Create} d5
        ${List.Create} d6
        ${List.Create} d7
        ${List.Create} d8
        ${List.Create} d9
        # переменные $R0-$R10
        ${List.Create} dR0
        ${List.Create} dR1
        ${List.Create} dR2
        ${List.Create} dR3
        ${List.Create} dR4
        ${List.Create} dR5
        ${List.Create} dR6
        ${List.Create} dR7
        ${List.Create} dR8
        ${List.Create} dR9
    FunctionEnd
!macroend
!insertmacro Func_InitDump ""
!insertmacro Func_InitDump "un."


/** Записывает текущий моментальный слепок nsis-переменных в Dump */
!define Dump "!insertmacro _Dump"
!macro _Dump
    !ifdef __UNINSTALL__
        Call un.Dump
    !else
        Call Dump
    !endif
!macroend

!macro Func_Dump un
    Function ${un}Dump
        # $0-$9
        ${List.Add} d0 $0
        ${List.Add} d1 $1
        ${List.Add} d2 $2
        ${List.Add} d3 $3
        ${List.Add} d4 $4
        ${List.Add} d5 $5
        ${List.Add} d6 $6
        ${List.Add} d7 $7
        ${List.Add} d8 $8
        ${List.Add} d9 $9
        # R0-R9
        ${List.Add} dR0 $R0
        ${List.Add} dR1 $R1
        ${List.Add} dR2 $R2
        ${List.Add} dR3 $R3
        ${List.Add} dR4 $R4
        ${List.Add} dR5 $R5
        ${List.Add} dR6 $R6
        ${List.Add} dR7 $R7
        ${List.Add} dR8 $R8
        ${List.Add} dR9 $R9
    FunctionEnd
!macroend
!insertmacro Func_Dump ""
!insertmacro Func_Dump "un."


/** Восстанавливает последний моментальный слепок nsis-переменных из Dump */
!define Undump "!insertmacro _Undump"
!macro _Undump
    !ifdef __UNINSTALL__
        Call un.Undump
    !else
        Call Undump
    !endif
!macroend

!macro Func_Undump un
    Function ${un}Undump
        # $0-$9
        ${List.Pop} $0 d0
        ${List.Pop} $1 d1
        ${List.Pop} $2 d2
        ${List.Pop} $3 d3
        ${List.Pop} $4 d4
        ${List.Pop} $5 d5
        ${List.Pop} $6 d6
        ${List.Pop} $7 d7
        ${List.Pop} $8 d8
        ${List.Pop} $9 d9
        # R0-R9
        ${List.Pop} $R0 dR0
        ${List.Pop} $R1 dR1
        ${List.Pop} $R2 dR2
        ${List.Pop} $R3 dR3
        ${List.Pop} $R4 dR4
        ${List.Pop} $R5 dR5
        ${List.Pop} $R6 dR6
        ${List.Pop} $R7 dR7
        ${List.Pop} $R8 dR8
        ${List.Pop} $R9 dR9
    FunctionEnd
!macroend
!insertmacro Func_Undump ""
!insertmacro Func_Undump "un."

И третьим шагом небольшое описание того, как это работает:

Каждая функция созданная и оформленная по правилу, описанному предшествующим основному коду комментарием, будет абсолютно защищённой с точки зрения опасности перезаписи внутри себя глобальных переменных $0-$9, $R0-$R9, а стало быть — от нарушения логики работы вызывающей функции.

Работает это так:

  1. Сама функция F1 вызывается не непосредственно,
    Call F1
    а через макрос-обёртку
    ${F1} [paramIn1 [paramIn2]...[paramInN]] [paramOut1 [paramOut2]...[paramOutM]]
  2. Ещё до непосредственно вызова функции, вызовется
    ${Dump}
    вследствие чего текущие значения переменных $0-$9, $R0-$R9 сохранятся в Dump. Под капотом у него — множество списков, привязанных каждый к своей целевой переменной;
  3. Загрузка в стек всех целевых для функции переменных
    Push ${paramIn1} Push ${paramIn2} ... Push ${paramInN}
    (если необходимо);
  4. Произойдёт вызов функции;
  5. Функция выполнит свою логику и завершит работу;
  6. Произойдёт вызов
    ${Undump}
    в следствии чего сохранённые на последнем вызове ${Dump} значения глобальных переменных будут восстановлены;
  7. Выгрузка из стека результатов работы функции
    Pop ${paramOut1} Pop ${paramOut2} ... Pop ${paramOutM}
    (если необходимо);
  8. Завершит свою работу макро обёртка над функцией F1.

Заключение


Как итог мы получили универсальную конструкцию для безопасного написания nsis-кода.

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

Надеюсь, это кому-то упростит жизнь :)

Спасибо!