[トップ][編集][ノート][編集履歴][一覧][最近の更新][->English]

HSP2.6:module


モジュール機能ガイド

このテキストには、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                               モジュール終了

指定された区間をモジュールとして別な空間に割り当てます。 モジュール内の変数やラベルは、モジュール外のものからは独立したものになります。 "モジュール名"は、複数のモジュールを名前で区分けする時につけることのできる名前で、 モジュール名が同じもの同士は、変数名やラベル名を共有します。 モジュール名が違うものの間では、変数名やラベル名はまったく違うものとして扱われます。 "モジュール名"を省略した場合は、他とは違う名前が自動的に割り当てられます。 モジュールは、必ず「#module」で開始を指示し、「#global」で終了しなければなりません。 このようにモジュールの区間を指定することにより、 その中を他から独立した空間にすることができます。 "モジュール名"は、18文字以内の長さでなければなりません。 また、スペースや記号を含まない文字列を指定するようにしてください。 (モジュール名で使用できる文字種は、a〜zまでのアルファベット、0〜9までの数字、 「_」記号となります。変数として使用できる文字列と同等です。) #moduleおよび、#globalはプリプロセッサ命令です。「#」で始まり、1行に他の命令は記述できません。

ユーザー拡張命令詳細

#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命令がありますが、システムの処理としては

  1. HSPのプログラムを中断
  2. onexit命令の飛び先を実行
  3. モジュールのクリーンアップ先を実行
  4. HSPリソースの完全な破棄

という順番になっています。 onexit命令の飛び先では、 プログラムを中断すること自体を中止することも可能ですが、 クリーンアップ機能の場合は中断はできません。また、複数のモジュールや、 複数のクリーンアップ命令が登録された場合には、 登録された順番とは逆順に辿って次々に実行されていきます。