Macros
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}"
!macroend
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"
).
_
) 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}`
!macroend
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}`
!macroend
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 == 1.2.3.4
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 == 1.2.3.4
MessageBox MB_OK "[Details]Name | $$0 == $0$\r$\n[Version]PackageVersion | $$1 == $1"
As Functions
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).
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.
Function THIS_WILL_WORK
Nop
FunctionEnd
Function THIS_WILL_NOT_WORK _STRING1 _STRING2
MessageBox MB_OK '${_STRING1} is as stupid as ${_STRING2}'
FunctionEnd
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.
!ifndef MOVEFILES_NSH_INCLUDED
!define MOVEFILES_NSH_INCLUDED
; --- Includes
!include Util.nsh
!macro MoveFilesCall _FROM _TO
!verbose push 3
Push `${_FROM}`
Push `${_TO}`
${CallArtificialFunction} MoveFiles_
!verbose pop
!macroend
!define MoveFiles '!insertmacro MoveFilesCall'
!macro MoveFiles_
!verbose push 3
; Prepare stack and registers
Exch $1 ; _TO
Exch
Exch $0 ; _FROM
Exch
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
!macroend
!endif ; MOVEFILES_NSH_INCLUDED
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.
;= DEFINES
;= ################
!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
;= MACROS
;= ################
; ${ReadAppInfoConfig} $0 "Section" "Key"
!define ReadAppInfoConfig `!insertmacro _ReadAppInfoConfig`
!macro _ReadAppInfoConfig _VALUE _SECTION _KEY
ReadINIStr ${_VALUE} `${INFOINI}` `${_SECTION}` `${_KEY}`
!macroend
; ${WriteAppInfoConfig} "Section" "Key" "Value"
!define WriteAppInfoConfig `!insertmacro _WriteAppInfoConfig`
!macro _WriteAppInfoConfig _SECTION _KEY _VALUE
WriteINIStr `${INFOINI}` `${_SECTION}` `${_KEY}` `${_VALUE}`
!macroend
; ${DeleteAppInfoConfig} "Section" "Key"
!define DeleteAppInfoConfig `!insertmacro _DeleteAppInfoConfig`
!macro _DeleteAppInfoConfig _SECTION _KEY
DeleteINIStr `${INFOINI}` `${_SECTION}` `${_KEY}`
!macroend
; ${DeleteAppInfoConfigSec} "Section"
!define DeleteAppInfoConfigSec `!insertmacro _DeleteAppInfoConfigSec`
!macro _DeleteAppInfoConfigSec _SECTION
DeleteINISec `${LAUNCHER}` `${_SECTION}`
!macroend
; ${ReadLauncherConfig} $0 "Section" "Key"
!define ReadLauncherConfig `!insertmacro _ReadLauncherConfig`
!macro _ReadLauncherConfig _VALUE _SECTION _KEY
ReadINIStr ${_VALUE} `${LAUNCHER}` `${_SECTION}` `${_KEY}`
!macroend
; ${WriteLauncherConfig} "Section" "Key" "Value"
!define WriteLauncherConfig `!insertmacro _WriteLauncherConfig`
!macro _WriteLauncherConfig _SECTION _KEY _VALUE
WriteINIStr `${LAUNCHER}` `${_SECTION}` `${_KEY}` `${_VALUE}`
!macroend
; ${DeleteLauncherConfig} "Section" "Key"
!define DeleteLauncherConfig `!insertmacro _DeleteLauncherConfig`
!macro _DeleteLauncherConfig _SECTION _KEY
DeleteINIStr `${LAUNCHER}` `${_SECTION}` `${_KEY}`
!macroend
; ${DeleteLauncherConfigSec} "Section"
!define DeleteLauncherConfigSec `!insertmacro _DeleteLauncherConfigSec`
!macro _DeleteLauncherConfigSec _SECTION
DeleteINISec `${LAUNCHER}` `${_SECTION}`
!macroend
; ${ReadLauncherConfigWithDefault} $0 "Section" "Key" "Default Value"
!define ReadLauncherConfigWithDefault `!insertmacro _ReadLauncherConfigWithDefault`
!macro _ReadLauncherConfigWithDefault _VALUE _SECTION _KEY _DEFAULT
ClearErrors
${ReadLauncherConfig} ${_VALUE} `${_SECTION}` `${_KEY}`
${IfThen} ${Errors} ${|} StrCpy ${_VALUE} `${_DEFAULT}` ${|}
!macroend
; ${ReadUserConfig} $0 "Key"
!define ReadUserConfig `!insertmacro _ReadUserConfig`
!macro _ReadUserConfig _VALUE _KEY
${ConfigReadS} `${CONFIG}` `${_KEY}=` `${_VALUE}`
!macroend
; ${WriteUserConfig} "Value" "Key"
!define WriteUserConfig `!insertmacro _WriteUserConfig`
!macro _WriteUserConfig _VALUE _KEY
${ConfigWriteS} `${CONFIG}` `${_KEY}=` `${_VALUE}` $R0
!macroend
; ${WriteRuntimeData} "Section" "Key" "Value"
!define WriteRuntimeData "!insertmacro _WriteRuntimeData"
!macro _WriteRuntimeData _SECTION _KEY _VALUE
WriteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}` `${_VALUE}`
WriteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}` `${_VALUE}`
!macroend
; ${DeleteRuntimeData} "Section" "Key"
!define DeleteRuntimeData "!insertmacro _DeleteRuntimeData"
!macro _DeleteRuntimeData _SECTION _KEY
DeleteINIStr `${RUNTIME}` `${_SECTION}` `${_KEY}`
DeleteINIStr `${RUNTIME2}` `${_SECTION}` `${_KEY}`
!macroend
; ${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}`
!macroend
; ${WriteRuntime} "Value" "Key"
!define WriteRuntime "!insertmacro _WriteRuntime"
!macro _WriteRuntime _VALUE _KEY
WriteINIStr `${RUNTIME}` ${PAL} `${_KEY}` `${_VALUE}`
WriteINIStr `${RUNTIME2}` ${PAL} `${_KEY}` `${_VALUE}`
!macroend
; ${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}`
!macroend
; ${WriteSettings} "Value" "Key"
!define WriteSettings `!insertmacro _WriteSettings`
!macro _WriteSettings _VALUE _KEY
WriteINIStr `${SETINI}` ${APPNAME}Settings `${_KEY}` `${_VALUE}`
!macroend
; ${ReadSettings} $0 "Key"
!define ReadSettings `!insertmacro _ReadSettings`
!macro _ReadSettings _VALUE _KEY
ReadINIStr `${_VALUE}` `${SETINI}` ${APPNAME}Settings `${_KEY}`
!macroend
; ${DeleteSettings} "Key"
!define DeleteSettings `!insertmacro _DeleteSettings`
!macro _DeleteSettings _KEY
DeleteINIStr `${SETINI}` ${APPNAME}Settings `${_KEY}`
!macroend