LoginSignup
1
1

More than 3 years have passed since last update.

NSISをもう少し使いこなす

Last updated at Posted at 2020-08-17

Unicode

NSISでインストーラを作ってみる その1でお茶を濁しましたが、最新のNSIS(3.06.1)ではUnicodeを使用しないと警告が表示されるようになっています。

1 warning:
  7998: ANSI targets are deprecated

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

Unicode true

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

変数

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

Field
Var SomeField

Section
  # 変数に値を代入します。
  StrCpy $SomeField "変数に代入"
  MessageBox MB_OK $SomeField
SectionEnd

StrCpyは、変数に値を代入する関数です。
Reference/StrCpy - NSIS
C言語を扱っている方にはお馴染みですが、関数名の通り、文字列をコピーする(String Copy)と理解した方が適切かもしれません。
MessageBoxはメッセージ ボックスを表示させます。
Reference/MessageBox - NSIS
image.png
$0のようなあらかじめ用意された変数を使う場面も多いと思います。詳しくは以下を読んでみて下さい。
4.2 Variables

!define

又、これまたC言語ではお馴染みですが、!defineはプリプロセッサ指令的な使い方(コンパイル前に展開される)をします。
以下の場合は、スタンプのような使い方ができます。

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

Section
  MessageBox MB_OK ${Message}
SectionEnd

※!defineで定義した値は、中かっこを付けないと展開されません。
image.png
変数ではないので、ステートメントそのものも定義できます。

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

Section
  StrCpy $SomeField "変数に代入"
  ${ShowField}
SectionEnd

又、中身(第2引数)を記述せず、スイッチのような使い方をする場合もあります。
色々な用途がありますが、詳細は下記をご覧下さい。
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
変数が文字列型のみなので当然ですが、StrCmp(String Compare)関数を使って文字列を比較します。
Reference/StrCmp - NSIS
第1引数と第2引数が一致すれば、第3引数へジャンプ、一致しなければ第4引数へジャンプします。
ここではラベル(Banana:,NotBanana:)を使用しましたが、上記リファレンスでは移動する行数を指定しています。
コードを修正して行数が変わったりした時に思わぬ動作をしたり、考えると気が狂いそうです。
ラベルを使うとしても、同じブロック内で同名のラベルは使用できません。
それに、多くのプログラミング言語でGoToのようなステートメントは好ましくないとされています。
正直、あまりこの書き方はしたくありません。

LogicLib

LogicLibを使うと、一般的なプログラミング言語に近い形で条件分岐を記述できます。
LogicLib - NSIS
Branch_001は以下のように置き換えることができます。

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

半角スペースの扱い

半角スペースを含む文字列の扱いには注意が必要です。
以下はコンパイルエラーになります。

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

Section
  MessageBox MB_OK ${Fruits}
SectionEnd

何故かと言えば、以下のように展開されるからです。

  MessageBox MB_OK バナナ りんご いちご

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

  MessageBox MB_OK "${Fruits}"

"も扱いたい時はどうでしょう?

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

Section
  MessageBox MB_OK '"${Fruits}" は果物です。'
  StrCpy $Vegetable "キャベツ トマト なす"
  MessageBox MB_OK '"$Vegetable" は野菜です。'
SectionEnd

'(シングルクオート)を使えばOKです。
image.pngimage.png
NSISに限りませんが、ファイル名があって、その後にコマンドライン引数等が続くような文字列では特に注意が必要です。
意図的に親ディレクトリやファイル名に半角スペースのある名前を用意してテストをするとバグに気付きやすいです。

関数(Function)とマクロ(!macro)

マクロは!defineと同じく、コンパイル前に展開されます。
同じようなステートメントを繰り返したりする場合に便利です。

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がそれぞれ引数です。
これは、以下のように展開されます。

Macro_001
Section
  MessageBox MB_OK "グー は チョキ に勝つ。"
  MessageBox MB_OK "チョキ は パー に勝つ。"
  MessageBox MB_OK "パー は グー に勝つ。"
  DetailPrint "ヘビ は カエル に勝つ。"
  DetailPrint "カエル は なめくじ に勝つ。"
  DetailPrint "なめくじ は ヘビ に勝つ。"
SectionEnd

image.png image.png image.png
image.png
一方で関数(Function)は通常のプログラミング言語等の関数と異なり、引数を渡せないため使い勝手はあまり良くありません。
マクロで代用できる場合もありますが、マクロは前述の通りスタンプのように展開されるので、スタック(メモリ領域)を消費する事になります。
実際展開後のコードは目に見えず、メモリ消費も致命的になる可能性は少ないですが、スクリプトがどういう意図で設計されているかを明確にするためにも、意識して区別すると良いでしょう。
例えば初期化の一連処理のような、手続き的な処理はFunctionを使用した方が良いでしょう。

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

その他

他にもPushやらPopやらサンプルによく現れますが、必要だと思った時に使ってみればいいと思います。
あまりガッツリ習得しなくてもさらっとインストーラが作れるのがNSISを使う理由の一つだと思います。
それにしても注意していると色々なところでNSISが使われているのに気付くことが多いです。
最近はむしろInstallShieldなんて久しく見ていない気がします。
オープンソースならインストーラ用の.nsiファイルも公開しているので、見てみると参考になると思います。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1