NSISをもう少し使いこなす

#Unicode
[NSISでインストーラを作ってみる その1](https://qiita.com/yuki_nishina/items/176d6d77911f2b54bd75)でお茶を濁しましたが、最新のNSIS(3.06.1)ではUnicodeを使用しないと警告が表示されるようになっています。

```
1 warning:
  7998: ANSI targets are deprecated
```
個人的には今までファイル名やディレクトリ名にUnicodeが使われたソフトウェアはあまり見た事がありませんが、プログラム内部ではよく使います。
又、最近のWindows アプリケーションはUnicodeを扱える事が必須となりつつあります。
Unicodeの使用は以下をSectionやFunctionの外部に記述します。
通常は、.nsiファイルの先頭に記述すると良いでしょう。
[Reference/Unicode - NSIS](https://nsis.sourceforge.io/Reference/Unicode)

```
Unicode true
```
ファイルのエンコーディングはUTF-8で良いはずですが、ファイル内容によっては何故かエラーとなる場合がありました。
その場合、BOM付きにすれば大丈夫でした。UTF-16でも良いですが…

#変数
変数は`$`で始まります。すべて文字列型で、グローバル変数(どのSectionやFunctionからもアクセスできる)のみで、宣言が必要です。
宣言はSectionやFunctionの外部に`Var 変数名`のように記述します。
使う時は、`$変数名`のように記述します。

```nsis:Field
Var SomeField

Section
  # 変数に値を代入します。
  StrCpy $SomeField "変数に代入"
  MessageBox MB_OK $SomeField
SectionEnd
```
`StrCpy`は、変数に値を代入する関数です。
[Reference/StrCpy - NSIS](https://nsis.sourceforge.io/Reference/StrCpy)
C言語を扱っている方にはお馴染みですが、関数名の通り、文字列をコピーする(String Copy)と理解した方が適切かもしれません。
`MessageBox`はメッセージ ボックスを表示させます。
[Reference/MessageBox - NSIS](https://nsis.sourceforge.io/Reference/MessageBox)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/a5765d46-9acf-07f8-78f6-26b4ffe6990e.png)
`$0`のようなあらかじめ用意された変数を使う場面も多いと思います。詳しくは以下を読んでみて下さい。
[4.2 Variables](https://nsis.sourceforge.io/Docs/Chapter4.html#variables)
#!define
又、これまたC言語ではお馴染みですが、`!define`はプリプロセッサ指令的な使い方(コンパイル前に展開される)をします。
以下の場合は、スタンプのような使い方ができます。

```nsis:Define_001
!define Message "コンパイル時に確定する文字列"

Section
  MessageBox MB_OK ${Message}
SectionEnd
```
※!defineで定義した値は、中かっこを付けないと展開されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/36ecd0eb-8a43-6d6f-02e4-b3afb7524566.png)
変数ではないので、ステートメントそのものも定義できます。

```nsis:Define_002
Var SomeField
!define ShowField "MessageBox MB_OK ${SomeField}"

Section
  StrCpy $SomeField "変数に代入"
  ${ShowField}
SectionEnd
```
又、中身(第2引数)を記述せず、スイッチのような使い方をする場合もあります。
色々な用途がありますが、詳細は下記をご覧下さい。
[Reference/!define - NSIS](https://nsis.sourceforge.io/Reference/!define)
#条件分岐
条件分岐は標準では扱いづらいものになっています。

```nsis:Branch_001
Var Actual
!define Expected "バナナ"

Section
  StrCpy $Actual "バナナ"
  StrCmp $Actual "${Expected}" Banana NotBanana
  Banana:
    MessageBox MB_OK "Actual はバナナです"
    Return
  NotBanana:
    MessageBox MB_OK "Actual はバナナではありません。"
SectionEnd
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/a77834fc-2955-20d4-c7aa-d60b706680b3.png)
変数が文字列型のみなので当然ですが、`StrCmp`(String Compare)関数を使って文字列を比較します。
[Reference/StrCmp - NSIS](https://nsis.sourceforge.io/Reference/StrCmp)
第1引数と第2引数が一致すれば、第3引数へジャンプ、一致しなければ第4引数へジャンプします。
ここではラベル(`Banana:`,`NotBanana:`)を使用しましたが、上記リファレンスでは移動する行数を指定しています。
コードを修正して行数が変わったりした時に思わぬ動作をしたり、考えると気が狂いそうです。
ラベルを使うとしても、同じブロック内で同名のラベルは使用できません。
それに、多くのプログラミング言語でGoToのようなステートメントは好ましくないとされています。
正直、あまりこの書き方はしたくありません。
##LogicLib
LogicLibを使うと、一般的なプログラミング言語に近い形で条件分岐を記述できます。
[LogicLib - NSIS](https://nsis.sourceforge.io/LogicLib)
Branch_001は以下のように置き換えることができます。

```nsis:Branch_002
# LogicLib を読み込む
!include LogicLib.nsh
Var Actual
!define Expected "バナナ"

Section
  StrCpy $Actual "バナナ"

  ${If} $Actual == "${Expected}"
    MessageBox MB_OK "Actual はバナナです"
  ${Else}
    MessageBox MB_OK "Actual はバナナではありません。"
  ${EndIf}
SectionEnd
```
#半角スペースの扱い
半角スペースを含む文字列の扱いには注意が必要です。
以下はコンパイルエラーになります。

```nsis:Space_001
!define Fruits "バナナ りんご いちご"

Section
  MessageBox MB_OK ${Fruits}
SectionEnd
```
何故かと言えば、以下のように展開されるからです。

```nsis
  MessageBox MB_OK バナナ りんご いちご
```
MessageBox関数に対して`バナナ`が第2引数、`りんご`が第3引数、`いちご`が第4引数となってしまうためです。
この場合、以下のように記述すれば問題ありません。

```nsis
  MessageBox MB_OK "${Fruits}"
```
`"`も扱いたい時はどうでしょう？

```nsis:Space_002
!define Fruits "バナナ りんご いちご"
Var Vegetable

Section
  MessageBox MB_OK '"${Fruits}" は果物です。'
  StrCpy $Vegetable "キャベツ トマト なす"
  MessageBox MB_OK '"$Vegetable" は野菜です。'
SectionEnd
```
`'`(シングルクオート)を使えばOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/805cd4d8-ef02-3800-02de-802035742134.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/5ddf134d-4db2-e470-84b8-f04588ef1a35.png)
NSISに限りませんが、ファイル名があって、その後にコマンドライン引数等が続くような文字列では特に注意が必要です。
意図的に親ディレクトリやファイル名に半角スペースのある名前を用意してテストをするとバグに気付きやすいです。
#関数(Function)とマクロ(!macro)
マクロは!defineと同じく、コンパイル前に展開されます。
同じようなステートメントを繰り返したりする場合に便利です。

```nsis:Macro_001
!macro Mac Arg1 Arg2 Arg3 Operate
  ${Operate} "${Arg1} は ${Arg2} に勝つ。"
  ${Operate} "${Arg2} は ${Arg3} に勝つ。"
  ${Operate} "${Arg3} は ${Arg1} に勝つ。"
!macroend

Section
  !insertmacro Mac "グー" "チョキ" "パー" "MessageBox MB_OK"
  !insertmacro Mac "ヘビ" "カエル" "なめくじ" "DetailPrint"
SectionEnd
```

`Mac`がマクロ名、`Arg1`, `Arg2`, `Arg3`, `Operate`がそれぞれ引数です。
これは、以下のように展開されます。

```nsis:Macro_001
Section
  MessageBox MB_OK "グー は チョキ に勝つ。"
  MessageBox MB_OK "チョキ は パー に勝つ。"
  MessageBox MB_OK "パー は グー に勝つ。"
  DetailPrint "ヘビ は カエル に勝つ。"
  DetailPrint "カエル は なめくじ に勝つ。"
  DetailPrint "なめくじ は ヘビ に勝つ。"
SectionEnd
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/58e5b390-6323-c227-73b6-4a4fea832835.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/594f5821-02b2-3874-4931-2337152e22da.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/b2025884-905b-3925-14d6-e064cc7d99e5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149140/a4c48e2f-b449-b91e-3b5c-039e973d3e71.png)
一方で関数(Function)は通常のプログラミング言語等の関数と異なり、引数を渡せないため使い勝手はあまり良くありません。
マクロで代用できる場合もありますが、マクロは前述の通りスタンプのように展開されるので、スタック(メモリ領域)を消費する事になります。
実際展開後のコードは目に見えず、メモリ消費も致命的になる可能性は少ないですが、スクリプトがどういう意図で設計されているかを明確にするためにも、意識して区別すると良いでしょう。
例えば初期化の一連処理のような、手続き的な処理はFunctionを使用した方が良いでしょう。

```nsis:Function
Var Object1
Var Object2
Var Object3
Var Object4

Function Initialize
  StrCpy $Object1 ""
  StrCpy $Object2 ""
  StrCpy $Object3 ""
  StrCpy $Object4 ""
FunctionEnd

Section .onInit
  Call Initialize
  ...
SectionEnd

Section
  Call Initialize
  ...
SectionEnd
```
[Macro vs Function - NSIS](https://nsis.sourceforge.io/Macro_vs_Function)
#その他
他にも[PushやらPopやら](https://nsis.sourceforge.io/Pop,_Push,_Exch..._The_Stack)サンプルによく現れますが、必要だと思った時に使ってみればいいと思います。
あまりガッツリ習得しなくてもさらっとインストーラが作れるのがNSISを使う理由の一つだと思います。
それにしても注意していると色々なところでNSISが使われているのに気付くことが多いです。
最近はむしろInstallShieldなんて久しく見ていない気がします。
オープンソースならインストーラ用の.nsiファイルも公開しているので、見てみると参考になると思います。