Macro vs Function: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
No edit summary
No edit summary
Line 26: Line 26:


Could be seen as just:
Could be seen as just:
<small>Example 1.1.2</small>
<small>Example 1.1.2</small>
<highlight-nsis>
<highlight-nsis>
Line 62: Line 63:
<small>Example 1.1.4</small>
<small>Example 1.1.4</small>
<highlight-nsis>
<highlight-nsis>
Section Test
   DetailPrint "Hello World"
   DetailPrint "Hello World"
   DetailPrint "Hello Tree"
   DetailPrint "Hello Tree"
   DetailPrint "Hello Flower"
   DetailPrint "Hello Flower"
SectionEnd
</highlight-nsis>
</highlight-nsis>


Line 118: Line 121:
</highlight-nsis>
</highlight-nsis>


= Caveats of Macros and Functions =
However, you cannot pass parameters/arguments to Functions as easily as you can with Macros.  I.e. the following is NOT a valid code equivalent of Example 1.1.3:
 
Example: 1.2.2
<highlight-nsis>
Function Hello What
  DetailPrint "Hello ${What}"
FunctionEnd
 
Section Test
  Call Hello "World"
  Call Hello "Tree"
  Call Hello "Flower"
SectionEnd
</highlight-nsis>
 
You could, of course, try to take a page out of the earlier installer/uninstaller book and wrap the function in a macro:
 
<small>Example 1.2.3</small>
<highlight-nsis>
!macro Hello What
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
!macroend
 
Section Test
  !insertmacro Hello "World"
SectionEnd
</highlight-nsis>
But this is -also- invalid code, as the code would end up having a Function definition within a section, which is not allowed:
 
<small>Example 1.2.4</small>
<highlight-nsis>
Section Test
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
SectionEnd
</highlight-nsis>
 
( even if Function definitions were allowed within Sections, keep in mind that you would have more than a single Function named "Hello" - which is also not allowed )
 
Instead, passing parameters/arguments to Functions is typically done through variables...
 
<small>Example 1.2.5</small>
<highlight-nsis>
var myVar
 
Function Hello
  DetailPrint "Hello $myVar"
FunctionEnd
 
Section Test
  StrCpy $myVar "World"
  Call Hello
SectionEnd
</highlight-nsis>
 
...or, more commonly, the stack (to save memory used by variables)
 
<small>Example 1.2.6</small>
<highlight-nsis>
Function Hello
  Exch $0
  DetailPrint "Hello $0"
  Pop $0
FunctionEnd
 
Section Test
  Push "World"
  Call Hello
SectionEnd
</highlight-nsis>
 
Which ends up acting as (but please note that the code is not actually inserted as such!):
 
<small>Example 1.2.7</small>
<highlight-nsis>
Section Test
  Push "World"
  ; At this point, the stack looks like the following (top is left, bottom is right)
  ; :: "World" -rest of the stack, if anything-
  Exch $0
  ; At this point the old value of $0 is now at the top of the stack, while "World" is copied into variable $0
  ; :: old$0 -rest of the stack, if anything-
  DetailPrint "Hello World"
  Pop $0
  ; At this point the old value of $0 is restored, and the stack just looks like the following:
  ; :: -rest of the stack, if anything-
</highlight-nsis>
 
Functions can either clear the stack relevant to the function, as in the above, or they can leave an item on the stack for use after the function.
 
<small>Example 1.2.8</small>
<highlight-nsis>
Function isDir
  Exch $0
  IfFileExists "$0\*.*" _dir _notdir
  _dir:
    StrCpy $0 "true"
    return
  _notdir:
    StrCpy $0 "false"
FunctionEnd
 
Section Test
  Push "some path on your drive"
  Call isDir
  Pop $0
SectionEnd
</highlight-nsis>
 
Where in the above code, after the call to "isDir", $0 contains "true" if the path was a directory, and "false" if not.
 
= Hybrid =
Mainly because of the fact that you can't easily pass parameters to Functions, many users adopt a hybrid approach employing both macros -and- functions, and a little bit of a define. (a define just allows you to say that e.g. "NSIS" means "Nullsoft Scriptable Install System" without having to write out as much).
 
<small>Example 2.1</small>
<highlight-nsis>
!define writeFile "!insertmacro writeFile"
 
!macro writeFile File String
  Push ${File}
  Push ${String}
  Call writeFile
!macroend
 
Function WriteFile
                              ; Stack: <file> <string>
  ClearErrors
  Exch $0                    ; Stack: $0 <string>
  Exch                        ; Stack: <string> $0
  Exch $1                    ; Stack: $1 $0
  Push $2                    ; Stack: $2 $1 $0
  ; $0 = file
  ; $1 = string
  FileOpen $2 "$0" "a"
  FileSeek $2 0 END
  FileWrite $2 $1
  FileClose $2
  Pop $2                      ; Stack: $1 $0
  Pop $1                      ; Stack: $0
  Pop $0                      ; Stack: -empty-
FunctionEnd
 
Section Test
  ${writeFile} "$TEMP\alogfile.txt" "A log entry"
SectionEnd
</highlight-nsis>
 
As you can see, this allows you to combine the best of both worlds by reducing code redundancy both in your installation script -and- in the compiled installer and allowing you to 'pass' parameters/arguments to a function by using a macro as an intermediate.  And in the end, your code looks even cleaner.
 
= Caveats - or Pros/Cons =
- insert content -
- insert content -


[[Category:Scripting FAQ]]
[[Category:Scripting FAQ]]

Revision as of 14:19, 16 September 2006

Author: Animaether (talk, contrib)


This page is currently in process of heavy editing. Please do not modify!

Macros and Functions in NSIS are powerful tools to help make coding your installer easier and more flexible. They can also be very powerful tools, allowing you to extend the NSIS scripting language to an extent - LogicLib is a great example of this.

Definitions

Macros

From the NSIS help file: "Macros are used to insert code at compile time, depending on defines and using the values of the defines. The macro's commands are inserted at compile time. This allows you to write a general code only once and use it a lot of times but with a few changes."

If you are new to macros in any scripting language, then this may sound a bit confusing. Let's go through a few examples to show what the above means.

"Macros are used to insert code at compile time"

What this means is that the code you define in a macro will simply be inserted at the location of your !insertmacro, as if copy/pasted, when you compile your installer script.

Example 1.1.1

!macro Hello
  DetailPrint "Hello world"
!macroend
 
Section Test
  !insertmacro Hello
SectionEnd

Could be seen as just:

Example 1.1.2

Section Test
  DetailPrint "Hello world"
SectionEnd

The above is obviously just a simple example with a single line and really wouldn't be a good use of macros. But if you have multiple lines of code that you may have to use over and over again, it may be a good idea to start using a macro for it; it allows you to just make the any changes once in the macro, and it will automatically be changed anywhere you insert it. It also just makes your code look a lot cleaner (a single !insertmacro line vs perhaps a dozen lines) which makes it a lot easier to follow and edit.

"depending on defines and using the values of the defines"

That bit is most likely to sound confusing, but gets a little more clear once you read the definition of macros in another section of the NSIS help:

"macro definitions can have one or more parameters defined. The parameters may be accessed the same way a !define would (e.g. ${PARMNAME}) from inside the macro."

If you are familiar with scripting in other languages, this may sound familiar to you, except in the context of functions - but please do not mix this up with Functions as they exist in the NSIS language.. we'll get to those later.

What the above means is that you can have a macro take parameters, or arguments, and use those within the macro:

Example 1.1.3

!macro Hello What
  DetailPrint "Hello ${What}"
!macroend
 
Section Test
  !insertmacro Hello "World"
  !insertmacro Hello "Tree"
  !insertmacro Hello "Flower"
SectionEnd

Could be seen as just:

Example 1.1.4

Section Test
  DetailPrint "Hello World"
  DetailPrint "Hello Tree"
  DetailPrint "Hello Flower"
SectionEnd

However, you only needed a single macro definition to get these three different results. Let's take a more complex example, straight from the NSIS Help. In NSIS, a Function can only be specified as being for the Installer, or the Uninstaller (prefixed by "un."). The reason for this is that it allows the compiler to make a smaller Uninstaller if it does not need the Installer's functions, and vice-versa. However, sometimes you may have a function that both the Installer and the Uninstaller require. You could then code the Function twice:

Example 1.1.5

Function SomeFunc
  ; Lots of code here
FunctionEnd
 
Function un.SomeFunc
  ; Lots of code here
FunctionEnd

However, as it should be apparent, if there's lots of code involved you have two separate areas where you have to make code changes, your code looks less clean, etc.

With the use of macros, this can easily be resolved by writing a macro around the function that simply adds the "un." prefix when requested:

Example 1.1.6

!macro SomeFunc un
  Function ${un}SomeFunc
    ; lots of code here
  FunctionEnd
!macroend
!insertmacro SomeFunc ""
!insertmacro SomeFunc "un."

The above will then be written out as example 1.1.5, but now you only have a single portion of code to maintain.

For more information on this specific topic, see: Sharing_functions_between_Installer_and_Uninstaller

Functions

Let's start again with what the NSIS Help file says on them:

"Functions are similar to Sections in that they contain zero or more instructions."

Let's try a different definition. Functions are like macros, except that the code does -not- actually get inserted or copy/pasted, if you will, when compiling. The compiled code only exists just once in your final installer.

A Function use equivalent to the Macro example 1.1.1 would be:

Example 1.2.1

Function Hello
  DetailPrint "Hello world"
FunctionEnd
 
Section Test
  Call Hello
SectionEnd

However, you cannot pass parameters/arguments to Functions as easily as you can with Macros. I.e. the following is NOT a valid code equivalent of Example 1.1.3:

Example: 1.2.2

Function Hello What
  DetailPrint "Hello ${What}"
FunctionEnd
 
Section Test
  Call Hello "World"
  Call Hello "Tree"
  Call Hello "Flower"
SectionEnd

You could, of course, try to take a page out of the earlier installer/uninstaller book and wrap the function in a macro:

Example 1.2.3

!macro Hello What
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
!macroend
 
Section Test
  !insertmacro Hello "World"
SectionEnd

But this is -also- invalid code, as the code would end up having a Function definition within a section, which is not allowed:

Example 1.2.4

Section Test
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
SectionEnd

( even if Function definitions were allowed within Sections, keep in mind that you would have more than a single Function named "Hello" - which is also not allowed )

Instead, passing parameters/arguments to Functions is typically done through variables...

Example 1.2.5

var myVar
 
Function Hello
  DetailPrint "Hello $myVar"
FunctionEnd
 
Section Test
  StrCpy $myVar "World"
  Call Hello
SectionEnd

...or, more commonly, the stack (to save memory used by variables)

Example 1.2.6

Function Hello
  Exch $0
  DetailPrint "Hello $0"
  Pop $0
FunctionEnd
 
Section Test
  Push "World"
  Call Hello
SectionEnd

Which ends up acting as (but please note that the code is not actually inserted as such!):

Example 1.2.7

Section Test
  Push "World"
  ; At this point, the stack looks like the following (top is left, bottom is right)
  ; :: "World" -rest of the stack, if anything-
  Exch $0
  ; At this point the old value of $0 is now at the top of the stack, while "World" is copied into variable $0
  ; :: old$0 -rest of the stack, if anything-
  DetailPrint "Hello World"
  Pop $0
  ; At this point the old value of $0 is restored, and the stack just looks like the following:
  ; :: -rest of the stack, if anything-

Functions can either clear the stack relevant to the function, as in the above, or they can leave an item on the stack for use after the function.

Example 1.2.8

Function isDir
  Exch $0
  IfFileExists "$0\*.*" _dir _notdir
  _dir:
    StrCpy $0 "true"
    return
  _notdir:
    StrCpy $0 "false"
FunctionEnd
 
Section Test
  Push "some path on your drive"
  Call isDir
  Pop $0
SectionEnd

Where in the above code, after the call to "isDir", $0 contains "true" if the path was a directory, and "false" if not.

Hybrid

Mainly because of the fact that you can't easily pass parameters to Functions, many users adopt a hybrid approach employing both macros -and- functions, and a little bit of a define. (a define just allows you to say that e.g. "NSIS" means "Nullsoft Scriptable Install System" without having to write out as much).

Example 2.1

!define writeFile "!insertmacro writeFile"
 
!macro writeFile File String
  Push ${File}
  Push ${String}
  Call writeFile
!macroend
 
Function WriteFile
                              ; Stack: <file> <string>
  ClearErrors
  Exch $0                     ; Stack: $0 <string>
  Exch                        ; Stack: <string> $0
  Exch $1                     ; Stack: $1 $0
  Push $2                     ; Stack: $2 $1 $0
  ; $0 = file
  ; $1 = string
  FileOpen $2 "$0" "a"
  FileSeek $2 0 END
  FileWrite $2 $1
  FileClose $2
  Pop $2                      ; Stack: $1 $0
  Pop $1                      ; Stack: $0
  Pop $0                      ; Stack: -empty-
FunctionEnd
 
Section Test
  ${writeFile} "$TEMP\alogfile.txt" "A log entry"
SectionEnd

As you can see, this allows you to combine the best of both worlds by reducing code redundancy both in your installation script -and- in the compiled installer and allowing you to 'pass' parameters/arguments to a function by using a macro as an intermediate. And in the end, your code looks even cleaner.

Caveats - or Pros/Cons

- insert content -