今回はWindows APIから少々離れて、スクリプト中でHSPのモジュール機能を活用してみることにしましょう。モジュールを活用することで、より高度で汎用性のあるプログラムを組んでいくことができます。
モジュール機能は、複数のスクリプトをラベル名や変数名の衝突を気にせず結合する仕組みのことで、スクリプトを整理し、再利用しやすくすることができます。一般に、ユーザー定義命令の実装と組み合わせて使用することで、「新しい命令を定義する機能」という位置付けがなされています。
多く利用されるモジュール機能の例としては、別のプログラマが作成したモジュールを、簡単に自分のスクリプトに組み込むというものがあります。これは、別のプログラマが作成した拡張プラグイン定義命令を使用するのに似ています。モジュール利用者は、その機能を実現するための具体的な手続きやアルゴリズムがわからなくても、他のプログラマが作成したモジュールを結合するだけで、新しい命令1つで実現することができてしまうのです。そのため、この機能は非常に便利なものとなっています。
しかし、この機能をただそれだけに利用していたのでは、モジュール機能の本領を発揮できないでしょう。モジュール機能には、うまく使えばスクリプトの構造を容易にすることができる、といった側面もあるのです。どちらかというとC言語などのようなプログラミングに近いスクリプト構造を実現して、スクリプトを単純にします。
例えば、スクリプトの開始点でウィンドウの初期化を行うとしましょう。ウィンドウサイズの設定、タイトルバー文字列の変更、オブジェクトの配置、文字の描画、……などといった処理を一つ一つ順に書いていくのもいいのですが、むしろ、これらすべての作業を実行するモジュールをまず作成して、その手続き名として“InitMainWindow”という名前をつけてしまえば、あとは、スクリプトのメインルーチンにおいて“InitMainWindow”と記述するだけでウィンドウの初期化がすんでしまいますし、あとからスクリプトを見たときも内容を把握しやすくなります。
このようなことを実現する別の手法としてサブルーチンがあります。サブルーチンを使っても上で述べたようなことを同様に行うことは可能です。しかし、1つ問題が生じます。それは変数名の衝突です。例えば、メインルーチンでaという変数を使っていた場合に、サブルーチンでも同じ名前の変数aを使ってしまうと、サブルーチン内でこの変数の内容を書き換えてしまうことによって予期しない不具合が起こる可能性があるのです。モジュールを使った場合には、モジュール空間内の変数aとメインルーチンの変数aは別の変数として扱われるので、このような問題を防ぐことができます。このような意味でも、モジュールを使うことがHSPプログラミングにおいて有効なのです。
モジュールを作成する場合、2つのプリプロセッサ命令#moduleおよび#globalを用いて行います。これらのプリプロセッサ命令#moduleと#globalではさまれた部分のスクリプトはモジュール空間(モジュール領域)と呼ばれます。逆に、モジュールの外の部分をグローバル空間(グローバル領域)と呼びます。
; この場所はグローバル空間です #module ; この場所がモジュール空間になります #global ; この場所はグローバル空間です
モジュール空間には名前を付けることができます。モジュール名は、プリプロセッサ命令#moduleに続けて記述します。例えば、次のようにすると“testmod”という名前のモジュール空間を作ることができます。
; この場所はグローバル空間です #module testmod ; この場所が名前 testmod のモジュール空間になります #global ; この場所はグローバル空間です
モジュール空間の名前を指定しなかった場合には、“_m0”, “_m1”, …というように、重複しないような名前が自動的に割り当てられます。
モジュール空間は、グローバル空間や、他の場所で定義されているモジュール空間とは独立しており、この中の変数名やラベル名は他の空間と衝突することはありません。また、モジュールはメインスクリプトの実行に影響を与えません。どういうことかというと、メインスクリプトの途中にモジュールが定義されていても、モジュール空間内のスクリプトは、その時点では実行されないということです。例えば、
mes "メインスクリプト実行中1" #module ; ------ ここからモジュール空間 ------ mes "モジュール空間内スクリプト実行中" #global ; ------ ここまでモジュール空間 ------ mes "メインスクリプト実行中2" stop
というスクリプトを実行すると、ウィンドウには
メインスクリプト実行中1 メインスクリプト実行中2
と表示されるだけです。モジュール空間に記述されているスクリプトは実行されていないことがわかります。
上で述べたように、モジュール空間にスクリプトを記述するだけでは、その部分は実行されません。モジュール空間に記述されたスクリプトを実行させるには、その部分の手続きを新規命令として定義し、メインスクリプト中でその新しい命令を実行させるという手段をとります。
ユーザー定義命令を新たに定義するには、プリプロセッサ命令の#deffuncを使います。#deffunc命令を使って新しい命令を定義しておくと、それ以降のスクリプト中でその命令を使うことによってモジュール空間内のスクリプトを実行することができます。ユーザー定義命令の実行はサブルーチンジャンプと同等の扱いになるので、命令の手続きからはreturnで戻ります。
mes "メインスクリプト実行中1" #module ; ------ ここからモジュール空間 ------ #deffunc myfunc1 ; 新規命令 myfunc1 を定義 mes "モジュール空間内スクリプト実行中" return #global ; ------ ここまでモジュール空間 ------ mes "メインスクリプト実行中2" myfunc1 ; 命令を実行 stop
上のスクリプトでは新しい命令myfunc1を定義して使用しています。このスクリプトを実行すると
メインスクリプト実行中1 メインスクリプト実行中2 モジュール空間内スクリプト実行中
と表示されるので、新規命令myfunc1を実行したときにモジュール空間内に記述された手続きが実行されていることがわかります。
HSP3.2よりも前のバージョンを使用する場合、ユーザー定義命令は#deffunc命令によって手続きを記述した部分よりも後ろでしか実行することができないということに注意してください。新規命令を定義するよりも前に新しい命令を実行しようとすると、エラーになります。
なお、HSP3.2以降ではこの問題が改善されており、プログラムの後ろの方で定義されているユーザ定義命令を実行できるようになっています。
通常の命令と同じように、新しいユーザー定義命令にパラメータを付けることができます。パラメータの種類(整数か実数か文字列か、あるいは変数か)を指定するのに、#deffuncと命令の名前のあとにパラメータの種類(型指定)と変数名を並べて記述します。
#deffunc myfunc2 var p1, str p2, int p3, int p4
上のように定義すると、新しい命令myfunc2は第1パラメータとして変数を、第2パラメータとして文字列を、第3および第4パラメータとして数値を指定することができるようになります。それぞれのパラメータは、モジュール空間の命令実装部分で、変数p1, p2, p3, p4の形で参照することができます。
指定可能な型指定には以下のものがあります。
型指定 | パラメータの型 |
---|---|
int | 整数値 |
var | 変数(配列なし) |
array | 変数(配列あり) |
str | 文字列 |
double | 実数値 |
label | ラベル |
実際の例を次に示します。
#module ; 新しい命令 myfunc2 を定義 #deffunc myfunc2 var p1, str p2, int p3, int p4 mes "第1パラメータ(変 数): " + p1 mes "第2パラメータ(文字列): " + p2 mes "第3パラメータ(数 値): " + p3 mes "第4パラメータ(数 値): " + p4 p1 = -10 ; 変数の内容を書きかえる return #global a = 50 mes "a = " + a myfunc2 a, "HSPモジュールテスト", 30, 40 mes "a = " + a stop
実行すると出力は以下のようになります。
a = 50 第1パラメータ(変 数) : 50 第2パラメータ(文字列) : HSPモジュールテスト 第3パラメータ(数 数) : 30 第4パラメータ(数 数) : 40 a = -10
モジュール命令のパラメータとして変数を指定した場合、モジュール空間内でそのパラメータに対応する変数(変数p1)はもとの変数(変数a)そのものになります。モジュール空間内で変数の内容を書きかえると、もとの変数の内容も書きかえられていることがわかります。
HSP3.1では、スクリプト中で新しい関数を定義して使用することもできるようになっています。ユーザ定義関数を新たに定義するには、上記のユーザ定義命令の実装の手順の際に、#deffuncの代わりに#defcfuncを使用するようにします。関数が引数(パラメータ)を持つ場合の定義方法は、パラメータ付きの#deffunc定義の場合と同様、関数名のあとにパラメータの種類(型指定)と変数名を並べて記述します。
#defcfunc myfunc3 int p1, int p2
関数というからには、何らかの戻り値を返す必要がありますね。ユーザ定義関数から戻り値を返すには、return命令に戻り値として渡すデータを指定します。戻り値の型(整数、実数、文字列など)は、return命令に渡される値の型によって自動的に決定されます。
#module ; 新しい関数 myfunc3 を定義 #defcfunc myfunc3 int p1, int p2 return (p1 + p2) ; 2つの値の和を返す(ここでの戻り値は整数型) #global ; 定義した関数を使用して結果を取得 mes "50と25の和は" + myfunc3(50, 25) stop
50と25の和は75
変数名の独立性が売りのモジュール機能ですが、やっぱりどうしてもモジュールの外(グローバル空間)の変数にアクセスしたいときもあります。ここでは、その方法を紹介しましょう。
モジュール内部からグローバル空間にある変数のデータを読み書きするには、変数の後ろにアットマーク(“@”)を付けるだけで実現されます。次の例は、モジュール空間から、グローバル空間の変数tempにアクセスしています。
#module #deffunc myfunc1 mes "---- モジュール命令処理はじめ ----" temp = 20 ; モジュール空間の変数 temp@ = 30 ; グローバル空間の変数 mes "temp = " + temp mes "temp@ = " + temp@ mes "---- モジュール命令処理おわり ----" return #global temp = 10 mes "temp = " + temp myfunc1 mes "temp = " + temp stop
temp = 10 ---- モジュール命令処理はじめ ---- temp = 20 temp@ = 30 ---- モジュール命令処理おわり ---- temp = 30
グローバル空間あるいは他のモジュール空間から別のモジュール空間内の変数にアクセスすることも可能です。この場合は変数名の直後に“@モジュール名”をつけることで実現できます。例えば、sizex@subとすると、subという名前のモジュール空間内のsizexという変数を指します。しかしこの手法を多用することはあまりお勧めできません。なぜならプログラムの構造を複雑にしてしまう危険性があるからです。使うとしても、モジュール内の変数の値を初期化するなどの目的でアクセスする程度にとどめておいたほうがいいでしょう。
上で述べたように、変数はその変数の割り当てられているモジュール空間あるいはグローバル空間内のみで有効で、他の領域からアクセスするには特殊な指定をしなければなりませんでした。ところが、変数以外にもそのようなものがあります。HSPでは、以下のものが、空間ごとに独立した名前を持っています。
これらは、外部の空間から使用するためには、グローバル空間で定義されたものなら“@”を、モジュール空間で定義されたものなら“@モジュール名”を、それぞれの名前の直後に付けなければいけません。
したがって、グローバル空間で#defineや#constで定義されたマクロや定数名、もしくはグローバル空間で#funcで定義された外部DLL関数(例えばWindows API関数)をモジュール領域で使うには、命令名のあとにアットマーク("@")をつけるか、あるいはモジュール内で再び#defineや#constや#funcによる定義をしなければなりません。
ただし、#define, #const, #func, #cfuncにはオプションでglobal指定子を付けることができ、これを付けた場合にはグローバルな名前として、どの空間からでも同じ名前で記述することができるようになっています。
逆に、#deffunc, #defcfuncによって定義されるユーザ定義命令は、グローバルな名前として、どの空間からでも同じ名前で参照できるものになっています。これらのユーザ定義命令をモジュール空間毎に独立した名前にするには、#deffunc, #defcfunc命令にlocal指定子オプションを付ける必要があります。(local指定子オプションはHSP3.1以降で使用可能です。)