Целевой хаб не соответствует содержанию статьи, но что поделать… — тематических по цели нет, а наиболее подходящий «С», т.к. читатели именно его с наибольшей лёгкостью смогут воспринять написанное.
Цель статьи
Вариант решение проблемы контроля за глобальными переменными при написании инсталлятора/деинсталлятора средствами 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, $R1-$R9, а стало быть — от нарушения логики работы вызывающей функции.
Работает это так:
- Сама функция F1 вызывается не непосредственно,
Call F1а через макрос-обёртку
${F1} [paramIn1 [paramIn2]...[paramInN]] [paramOut1 [paramOut2]...[paramOutM]] - Ещё до непосредственно вызова функции, вызовется
${Dump}в следствии чего текущие значения переменных $0-$9, $R0-$R9 сохранятся в Dump, реализованный «под капотом» множеством, привязанных каждый к своей целевой переменной, списков;
- Загрузка в стек всех целевых для функции переменных
Push ${paramIn1} Push ${paramIn2} ... Push ${paramInN}(если необходимо);
- Произойдёт вызов функции;
- Функция выполнит свою логику и завершит работу;
- Произойдёт вызов
${Undump}в следствии чего сохранённые на последнем вызове ${Dump} значения глобальных переменных будут восстановлены;
- Выгрузка из стека результатов работы функции
Pop ${paramOut1} Pop ${paramOut2} ... Pop ${paramOutM}(если необходимо);
- Завершит свою работу макро обёртка над функцией F1.
Заключение
Как итог мы получили универсальную конструкцию для безопасного написания nsis-кода.
Можно даже сказать, что она не привносит каких-либо минусов, за исключением того, что скомпилированный код будет работать примерно на 30 мл.сек медленнее.
Надеюсь, это кому-то упростит жизнь 🙂
Спасибо!
ссылка на оригинал статьи https://habr.com/ru/post/486302/
Добавить комментарий