Topic 3.6


A Quick Review of Macros

About Macros

A macro's code is not called at runtime, it is instead inserted at compile time. Think of it like you are copying and pasting your code each time you express the !insertmacro statement. So anything written between the !macro MacroName and the !macroend commands will then be copied and pasted in place of !insertmacro MacroName at compile time.

!macro MacroName _PARM1 _PARM2
  DetailPrint "${_PARM1}"
  MessageBox MB_OK "${_PARM2}"

So you can think of a macro as a piece of code you can write just the once, thus enabling the use of defining just a single command (!insertmacro MacroName) for it's function at anytime thereafter. This enables you to write commonly used snippets of code only once and use it as many times as needed with little changes.

Writing Macros

To create a macro you start with the command !macro and you end it with the !macroend command like in the above snippet. The name of the macro is declared just after the !macro statement. So looking at the above example the macro's name is MacroName. Macros can have parameters, as many as you desire, which may be accessed the same way a !define statement would be from inside the macro. Using the above snippet the parameters would be declared just after the macro's name (_PARM1 and _PARM2). To make use of a marco later on you would use the !insertmacro command (i.e. !insertmacro MacroName "Parameter 1" "Parameter 2").

I use an underscore (_) for parameter names because it is easier this way to discern from !define definitions which may be used within a macro as well. I also try to keep parameters, along with !define statements, capitalized as it is easier to read.

Alright, lets start writing our first macro now. When writing a macro you'll want to think of it's purpose and functionality on how it'll be used. I assume you're familiar with how a PAF is structured and that you are aware about a configuration file called AppInfo.ini that resides in ..\App\AppInfo. To reference the PAL that I'm developing, I sometimes need an easy way to read/write key/values to this configuration file. So let's start with something simple like the following:

!macro ReadAppInfoConfig _VALUE _SECTION _KEY
	ReadINIStr ${_VALUE} `$EXEDIR\App\AppInfo\AppInfo.ini` `${_SECTION}` `${_KEY}`

Now that we have our macro declared we can now access it by expressing the !insertmacro command, like this: !insertmacro ReadAppInfoConfig $0 "Section" "Key". We do not need to reference the file we're trying to access because it's already defined inside the macro declaration. However, that seems like a lot of typing just to use a simple macro that only has one command instruction inside it, right? You might be thinking to yourself, "Why not just use the ReadINIStr command instead?" Well, let's look at this next code snippet.

!define ReadAppInfoConfig `!insertmacro _ReadAppInfoConfig`
!macro _ReadAppInfoConfig _VALUE _SECTION _KEY
	ReadINIStr ${_VALUE} `$EXEDIR\App\AppInfo\AppInfo.ini` `${_SECTION}` `${_KEY}`

Do you see what's going on in the above code block? If not, I'll explain. Line 1 is a !define which declares ReadAppInfoConfig as !insertmacro _ReadAppInfoConfig. Remember, a macro is like copying and pasting so any time you use ${ReadAppInfoConfig} it'll be replaced with !insertmacro _ReadAppInfoConfig. Pretty cool, huh? so now you can do something like the following:

;=# The following code is what we see and is easy to read:
${ReadAppInfoConfig} $0 "Details" "Name"
${ReadAppInfoConfig} $1 "Version" "PackageVersion"
;= $0 == Application Name
;= $1 ==
MessageBox MB_OK "[Details]Name | $$0 == $0$\r$\n[Version]PackageVersion | $$1 == $1"

;=# To the compiler the above code will look like this:
ReadINIStr $0 `$EXEDIR\App\AppInfo\AppInfo.ini` `Details` `Name`
ReadINIStr $1 `$EXEDIR\App\AppInfo\AppInfo.ini` `Version` `PackageVersion`
;= $0 == Application Name
;= $1 ==
MessageBox MB_OK "[Details]Name | $$0 == $0$\r$\n[Version]PackageVersion | $$1 == $1"
As Functions
The following section was submitted by fellow portable developer, LegendaryHawk. Thank you LegendaryHawk for sharing this topic.

Let's talk about a so-called "Library". Those are basically collections of code, that have a similar purpose, but with slight modifications. Some handle processes, reading/writing to/from a file, etc.

As a portable application developer, we use this advanced concept to create some more functionality; for example, communicating with some system internals. You can also use plug-ins those and make some very interesting functionality, that can really help you out in the future. As an example, NSIS cannot move files across drives, so we have to find a way to do this ourselves (if possible).

I recommend reading through Microsoft's documentation (MSDN) for very interesting stuff about system related stuff and how things work.

The main difference in our case, is the inability of functions taking parameters. That's why we use macros. This is discussed in more detail in the next topic.


    MessageBox MB_OK '${_STRING1} is as stupid as ${_STRING2}'

I know what you might be thinking, "Why not use the stack and push/pop stuff?" The reason for this is, that you have to manage the stack every single time you do something with a function. We all know how easy it is to make mistakes doing this sometimes. That's why we let a macro handle the stack automatically by setting everything up just once.


; --- Includes
!include Util.nsh

!macro MoveFilesCall _FROM _TO
    !verbose push 3
    Push `${_FROM}`
    Push `${_TO}`
    ${CallArtificialFunction} MoveFiles_
    !verbose pop
!define MoveFiles '!insertmacro MoveFilesCall'

!macro MoveFiles_
    !verbose push 3

    ; Prepare stack and registers
    Exch $1 ; _TO
    Exch $0 ; _FROM
    Push $2 ; Push $2 to the stack. Otherwise, we can't restore everything correctly

    ; Move everything without asking anything
    System::Call '*(i$HWNDPARENT, i0x0001, t"$0", t"$1", i0x0004|0x0010|0x0200|0x0400, i0, i0, i0)i.s' ; FO_MOVE | FOF_SILENT, FOF_NOCONFIRMATION, FOF_NOCONFIRMMKDIR, FOF_NOERRORUI
    Pop $2
    Push $2
    System::Call shell32::SHFileOperationW(is)
    System::Free $2

    ; Restore original variables
    Pop $2
    Pop $1
    Pop $0

    !verbose pop

So, let's start at the top for some explanation. The !ifndef acts basically as an "include once" switch, so if you have multiple files wanting to include this file, it just goes past the code. Next, we include the Utils.nsh, which we need to be able to use ${CallArtificialFunction}. Now, our macro MoveFilesCall. In this case, it sets up some stuff, before the Call to the actual "function" happens. Arguments are pushed to the stack, "function" is called.

At last, we have the interesting part: MoveFiles_. That piece of code is the actual function we are going to execute. As you can see, we need three variables in this code, so we need to prepare the stack and save the content in already existing variables. Then, the actual magic happens. It would be way off track to explain the whole thing, but feel free to read the documentation of the System plug-in and the SHFileOperation to understand, what we are doing here. Then, we restore the original variables, so we don't corrupt any of the code that follows it. In a real world project, you could use it like this:

; Download file from the internet
; Code for extraction of the installer

${MoveFiles} "$TEMP\installer\*.*" "$INSTDIR"

; Other code

I know that this concept is not really needed all the time, and can lead to some frustration, but it really is worth playing around with it in the end. A good library can save you the need for working with outdated plug-ins, plus you actually learn something about your system.

Getting a little off-topic now, but something nobody will usually ever tell you; even though you have the MSDN available to you, not all the stuff you want/need is documented inside. The FO_MOVE instruction you see above, actually is a hex-code in our case. Those things (and many more) can be found in assembler code. Just look for MASM32, UASM, WinInc or whatever you like. You could also look at the EasyCode project, which actually is a editor for assembler code, but also has some includes worth looking at.

Useful Macros

The code block below contains things that may be used with my version of PAL. It is all declared in the PortableApps.comLauncher.nsi file which means you are able to use any !define and !macro in the custom.nsh file. Hopefully you now have the means to read the code so it'll be self-explanatory and understandable.

If you don't understand something and/or need help on what something actually does, just leave me a comment further below and I'll asset you accordingly.
;= ################
!define APPINFO					`$EXEDIR\App\AppInfo`
!define INFOINI					`${APPINFO}\appinfo.ini`
!define DATA					`$EXEDIR\Data`
!define SET						`${DATA}\settings`
!define DEFDATA					`$EXEDIR\App\DefaultData`
!define DEFSET					`${DEFDATA}\settings`
!define LAUNCHDIR				`${APPINFO}\Launcher`
!define LAUNCHER				`${LAUNCHDIR}\${APPNAME}.ini`
!define LAUNCHER2				`$PLUGINSDIR\launcher.ini`
!define RUNTIME					`${DATA}\PortableApps.comLauncherRuntimeData-${APPNAME}.ini`
!define RUNTIME2				`$PLUGINSDIR\runtimedata.ini`
!define SETINI					`${SET}\${APPNAME}Settings.ini`
!define CONFIG					`$EXEDIR\${APPNAME}.ini`
!define OTHER					`$EXEDIR\Other`
!define PAL						PortableApps.comLauncher

;= ################
; ${ReadAppInfoConfig} $0 "Section" "Key"
!define ReadAppInfoConfig `!insertmacro _ReadAppInfoConfig`
!macro _ReadAppInfoConfig _VALUE _SECTION _KEY
	ReadINIStr ${_VALUE} `${INFOINI}` `${_SECTION}` `${_KEY}`

; ${WriteAppInfoConfig} "Section" "Key" "Value"
!define WriteAppInfoConfig `!insertmacro _WriteAppInfoConfig`
!macro _WriteAppInfoConfig _SECTION _KEY _VALUE
	WriteINIStr `${INFOINI}` `${_SECTION}` `${_KEY}` `${_VALUE}`

; ${DeleteAppInfoConfig} "Section" "Key"
!define DeleteAppInfoConfig `!insertmacro _DeleteAppInfoConfig`
!macro _DeleteAppInfoConfig _SECTION _KEY
	DeleteINIStr `${INFOINI}` `${_SECTION}` `${_KEY}`

; ${DeleteAppInfoConfigSec} "Section"
!define DeleteAppInfoConfigSec `!insertmacro _DeleteAppInfoConfigSec`
!macro _DeleteAppInfoConfigSec _SECTION
	DeleteINISec `${LAUNCHER}` `${_SECTION}`

; ${ReadLauncherConfig} $0 "Section" "Key"
!define ReadLauncherConfig `!insertmacro _ReadLauncherConfig`
!macro _ReadLauncherConfig _VALUE _SECTION _KEY
	ReadINIStr ${_VALUE} `${LAUNCHER}` `${_SECTION}` `${_KEY}`

; ${WriteLauncherConfig} "Section" "Key" "Value"
!define WriteLauncherConfig `!insertmacro _WriteLauncherConfig`
!macro _WriteLauncherConfig _SECTION _KEY _VALUE
	WriteINIStr `${LAUNCHER}` `${_SECTION}` `${_KEY}` `${_VALUE}`

; ${DeleteLauncherConfig} "Section" "Key"
!define DeleteLauncherConfig `!insertmacro _DeleteLauncherConfig`
!macro _DeleteLauncherConfig _SECTION _KEY
	DeleteINIStr `${LAUNCHER}` `${_SECTION}` `${_KEY}`

; ${DeleteLauncherConfigSec} "Section"
!define DeleteLauncherConfigSec `!insertmacro _DeleteLauncherConfigSec`
!macro _DeleteLauncherConfigSec _SECTION
	DeleteINISec `${LAUNCHER}` `${_SECTION}`

; ${ReadLauncherConfigWithDefault} $0 "Section" "Key" "Default Value"
!define ReadLauncherConfigWithDefault `!insertmacro _ReadLauncherConfigWithDefault`
!macro _ReadLauncherConfigWithDefault _VALUE _SECTION _KEY _DEFAULT
	${ReadLauncherConfig} ${_VALUE} `${_SECTION}` `${_KEY}`
	${IfThen} ${Errors} ${|} StrCpy ${_VALUE} `${_DEFAULT}` ${|}

; ${ReadUserConfig} $0 "Key"
!define ReadUserConfig `!insertmacro _ReadUserConfig`
!macro _ReadUserConfig _VALUE _KEY
	${ConfigReadS} `${CONFIG}` `${_KEY}=` `${_VALUE}`

; ${WriteUserConfig} "Value" "Key"
!define WriteUserConfig `!insertmacro _WriteUserConfig`
!macro _WriteUserConfig _VALUE _KEY
	${ConfigWriteS} `${CONFIG}` `${_KEY}=` `${_VALUE}` $R0

; ${WriteRuntimeData} "Section" "Key" "Value"
!define WriteRuntimeData "!insertmacro _WriteRuntimeData"
!macro _WriteRuntimeData _SECTION _KEY _VALUE
	WriteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}` `${_VALUE}`
	WriteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}` `${_VALUE}`

; ${DeleteRuntimeData} "Section" "Key"
!define DeleteRuntimeData "!insertmacro _DeleteRuntimeData"
!macro _DeleteRuntimeData _SECTION _KEY
	DeleteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}`
	DeleteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}`

; ${ReadRuntimeData} $0 "Section" "Key"
!define ReadRuntimeData "!insertmacro _ReadRuntimeData"
!macro _ReadRuntimeData _RETURN _SECTION _KEY
	IfFileExists `${RUNTIME}` 0 +3
	ReadINIStr `${_RETURN}` `${RUNTIME}` `${_SECTION}` `${_KEY}`
	Goto +2
	ReadINIStr `${_RETURN}` `${RUNTIME2}` `${_SECTION}` `${_KEY}`

; ${WriteRuntime} "Value" "Key"
!define WriteRuntime "!insertmacro _WriteRuntime"
!macro _WriteRuntime _VALUE _KEY
	WriteINIStr `${RUNTIME}` ${PAL} `${_KEY}` `${_VALUE}`
	WriteINIStr `${RUNTIME2}` ${PAL} `${_KEY}` `${_VALUE}`

; ${ReadRuntime} $0 "Key"
!define ReadRuntime "!insertmacro _ReadRuntime"
!macro _ReadRuntime _VALUE _KEY
	IfFileExists `${RUNTIME}` 0 +3
	ReadINIStr `${_VALUE}` `${RUNTIME}` ${PAL} `${_KEY}`
	Goto +2
	ReadINIStr `${_VALUE}` `${RUNTIME2}` ${PAL} `${_KEY}`

; ${WriteSettings} "Value" "Key"
!define WriteSettings `!insertmacro _WriteSettings`
!macro _WriteSettings _VALUE _KEY
	WriteINIStr `${SETINI}` ${APPNAME}Settings `${_KEY}` `${_VALUE}`

; ${ReadSettings} $0 "Key"
!define ReadSettings `!insertmacro _ReadSettings`
!macro _ReadSettings _VALUE _KEY
	ReadINIStr `${_VALUE}` `${SETINI}` ${APPNAME}Settings `${_KEY}`

; ${DeleteSettings} "Key"
!define DeleteSettings `!insertmacro _DeleteSettings`
!macro _DeleteSettings _KEY
	DeleteINIStr `${SETINI}` ${APPNAME}Settings `${_KEY}`