#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 変数名
のように記述します。
使う時は、$変数名
のように記述します。
Var SomeField
Section
# 変数に値を代入します。
StrCpy $SomeField "変数に代入"
MessageBox MB_OK $SomeField
SectionEnd
StrCpy
は、変数に値を代入する関数です。
Reference/StrCpy - NSIS
C言語を扱っている方にはお馴染みですが、関数名の通り、文字列をコピーする(String Copy)と理解した方が適切かもしれません。
MessageBox
はメッセージ ボックスを表示させます。
Reference/MessageBox - NSIS
$0
のようなあらかじめ用意された変数を使う場面も多いと思います。詳しくは以下を読んでみて下さい。
4.2 Variables
#!define
又、これまたC言語ではお馴染みですが、!define
はプリプロセッサ指令的な使い方(コンパイル前に展開される)をします。
以下の場合は、スタンプのような使い方ができます。
!define Message "コンパイル時に確定する文字列"
Section
MessageBox MB_OK ${Message}
SectionEnd
※!defineで定義した値は、中かっこを付けないと展開されません。
変数ではないので、ステートメントそのものも定義できます。
Var SomeField
!define ShowField "MessageBox MB_OK ${SomeField}"
Section
StrCpy $SomeField "変数に代入"
${ShowField}
SectionEnd
又、中身(第2引数)を記述せず、スイッチのような使い方をする場合もあります。
色々な用途がありますが、詳細は下記をご覧下さい。
Reference/!define - NSIS
#条件分岐
条件分岐は標準では扱いづらいものになっています。
Var Actual
!define Expected "バナナ"
Section
StrCpy $Actual "バナナ"
StrCmp $Actual "${Expected}" Banana NotBanana
Banana:
MessageBox MB_OK "Actual はバナナです"
Return
NotBanana:
MessageBox MB_OK "Actual はバナナではありません。"
SectionEnd
変数が文字列型のみなので当然ですが、StrCmp
(String Compare)関数を使って文字列を比較します。
Reference/StrCmp - NSIS
第1引数と第2引数が一致すれば、第3引数へジャンプ、一致しなければ第4引数へジャンプします。
ここではラベル(Banana:
,NotBanana:
)を使用しましたが、上記リファレンスでは移動する行数を指定しています。
コードを修正して行数が変わったりした時に思わぬ動作をしたり、考えると気が狂いそうです。
ラベルを使うとしても、同じブロック内で同名のラベルは使用できません。
それに、多くのプログラミング言語でGoToのようなステートメントは好ましくないとされています。
正直、あまりこの書き方はしたくありません。
##LogicLib
LogicLibを使うと、一般的なプログラミング言語に近い形で条件分岐を記述できます。
LogicLib - NSIS
Branch_001は以下のように置き換えることができます。
# 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
#半角スペースの扱い
半角スペースを含む文字列の扱いには注意が必要です。
以下はコンパイルエラーになります。
!define Fruits "バナナ りんご いちご"
Section
MessageBox MB_OK ${Fruits}
SectionEnd
何故かと言えば、以下のように展開されるからです。
MessageBox MB_OK バナナ りんご いちご
MessageBox関数に対してバナナ
が第2引数、りんご
が第3引数、いちご
が第4引数となってしまうためです。
この場合、以下のように記述すれば問題ありません。
MessageBox MB_OK "${Fruits}"
"
も扱いたい時はどうでしょう?
!define Fruits "バナナ りんご いちご"
Var Vegetable
Section
MessageBox MB_OK '"${Fruits}" は果物です。'
StrCpy $Vegetable "キャベツ トマト なす"
MessageBox MB_OK '"$Vegetable" は野菜です。'
SectionEnd
'
(シングルクオート)を使えばOKです。
NSISに限りませんが、ファイル名があって、その後にコマンドライン引数等が続くような文字列では特に注意が必要です。
意図的に親ディレクトリやファイル名に半角スペースのある名前を用意してテストをするとバグに気付きやすいです。
#関数(Function)とマクロ(!macro)
マクロは!defineと同じく、コンパイル前に展開されます。
同じようなステートメントを繰り返したりする場合に便利です。
!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
がそれぞれ引数です。
これは、以下のように展開されます。
Section
MessageBox MB_OK "グー は チョキ に勝つ。"
MessageBox MB_OK "チョキ は パー に勝つ。"
MessageBox MB_OK "パー は グー に勝つ。"
DetailPrint "ヘビ は カエル に勝つ。"
DetailPrint "カエル は なめくじ に勝つ。"
DetailPrint "なめくじ は ヘビ に勝つ。"
SectionEnd
一方で関数(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ファイルも公開しているので、見てみると参考になると思います。