-
Notifications
You must be signed in to change notification settings - Fork 15
Labels and Jumps
As long as you're working with macros, you cannot use static labels. A static label can only be used once in a function or section. When inserting a macro, all the code between !macro and !macroend will be substituted for the !insertmacro. As such, you shouldn't use static labels in macros—you could only insert the macro once which makes the idea behind a macro rather pointless. Instead, you can use relative jumps (e.g. Goto +2) or make your labels dynamic by using the standard predefines with your labels. Note the following bit a code:
;=#
; Using the standard predefines. The following uses ${__COUNTER__}
; which always increments on each macro expansion
!macro MacroName
!define UNIQUE ${__COUNTER__} ;= This will auto-increment every time the macro expands.
LOOP_${UNIQUE}:
MessageBox MB_YESNO "Loop this message?" IDYES LOOP_${UNIQUE} IDNO END_${UNIQUE}
;= Some random code here...
END_${UNIQUE}:
!macroend
Remember, your macro may be used multiple times and if it is, then ${UNIQUE} isn't so unique anymore. It should only be unique to that macro when called upon wherever it's called. So, by using ${__COUNTER__} within your macro ensures no two invocations will ever produce the same label.
Here's another workaround which just makes use of another parameter. By adding a parameter to your macro specially meant for loops you can ensure they'll work for that macro when called.
;=#
; Alternatively you could just add a special parameter to
; your macro like in the following:
!macro MyMacro _PARAMETER
${_PARAMETER}_LOOP:
MessageBox MB_YESNO "Loop this message?" IDYES ${_PARAMETER}_LOOP IDNO ${_PARAMETER}_END
;= Some random code here...
${_PARAMETER}_END:
!macroend
However, you must be sure you use a different parameter every time you call the above macro otherwise it'll fail with the same error as the top macro. Personally, I use the first method since sometimes I'll call a macro from within another macro. The first method ensures it works even when you are nesting macros.
Spite what labels can do, relative jumps are, of course, relative to the place they are executed from. Anywhere you can use a label you can also use a relative jump. Relative jumps use numbers to navigate over your code. So, Goto +1 jumps to the next command instruction (which is the default course of action so you'll never see Goto +1 ..lol.), Goto +2 will skip one command instruction and go to the second command from the current instruction, Goto -2 will jump two instructions backward. You can use Goto +37 if your script was able to handle such a leap.
Warning
Be aware that when using relative jumps with macros inside or outside of a macro define you should compensate for the lines that are therein. Remember that a macro is "copy and pasted" or expanded before relative jumps are applied.
You should know that a macro is not considered one command instruction when it comes to relative jumps so when inserting a defined !insertmacro everything that you defined, however many lines of code will take its place. If you aren't careful with how you structure your code, you could very well jump into a bit of code inside an inserted macro which could land you somewhere unexpected and most likely break your launcher. This, my friend, is also hard to debug if you aren't familiar with this concept. Let's look at the following code snippet for more on this subject.
;=#
; A couple of defines and variables used within PortableApps.comLauncher.nsi
Var Admin
!define RUNTIME `$EXEDIR\Data\PortableApps.comLauncherRuntimeData-${APPNAME}.ini`
!define RUNTIME2 `$PLUGINSDIR\runtimedata.ini`
!define WriteRuntimeData "!insertmacro _WriteRuntimeData"
!macro _WriteRuntimeData _SECTION _KEY _VALUE
WriteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}` `${_VALUE}`
WriteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}` `${_VALUE}`
!macroend
System::Call `kernel32::GetModuleHandle(t 'shell32.dll') i .s`
System::Call `kernel32::GetProcAddress(i s, i 680) i .r0`
System::Call `::$0() i .r0`
StrCmpS $0 1 0 +4
${WriteRuntimeData} UAC Admin true
StrCpy $Admin true
Goto +2
StrCpy $Admin false
The above code looks like everything is correct however if we look at line 15 you can see there's a relative jump (+4). This line basically evaluates to if $0 is the same as 1 then jump zero lines otherwise jump four lines ahead. If our Launcher had administrative rights the code would work flawlessly however if our Launcher did not have administrative rights, the variable $Admin would have no value appended to it because ${WriteRuntimeData} is actually two instructions, not one. The above code snippet will actually look like this to the compiler:
;=#
; A couple of defines and variables used within PortableApps.comLauncher.nsi
Var Admin
!define RUNTIME `$EXEDIR\Data\PortableApps.comLauncherRuntimeData-${APPNAME}.ini`
!define RUNTIME2 `$PLUGINSDIR\runtimedata.ini`
!define WriteRuntimeData "!insertmacro _WriteRuntimeData"
!macro _WriteRuntimeData _SECTION _KEY _VALUE
WriteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}` `${_VALUE}`
WriteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}` `${_VALUE}`
!macroend
System::Call `kernel32::GetModuleHandle(t 'shell32.dll') i .s`
System::Call `kernel32::GetProcAddress(i s, i 680) i .r0`
System::Call `::$0() i .r0`
StrCmpS $0 1 0 +4
WriteINIStr `${RUNTIME}` `UAC` `Admin` `true`
WriteINIStr `${RUNTIME2}` `UAC` `Admin` `true`
StrCpy $Admin true
Goto +2
StrCpy $Admin false
So, if our Launcher wasn't running with elevated privileges then line 15 would actually send the compiler to land on line 19. This line is going to send the compiler two lines forward which, consequently, will skip line 20 not appending a true/false value to the variable $Admin. So, you can see the importance of being aware of the aforementioned concept.
Launcher.ini Config Examples