HSP : Hot Soup Processor ver2.61 / onion software 1997-2004(c)
モジュール機能ガイド
このテキストには、Hot Soup Processor ver2.5からサポートされている
モジュール機能についての説明が含まれています。
モジュール機能は、HSPをより深く高度に使いたいという方のための
拡張機能です。しかし、この機能はすべての人に必要なものではありません。
初心者の方や、これからHSPを使う方は、まだモジュールについての習得は
しなくても大丈夫です。
・HSPモジュール機能について
HSP ver2.5β5よりHSPモジュール機能が追加されています。これは、複数の
スクリプトをラベル名や変数名の衝突を気にせず結合するためのものです。
この機能は、いままでのHSPではボトルネックになっていた問題、大きなサイズ
のスクリプトを作る時に変数名などの管理がしにくくなるという点、他の人が
作成したスクリプトの再利用が難しかったという点を改善し、より汎用性の
高いスクリプトを組むことができるようになります。
しかし、この機能はすべての人に必要なものではありません。
HSPを難解で扱いにくいシステムにするつもりはありません。
いままでのHSPスクリプトの概念や互換性は維持されているので、初心者の方は、
今まで通りにスクリプトを書いて差し支えありません。
ある程度HSPを習得した中上級者には、モジュール機能は便利なものになる
はずです。また、モジュール機能を使わない人であっても、他の人がモジ
ュール機能を使って追加した新しい命令を使うことが可能です。これは、
DLLによる拡張プラグインの仕組みと変わりません。
まず、モジュールについて説明してみましょう。
たとえば、「test1.as」というソーススクリプトがあったとしましょう。
このソーススクリプトには、変数aと変数bを使っているとします。
別な人が、「test2.as」というソーススクリプトを作ったとして、そこに
とても便利なサブルーチンがあったとしたら、どうなるでしょう。
「test2.as」で変数aと変数bという名前を使っていなければ問題なく、
そのままサブルーチンだけを持ってくることができるかもしれません。
しかし、もし「test2.as」でも変数aと変数bを別な用途で使っていたとしたら
とてもやっかいです。
HSPモジュール機能を使うと、「test1.as」から「test2.as」のスクリプトを
呼び出すことが可能になりますが、「test1.as」と「test2.as」で使われて
いる変数は(たとえ名前が同じであっても)独立したものとして扱われます。
また、この独立したスクリプト内のサブルーチンを、新規命令として登録
することができ、パラメータを渡したり、受け取ったりすることが可能です。
過去に作ったモジュールを再利用したり、人に使ってもらうために公開したり、
誰か他の人が作ったモジュールを使うなど、HSPスクリプトをより広く応用
することが可能になります。
HSPモジュールを使いこなすためには、ユーザー拡張命令、モジュールイン
ポート命令、mref命令などを使う必要があります。これらは、単体でも便利な
機能を提供する命令です。一度に覚えようとしないで、わかるところから1つ
1つマスターしていきましょう。
・ユーザー定義命令について
ユーザー定義命令は、HSPモジュール機能とともに追加された命令の1つで、
新しい名前の命令をユーザーが任意に追加できるというものです。
これは、HSPモジュール機能とは別に単体で使っても非常に強力なものと
なるでしょう。
ユーザー定義命令は、以下のように使います。
例:
goto *main
#deffunc routine
mes "sub-routine"
return
*main
routine
stop
HSPの命令には「routine」はありませんから、いままでならエラーになって
しまうところですが、実際にこのスクリプトを実行すると、「sub-routine」
という表示がされて、「routine」という命令が実行されます。
ユーザー定義命令は、「#deffunc」という命令によって定義できます。
#deffunc 命令の名前
で、新しい名前の命令が追加されます。
これ以降、新しい命令が出てきた場合には、「#deffunc」のある場所に
サブルーチンジャンプします。
つまり、
例:
goto *main
*routine
mes "sub-routine"
return
*main
gosub *routine
のようなスクリプトでも、
例:
goto *main
#deffunc routine
mes "sub-routine"
return
*main
routine
でも同じということです。
ただし、「#deffunc」には1つだけ注意点があります。
「#deffunc」は実際に命令を使う位置よりも前に置くこと。
これはたとえば、gosub命令の場合は呼び出すサブルーチン(ラベル)が
gosub命令よりも前にあっても、後にあっても問題はありませんでした。
「#deffunc」では追加した命令が使えるようになるのは、「#deffunc」
の定義位置から先になります。
これは、スクリプトのコンパイル時に新規キーワードが変数か命令かを
決定するために、あらかじめ定義されていることが必要なためです。
「#deffunc」を使って新しい命令を作る場合は、サブルーチンを先に、
メインのスクリプトはその後にするような構成を取るように心がけて
みてください。
またユーザー定義命令は、サブルーチンにパラメータを渡すことを
可能にしています。
例:
goto *main
#deffunc routine
mref prm1,0
mref prm2,1
mes "パラメータ1は、"+prm1+"です。"
mes "パラメータ2は、"+prm2+"です。"
return
*main
routine 10,20
いままでのgosub命令では、値をサブルーチンに渡す時には、あらかじめ
決められた変数に値を入れて、呼び出すしかありませんでした。
ユーザー定義命令では、それに代わってスマートな方法で値を渡すことを
可能にしています。
また、渡すパラメータは数値だけでなく、文字列、変数(配列)など
いくつものバリエーションがあります。また、サブルーチンから戻る際に
システム変数statに値を代入したり、パラメータとして指定した変数に
値を書き戻すことも可能です。
これらの機能を提供するのが、mref命令です。
mref命令は、
mref 変数名 , 割り当てられるリソース
のように記述することで、各種メモリ要素を変数に割り当てることができ
ます。この命令により、ユーザー定義命令の呼び出しで指定された
パラメータを変数に取得することができます。
詳しくは、「mref命令詳細」を参照してください。
・モジュールの使い方(基本)
モジュールは、変数名やラベル名が独立して扱えるソースの単位を指します。
推奨される最も簡単なモジュールの使い方は、モジュール部分を1つの
ソーススクリプトファイル(asファイル)にしておき、別なファイルから
モジュールとして呼び出すことです。
たとえば、test1.asというファイルにモジュールとなるソースを書きます。
例:test1.asの内容
#module
#deffunc test1
mes "test1.asで表示しています。"
return
#global
モジュールは、必ず「#module」で始まり「#global」を最後に書くのが
お約束だと思って下さい。
このように記述すると、モジュール内に「test1」という命令で呼び出す
ことのできるサブルーチンができあがります。
次に、これを呼び出すためのソースを「test2.as」というファイルに
書いてみましょう。
例:test2.asの内容
#include "test1.as"
mes "test2.asです。"
test1
stop
「#include」命令でモジュールを登録するためのソース「test1.as」を
読み込んでいます。
この後、「test1」という命令が書かれた行が出てきます。
これはユーザー定義命令なので(詳しくは「ユーザー定義命令について」
を参照)、「test1.as」のサブルーチンが呼び出されます。
実際にこのスクリプトを実行すると、
test2.asです。
test1.asで表示しています。
という2つの行が現われ、test2.asからtest1.asのモジュールが呼び出された
ことがわかります。
この基本的な例では、メッセージを表示しているだけなので、gosub命令による
サブルーチン呼び出しと、あまり変わっておらず、モジュールとしての威力は
発揮されていません。
では、次の例ではどうでしょうか。
例:test1.asの内容
#module
#deffunc test1
a=456
mes "test1では、"
mes "A="+a+"です。"
return
#global
例:test2.asの内容
#include "test1.as"
a=123
test1
mes "test2では、"
mes "A="+a+"です。"
stop
これで「test2.as」を実行してみると、
test1では、
A=456です。
test2では、
A=123です。
となります。普通ならば、先に「test1.as」側で変数aに456が代入されて
いるので、「test2.as」でも同じ値になるところですが、モジュールの
中と外では名前が独立しているので、同じ変数aでも、「test1.as」と、
「test2.as」では別々の内容を保持しています。
このように、変数やラベルの名前が重複していても、まったく問題なく
それぞれのスクリプトが動作するという点が、モジュールの基本的な概念
です。
しかし、変数が独立していると困る部分もあります。モジュールを呼び出して
何らかの結果を得たい場合や、モジュールに値を渡したいこともあります。
実は、このための機能がユーザー定義命令に用意されていて、別なモジ
ュールとのパラメータのやり取りに使うことができます。
例:test1.asの内容
#module
#deffunc test1
mref prm1,0
mref res,64
mes "パラメータは、"+prm1+"です。"
res=1
return
#global
例:test2.asの内容
#include "test1.as"
a=543
test1 a
mes "STATは、"+stat+"です。"
stop
上の例では、ユーザー定義命令test1の呼び出しで、「test1 a」という
パラメータ付きの記述で、変数aの内容、543という値を渡しています。
「test1.as」では、この渡された値を、mref命令によって取り出し、
さらに、システム変数statに値1を代入して終了します。
重要なことは、「test2.as」では「test1.as」で使われている変数名
などは気にする必要がないことです。
同様に、「test1.as」からも呼び出す側との変数名が重なることを
気にする必要がありません。
独立した機能を持ったサブルーチンを、完全に分離してしまうことが
HSPモジュール機能なのです。
以上が基本的なモジュールの使い方です。
さらなる応用については、次の項を見てください。
・モジュールの使い方(応用)
基本の説明では、1つのモジュールに1つの命令だけを定義していましたが、
1つのモジュールの中に、複数のユーザー定義命令を作ることもできます。
例:test1.asの内容
#module
#deffunc test1
mes "test1で表示しています。"
return
#deffunc test2
mes "test2で表示しています。"
return
#global
上の例では、「test1」と「test2」という命令が新たに追加されます。
この場合、「test1」と「test2」の間では変数名やラベル名は同じものに
なります。
「test1」と「test2」でやはり独立した変数名やラベル名を使いたい場合は、
#module
#deffunc test1
mes "test1で表示しています。"
return
#global
#module
#deffunc test2
mes "test2で表示しています。"
return
#global
このように記述することで実現できます。
つまりモジュールとは、「#module」〜「#global」で区切られた区間と
それ以外の区間をまったく別な世界と考えるように指示を出している
ことなのです。
基本の説明では、「test1.as」と「test2.as」という2つのファイルに
モジュールと、そうでない部分(グローバル領域)を分けて説明して
いました。実際のところ「test1.as」をインクルードすることなく
1つのファイル内でもモジュールをどんどん作っていくことも可能ですが、
なるべく1つのファイルには1つのモジュールのみにして、1つのソース
スクリプトでは変数名とラベル名を統一した方がいいでしょう。
特にラベル名は、1つのソーススクリプト内に同じラベルがいくつも
あると混乱を招きます。
モジュールは、C言語やJavaなど他の言語でサポートされている、
ローカル変数に近いものを、HSPに提供しますが違う部分もあります。
HSPでは、すべての変数は静的(static)なものです。つまり一度設定
された変数は、他の値が代入されない限り、内容を失うことはありません。
これはモジュール内の変数に関しても同じことが言えます。
モジュール内のサブルーチンを呼び出して、そこで設定された内容は、
もう一度同じサブルーチンを呼び出した時にも残っています。
(他の言語のローカル変数は、呼び出すたびにその内容は不定なものに
なります)
そのため、HSPのモジュールでは再帰呼び出し(自分自身を呼び出すこと)
ができません。唯一、ローカルパラメータ(ユーザー定義命令で指定された
パラメータ)だけは呼び出しの深さごとに違う値を保持することができます
が、それ以外の変数は常に最後に設定された値が保持されます。
・モジュール定義命令詳細
#module "モジュール名" モジュール開始
#global モジュール終了
指定された区間をモジュールとして別な空間に割り当てます。
モジュール内の変数やラベルは、モジュール外のものからは独立したものに
なります。
"モジュール名"は、複数のモジュールを名前で区分けする時につけることの
できる名前で、モジュール名が同じもの同士は、変数名やラベル名を共有
します。モジュール名が違うものの間では、変数名やラベル名はまったく
違うものとして扱われます。
"モジュール名"を省略した場合は、他とは違う名前が自動的に割り当て
られます。(「M」+数字で構成されるモジュール名が生成されます。)
モジュールは、必ず「#module」で開始を指示し、「#global」で終了
しなければなりません。このようにモジュールの区間を指定することにより、
その中を他から独立した空間にすることができます。
"モジュール名"は、18文字以内の長さでなければなりません。
また、スペースや記号を含まない文字列を指定するようにしてください。
(モジュール名で使用できる文字種は、a〜zまでのアルファベット、0〜9までの数字、「_」記号
となります。変数として使用できる文字列と同等です。)
#moduleおよび、#globalはプリプロセッサ命令です。「#」で始まり、1行に
他の命令は記述できません。
( #module〜#global定義を行なうと、内部で自動的に「_」+「モジュール名」+「_exit」という名称のラベルが生成されます。
このラベルは、モジュールの最後となる場所示すために使用されます。)
・ユーザー拡張命令詳細
#deffunc p1 p2,p3… 新規命令を割り当てる
p1=命令名 : 割り当てられる命令の名前
p2〜p9 = バラメータタイプ名
ユーザーによる新規命令を指定します。
p1に新規命令の名前を、p2以降に呼び出しパラメータタイプを指定します。
#deffunc命令で定義した位置より以降は、指定された名前を命令語として
使用することが可能です。
新規命令は、#deffuncで指定された行以降が実行される内容になります。
実行はgosub命令と同じくサブルーチンジャンプとして行なわれ、
return命令でもとの実行位置に戻ります。
追加された新規命令では8つまでのパラメータを指定できるようになります。
それぞれのパラメータに指定できる型はp2〜p9で指定します。
指定するパラメータタイプには以下のものがあります。
パラメータタイプ名 内容
---------------------------------------------------
int 数値
str 255文字以下の文字列
val 変数
たとえば、
#deffunc test val,str,int
のように指定すると、以降はtestという命令が使えるようになり、
test 変数, 文字列, 数値
のようにパラメータを指定できるようになります。
ただし、バラメータタイプの指定には、いくつかの制限があります。
- strタイプで渡せる文字列は255文字以下
モジュールに文字列を渡す場合には、基本的に255文字以下
となります。それ以上の文字数を持つ文字列を渡す場合は、
変数を経由するようにしてください。
- val,strタイプは、最初の2つのみ指定可能
valおよび、strのタイプ指定は最初の2つにのみ可能です。
つまり、3番目以降のパラメータはすべてintとなります。
それぞれのパラメータは、mref命令のローカルパラメータとして取り出す
ことが可能です。mref命令については、mref命令詳細を参照してください。
パラメータタイプ名に「onexit」を指定すると、クリーンアップモジュール命令として
登録されます。詳しくは「モジュールのクリーンアップ機能について」を参照してください。
#deffuncはプリプロセッサ命令です。「#」で始まり、1行にはdeffunc
以外は記述できません。
・mref命令詳細
mref p1,p2 特殊なメモリを変数に割り当てる
p1=変数名 : 割り当てられる変数名
p2=0〜(0) : リソースID (割り当てるメモリ内容)
p1で指定された変数に、p2で指定したメモリ内容を割り当てます。
値 対応するリソース
---------------------------------------------------------
0〜 7 ローカルパラメータ#1〜8(数値)
16 ローカルパラメータ#1(数値型変数)
17 ローカルパラメータ#2(数値型変数)
24 ローカルパラメータ#1(文字列型変数)
25 ローカルパラメータ#2(文字列型変数)
32 ローカルパラメータ#1(255文字以下の文字列)
33 ローカルパラメータ#2(255文字以下の文字列)
48 ローカルパラメータ#1(数値型配列変数)
49 ローカルパラメータ#2(数値型配列変数)
56 ローカルパラメータ#1(文字列型配列変数)
57 ローカルパラメータ#2(文字列型配列変数)
64 システム変数stat
65 システム変数refstr
66 ウインドゥ内画像データ(VRAM)
67 現在のウインドゥ情報(BMSCR構造体)
96〜103 ウインドゥID0〜7の情報(BMSCR構造体)
1024 ローカルパラメータ#1情報(PVAL2構造体)
1025 ローカルパラメータ#2情報(PVAL2構造体)
mref命令は、dup命令と同様に変数名に特別な役割を持たせます。
役割の内容は、p2で指定されたメモリ内容を読み書きできるという
ことになりますが、注意しなければならないのは、p1で指定された
変数は、以降は通常の変数と思って使わないということです。
mref命令では、たとえば「mref a,64」と指定した場合に、変数aは
システム変数statと同じもの(クローン)になります。
これ以降は、変数aはシステム変数と同じものになってしまうので、
別なところで「a="abcdef"」のように、文字列を代入して別な用途に
使おうとすると予期しない問題が起こることになります。
このような問題を防ぐためにも、mrefで割り当てる変数は、mrefで
使うための専用のものを使うことが望ましいです。
ローカルパラメータは、ユーザー定義命令(#deffunc)で新規に
追加された命令が使われた際のパラメータ内容が格納されています。
パラメータのタイプ(数値、変数、文字列)に従って取得することが
可能です。
リソースIDに「ローカルパラメータ」を指定した場合は、モジュール
の呼び出し元(新規命令が実行された時)に指定されていた数値や
変数のクローンとなります。たとえば、「newfunc a」というよう
に変数aがパラメータとして受け渡された時に、モジュール側で、
「mref i,16」を指定すると変数iが変数aと同等のものになります
(dup命令と同様です)。
ローカルパラメータとして、変数をそのまま取得する場合は、
ユーザー定義命令(#deffunc)で指定されたパラメータタイプも
「変数」になっている必要があります。ローカルパラメータとして
取得した変数に値を代入すれば、もとの指定された変数の値も
変化することになります。また、ローカルパラメータに変数を
指定した場合は、指定した変数の型(文字列か数値か)に強制的に
変更されます。
リソースID48,49も、リソースID16,17と同様にローカルパラメータ
変数を取得しますが、こちらは配列の構造を含めた完全なクローン
になります。たとえば、「newfunc a.1」というように配列変数の
一部をパラメータに指定した場合、「mref i,16」では変数iは、
変数a.1と同じになり、配列変数aとは異なるものになります。
これに対して、「mref i,48」の場合は変数iが配列変数aとまったく
同等のものになります。
リソースID64,65のシステム変数は、たとえば「mref i,64」とした
場合、変数aがシステム変数statと同等になり、値を代入することが
できるようになります。
これにより、ユーザー定義命令内の計算結果などをシステム変数に
反映して、呼び出し元に返すことができます。
リソース66のウインドウ内画像データ(VRAMデータ)は、表示されて
いる画像を内容とする配列変数になります。
これにより、poke,peek命令などで画像データに直接アクセスが
可能になります。
また、67以降も同様にHSPの内部データに直接アクセスできるように
するものですが、通常は使う必要はありません。DLLへ渡すための
パラメータ準備のためなど、ごく限られた用途のために用意されて
いるもので、ほとんどの人は使うことはないはずです。
・モジュールのクリーンアップ機能について
ver2.55(ver2.6b8)から、モジュール機能にクリーンアップ機能が追加されています。
これは、HSPプログラムが終了する直前にモジュール内のスクリプトが自動的に呼び
出されるもので、モジュールによって機能を拡張した場合などにその後始末、システム
やメモリの解放などが行なえるようにしたものです。これをクリーンアップ機能と
呼んでいます。この機能を使うには、モジュール内でのユーザー定義命令宣言時に、
#deffunc 名前 onexit
のように、引数を記述する部分に「onexit」を入れておいてください。
HSPのプログラムが終了した場合に、その命令が自動的に実行されます。
ただし、この命令内では以下の点に注意してください。
- end、stop、wait、await、dialogなど時間待ちが起こる命令は使用できません
- mesなど画面に出力する命令は機能しません
つまり最低限のメモリ解放や外部DLLの呼び出しなど最終的に行なわれる作業だけを
記述するものと考えて下さい。
同様の動作をするものにonexit命令がありますが、システムの処理としては
[HSPのプログラムを中断]
↓
[onexit命令の飛び先を実行]
↓
[モジュールのクリーンアップ先を実行]
↓
[HSPリソースの完全な破棄]
という順番になっています。
onexit命令の飛び先では、プログラムを中断すること自体を中止することも
可能ですが、クリーンアップ機能の場合は中断はできません。
また、複数のモジュールや、複数のクリーンアップ命令が登録された場合には、
登録された順番とは逆順に辿って次々に実行されていきます。
・標準キーワードと「@hsp」モジュール名について(new)
ver2.61から、標準キーワードは「@hsp」というモジュール名の空間に割り当てられています。
これは、たとえばmes命令であれば、「mes@hsp」という名前が正式な名称であることを意味します。
ただし、通常のグローバルな空間でも使用できるように「@hsp」がない名称も別名として登録されているので、いままで通りの命令名でそのまま使用することができます。
たとえば、mes命令であれば「#define global mes mes@hsp」が最初から定義されているのと同じです。
(プリプロセッサ処理後に、標準キーワードは「@hsp」が付けられた正規の名称に展開されます。コンパイル時に出力される「hsptmp.i」を開いてみるとわかると思います。)
これにより、標準キーワードとして登録されている名称そのものを別名にしたり、ユーザーが定義し直すことが可能になります。
以下は、mes命令をマクロにより置き換えている例です。
#undef mes
#define mes(%1) mes@hsp "MES->"+%1
mes "メッセージです。"
stop
「mes」というキーワードを、#undef命令により取り消した後、再定義しています。
HSPで使用される標準キーワードすべては、同様に取り消し、再定義することが可能です。
以下は、mes命令をユーザー定義命令に置き換えている例です。
#module
#deffunc mes str
mref _p1,32
_y=csry
color 0,0,0
pos csrx+1,_y+1
mes@hsp _p1
color 0,192,255
pos csrx,_y
mes@hsp _p1
return
#global
mes "mes命令を影文字にしてみました。"
標準キーワードの再定義は、それ以降のキーワードすべてに影響があるため注意して使用してください。