解析プログラム
解析ルーチンの動きを追ってみます
プログラムを起動すると main.cpp の _tmain 関数が呼ばれます
最初に引数で渡されたファイルをメモリ内に格納しています
解析処理に必要な解析マネージャーとジョブマネージャーはグローバル宣言されています
サンプルではグローバル宣言されていますが実際には必要なインスタンス内に作成してください
ジョブマネージャーはジョブを複数動作させる事を想定して配列化したものです
ジョブ(スクリプト処理の単位)が1つしかない場合はジョブマネージャーを作成する必要はありません
(ジョブマネージャーはジョブ同士の情報の受け渡しも担っている)
ここで読み込んだバイナリーデータをスクリプトとして s_cAnlyz
に関連付けています
これで以降スクリプトとして解析を開始する事が出来ます
s_cAnlyz.Setup( pMem, iLength,
s_cJobMgr.GetJob( 0 )->GetPcPtr(), SCRIPT_ID );
引数解説
pMem
バイナリーデータのポインタ
iLength
バイナリーデータのサイズ。バッファオーバーしてスクリプトを解説しないようにするためのもの
s_cJobMgr.GetJob( 0 )->GetPcPtr()
解析位置を示す変数のポインタ
pMemポインタから
プラスいくつのポイントを今解析しているか。の情報を外から与えている(解析ルーチンを共通化するため)
ここではジョブマネージャーの
0番目のジョブの m_uPc のポインタを渡している
(複数のジョブを稼働させる場合は m_uPc
を2つ目以降のジョブにコピーするだけでよい)
SCRIPT_ID
スクリプトのID。mlc.ini
で定義される2文字の文字列。バイナリーデータの冒頭に格納されているので
この値が違うとスクリプトバイナリーではないと判断してエラーが返る
その後は更新処理としてずっと Update が回っているだけです。
ここでは 秒間30フレームと想定した1フレームあたりの更新時間を渡しているがサンプルでは使用していない
更新処理は以下です
やっている事は
ジョブ情報取得.
CJobInfo *pJob =
s_cJobMgr.GetJob( 0 );
レジスタ情報割り当て
s_cAnlyz.SetJobInfo( pJob
);
スクリプトコマンド実行
s_cAnlyz.Command(
pJob->GetPcPtr() );
の3つだけです。
s_cJobMgr.GetJob( 0 )
は s_cJobMgr
の中にあるジョブ情報をインデックスを指定して取得しています。
サンプルではジョブは1つしかないので引数は 0
です。
s_cAnlyz.SetJobInfo( pJob )
で取得したジョブを s_cAnlyz に渡しています。
これによってジョブが保持しているレジスタ(スクリプトが使う変数)に Analyzer
がアクセスできるようになります。
s_cAnlyz.Command( pJob->GetPcPtr() )
でコマンド実行。
pJob->GetPc()
でスクリプトの実行位置が返り、
pJob->GetPcPtr()
でスクリプトの実行位置を格納している変数のポインタが返ります。
別途ポインタで渡しているのは現在の実行位置とは別にサブルーチン処理をさせたりなど実行位置は唯一無二のものとは限らないためです。
複数ジョブやサブルーチンに縁のない人はただ面倒くさいものと思っておいてください。
とにかく実行位置は Command関数 に渡されます。
SetProcCounter( &_uPc )
で実行位置を内部の変数にセットしています。
ActOnCommand()
で実行すべきコマンドのIDを得ています。
ごちゃごちゃとややこしいように見えますがやっている事は
バイナリーデータ上の実行位置にあるデータを取り出し、実行位置を次のめいれいの頭にセット。
引数取得のためのポインタに引数部分の頭をセット(パラメータ参照ポインタセット)
。
しているだけです。
あとは終端やエラーのチェックです。
でコマンド実行です。
scr_com_function はポインタ関数の配列になっています。
前章で見た
table_vaiss.h
の個所で代入していましたね。
少々不格好な書式ではありますが、自動的出力されるので追加間違いなどは起こりません。
そのポインタ配列の添え字に pComm->m_uCode を指定する事でコマンドIDに相当する関数が呼ばれます。
this->*scr_com_function[
pComm->m_uCode ])()
ブレークを張り、IDによってそれぞれの関数が呼び分けられているのを確認してみてください。
そしてコマンドID
#define SCR_COM_PRINT 0x0011
が指定された時 scr_com_function の飛び先は scr_com_print() 関数になります。
これは「プロトタイプ宣言ソース出力」ボタンを押した時に追加される内容のままです。
INPUT コマンドを追加した時もそうでしたね。
それは
DEBUG_SCR_COM_TRACE
のデバッグマクロが「コマンドと引数の内容を標準出力する」
というものだからです。
PRINT でやりたい事と同じなので特に変えずにそのままにしました。
本当はちきんと引数から値を取得して表示させるのが正しいです(デバッグビルドでしか表示されないし)。
それをやってみたいと思います。
これで数値だけの表示に変わります。
get_register()
で引数として渡された変数の値を取得しています。
引数
複数記述する場合は,で区切り必ず終了番号を付加する(引数なしは終了のみ)
0:終了 1:アドレス 2:byte 3:word 4:dword 5:str 6:拡張情報付str 7:ダミー
8:レジスタ 9:固定小数点 10:文字配列 11:連続文字列 12:連続アドレス
13:省略可アドレス 14:省略可byte 15:省略可word 16:省略可dword 17:省略可str 18:省略可レジスタ
#define SCR_COM_PRINT 0x0011 // 0 8,0
引数指定に 8 が1つなので(0は終端)
PRINT は変数を1つ指定できるコマンドという事です。
これを
#define SCR_COM_PRINT 0x0011 // 0 4,0
とすると DWORD型の数値が1つ指定できるコマンドになります。
この状態で PRINT コマンドの引数に変数を指定するとエラーになります。
しかし
#define SCR_COM_PRINT 0x0011 // 0
8,0
の状態で
iValue =
get_register();
としている時に PRINT
コマンドの引数に定数(数字をそのまま)
を指定するのは有効です。
指定した数値がそのまま取得されます。
じゃ全部レジスタにしとけばいいんじゃないか
と言えばそうなんですが、変数引数は定数を指定していても8byte(倍)あり、取得にも負荷がかかります。
その上で問題ないのであれば値の取得には全部レジスタを使用しておくと楽になりますが、
変数を指定するはずのない、必要のないものは定数型にしておいた方が間違った時にエラーでわかるのでケースバイケースで分けてください。
以下、基本的な引数属性に対する取得関数(正しくはマクロ)です
1 | アドレス | unsigned int a = get_adrs(); |
2 | byte | char a = get_byte(); |
3 | word | short a = get_word(); |
4 | dword | int a = get_dword(); |
5 | str | char *a = get_string(); |
8 | レジスタ | int a = get_register(); |
9 | 固定小数点 | float a = get_float(); |
動作環境によってはアライメントに注意する必要があります。
章締
ここで解説したものはスクリプトシステムを取り扱うのに必要な知識ではありません。
ブラックボックスと思って特に触れないで扱うこともできるでしょう。
ですがデバッグ時に「スクリプトが正しく動作しているか」を追っていく際には非常に重要になります。
DEBUG_SCR_COM_TRACE
を全ての関数に置いておけば、標準出力から確認できますが、
出力されるものが多くなったり、複数のジョブを走らせたりと、高度な使用になっていくに従ってそのうち限界に達します。
次章ではレジスタとして確保してある領域にプログラムがどう扱っているのかを解説します。