Topic 3.8

Labels and Jumps

Moving Through Code with Labels/Jumps

Static Labels

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 will put the current
; line number in the define ${UNIQUE}
!macro MacroName
	!define UNIQUE ${__LINE__} ;= This will hold the current line number.
	LOOP_${UNIQUE}:
	MessageBox MB_YESNO "Loop this message?" IDYES LOOP_${UNIQUE} IDNO END_${UNIQUE}
	;= Some random code here...
	END_${UNIQUE}:
!macroend

;=#
; 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
Relative Jumps

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.

Caution
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 it's 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 fimiliar 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 appeneding a true/false value to the variable $Admin. So you can see the importance of being aware of the aforementioned concept.