Home -> HSP講座 -> HPI編 No.07

ジャンプ魂

サブタイトルがあれですが、まあいいでしょう。( 怒られたらどうしよう )

今回は、プラグインでプログラムジャンプ( goto, gosub )をしてみよう! という計画です。


jump命令

この命令は gosub 命令と全く同じ動作をします。
え、意味ないんじゃね? ……そのとおりです。(おい)


処理をコーディング

一気に全部を乗せてしまいます。

{
	code_call( code_getlb() );
}

……は?
えええええええええ、終わりかよ!!

変数一つも使いませんでした。
code_getlb()関数でラベル型の引数を取り出し、その返値を直接 code_call() 関数に渡しています。
code_call()関数はgosub命令の処理をする関数とほぼ同じなので、まぁ、こんな感じになってしまうわけですよ。(何)

// jump - dllmain.cpp

#include <windows.h>
#include "hsp3plugin.h"

// 命令コマンド処理関数
static int cmdfunc( int cmd )
{
	code_next();		// 次のコードを取得(必須)
	
	switch( cmd ) {
		case 0x000:  code_call( code_getlb() ); break;
		case 0x001: code_setpc( code_getlb() ); break;
		default:
			puterror( HSPERR_UNSUPPORTED_FUNCTION );// サポート外エラー
	}
	return RUNMODE_RUN;
}

// プラグイン登録関数
EXPORT void WINAPI hsp3hpi_init( HSP3TYPEINFO* info )
{
	hsp3sdk_init( info );		// SDKの初期化
	info->cmdfunc = cmdfunc;	// 実行関数(cmdfunc)の登録
	return;
}

dllmain.cpp

dllmain.h もいりません。これをコンパイルして完成です。
で、.as ヘッダはこんな感じ。

// jump - public header

#ifndef IG_JUMP_HPI_AS
#define IG_JUMP_HPI_AS

#regcmd "hsp3hpi_init", "jump.hpi"
#cmd jump  0x000
#cmd goto2 0x001

// サンプル
#if 1

	jump *a
	goto2 *b	// *b の return に失敗する
	stop
	
*a
	mes "*a"
	jump *b
	return
	
*b
	mes "*b"
	return
	
#endif

#endif

VC++の場合は _hsp3hpi_init@4 となります。

ちなみに、code_setpc() 関数は goto 命令と同じものです。

これ以降は僕が勝手に遊んでるだけなので、無視して結構です。
次の講座へ進む。
+参照:HSP3向けプラグイン「jump」(lzh)


returnされたときの値を求める

ちょっとマニアックなことをしてみます。
上記の dllmain.cpp で、jump の部分を次のように書き換えてください。

	...
	case 0x000:
	{
		int old_retlevel = ctx->retval_level;
		ctx->retval_level = -1;
		code_call( code_getlb() );
		if ( ctx->retval_level == (ctx->sublev + 1) ) {
			MessageBox( 0, "返値つき return された", "jump.hpi", MB_OK );
		}
		ctx->retval_level = old_retlevel;
		break;
	}
	...

これをコンパイルして、次のスクリプトを実行してみましょう。

// jump - public header

#ifndef IG_JUMP_HPI_AS
#define IG_JUMP_HPI_AS

#regcmd "hsp3hpi_init", "jump.dll"
#cmd jump  0x000
#cmd goto2 0x001

// サンプル
#if 1

	jump *a
	stop
	
*a
	mes "*a"
	jump *b
	return 1
	
*b
	mes "*b"
	return 1
	
#endif

#endif

「返値つき return された」という MessageBox が2回表示されます。
ctx->retval_level は、引数付きのreturnがされたときのサブルーチンレベル(sublev)が格納されています。
呼び出し側のレベル(ctx->sublev)より一つ上に設定されていたということは、呼び出した先のラベルが、何かの値を返したということです。
※サブルーチンレベルは、サブルーチンジャンプ(gosub)するたびに 1 増加し、returnするたびに 1 減少する値。システム変数 sublev
※元々 ctx->retval が (sublev + 1) だった場合、呼び出し先が値を返さなくても返値があったと誤解してしまうので、一旦 -1 に変えておきます。どうせ ctx->retval_level なんてHSP側は使っていません。

そして、肝心の「何を返したか」ですが……。
exinfo->mpval (PVal**) が指す先の PVal* に、値が入っている模様です。
ドキュメントに書いていないので、いつ仕様が変更されるかわかりませんがね。^^;

	...
	case 0x000:
	{
		unsigned short* label;
		PVal* pval = NULL;
		APTR aptr;
		int old_retval_level;
		
		label = code_getlb();
		if ( (*exinfo->npexflg & EXFLG_1) == false ) {
			// まだ引数があるなら
			aptr = code_getva( &pval );	// 返値を受け取る変数
		}
		
		old_retlev        = ctx->retval_level;
		ctx->retval_level = -1;
		
		code_call( label );
		
		if ( pval != NULL ) {
			if ( ctx->retval_level == (ctx->sublev + 1) ) {
				// 返値あり
				PVal*       pvResult = *exinfo->mpval;
				HspVarProc* vp       = exinfo->HspFunc_getproc( pvResult->flag );
				
				code_setva( pval, aptr, pvResult->flag, phvp->GetPtr(pretval) );
				ctx->stat = 1;	// 真
			} else {
				ctx->stat = 0;	// 偽
			}
		}
		break;
	}
	...

「jump *label, 変数」で、返値を変数に格納できます。
通常、returnを使った場合、システム変数 stat, refstr, refdval に分散してしまい、受け取りにくいのですが、これなら一括して変数に代入されるので、問題ありません。
以下、解説です。

まず、ラベルを取り出します。
ラベルは unsigned short* 型で取り扱っているので、それに従います。

( (*exinfo->npexflg & EXFLG_1) == false ) という条件は、次が引数かどうかを判断します。
これが真なら、次は引数です。偽なら文頭です。
つまり、第二パラメータの変数を省略して、gosubと全く同じように使用することも可能ということです。

それで、変数がある場合、それを code_getva() で取り出します。返値の aptr は後で使うので保存します。

code_call( label ); でサブルーチンジャンプ。

ジャンプから帰ってきて、返値つき return がされている場合は、
pvResult に返値の PVal へのポインタを格納し、それの HspVarProc* (vp) も取得しておきます。
code_setva() 関数は、変数に代入するための関数で:

	code_setva( 代入先PVal*, ←のAPTR, 代入する値の型, 代入する値の実体ポインタ );

という形式です。

変数が指定されている場合 ( pval が NULL ではない場合 )、返値があれば stat に真を、なければ偽を格納します。
ちなみに、ほとんどのシステム変数はHSPCTX構造体( へのポインタ ctx )でアクセス可能です。


関数形式サブルーチン呼び出し

↑で、サブルーチンの返値を得る方法が分かったので、これを使い、関数形式サブルーチン呼び出しをしてみたいなぁ……と。

命令形式・関数形式両方で使用する関数、jump_core()を作ります。
とりあえず:

	bool jump_core( unsigned short* lb, PVal* pval );

とします。
引数にジャンプ先のラベルと、返値を格納するための変数のポインタです。
返値があった場合は、引数 pval に代入し、trueを返します。

// jump - command

#include "cmd.h"

extern bool jump_core( label_t lb, PVal* pval);

// jump命令のコア
static bool jump_core( label_t lb, PVal* pval)
{
	int old_retlev;
	
	old_retlev = ctx->retval_level;	// retval_level を記憶しておく
	ctx->retval_level = -1;
	
	code_call( lb );
	
	 // 返値あり
	if (ctx->retval_level == (ctx->sublev + 1) ) {
		PVal* pvResult = *(exinfo->mpval);
		
		if ( pval != NULL ) {
			HspVarProc* vp = exinfo->HspFunc_getproc( pvResult->flag );
			code_setva( pval, pval->offset, pvResult->flag, vp->GetPtr( pvResult ) );
		}
		
		return true;
	}
	
	return false;	// 返値なし
}

/* 続く */

cmd.cpp の前の方

さっきとほぼ同じです。
label_t 型は、cmd.h で次のように定義しておいてください。

typedef unsigned short* label_t;

code_getlb()関数の返値と同じ型で、ラベルを表します。

次に、命令形式の jump_st() 関数です。

/* 続き */

//------------------------------------------------
// jump (命令形式)
//------------------------------------------------
void jump_st(void)
{
	label_t lb;
	PVal* pval = NULL;
	APTR aptr;
	
	lb = code_getlb();
	if ( (*exinfo->npexflg & EXFLG_1) == false ) {	// まだ引数があるなら
		aptr         = code_getva( &pval );
		pval->offset = aptr;
	}
	
	// gosub 
	if ( pval == NULL ) {
		code_call( lb );
		
	// 返値つき gosub
	} else {
		ctx->stat = ( jump_core( lb, pval ) ? 1 : 0 );
	}
	return;
}

/* 続く */

さっきとほぼ同じですね。
jump_core() の中の code_setva() で、aptr は pval->offset の値を参照されるので、一応返値の aptr をセットしています。
変数が指定されない場合は、ただの gosub です。

最後に、関数形式の jump_f() です。

/* 続き */

//------------------------------------------------
// jump (関数形式)
//------------------------------------------------
int jump_f( void** ppResult )
{
	static PVal* stt_retvar = NULL;
	label_t lb;
	
	// 初回呼び出しの場合 : 返値を保存する変数を確保する
	if ( stt_retvar == NULL ) {
		stt_retvar         = (PVal*)hspmalloc( sizeof(PVal) );
		stt_retvar->flag   = HSPVAR_FLAG_INT;
		stt_retvar->mode   = HSPVAR_MODE_NONE;
		stt_retvar->offset = 0;
		
		HspVarProc* vp;
		vp = exinfo->HspFunc_getproc(HSPVAR_FLAG_INT);
		vp->Alloc( stt_retvar, NULL );
	}
	
	// サブルーチン呼び出し
	lb = code_getlb();
	
	// 返値あり
	if ( jump_core( lb, stt_retvar ) ) {
		HspVarProc* vp;
		
		vp        = exinfo->HspFunc_getproc( stt_retvar->flag );
		*ppResult = vp->GetPtr( stt_retvar );
		
	} else {
		puterror( HSPERR_NORETVAL );
	}
	
	return stt_retvar->flag;
}

PVal構造体を自力で作成しています。この方法を使えば、簡単に変数の値を保存することができます。
とりあえず flag に型タイプ値を、mode に HSPVAR_MODE_NONE を設定して、それの HspVarProc の Alloc 関数ポインタを呼び出せばいいんです。

最後に、サンプルです。( ヘッダファイルはさっきと同じ )

// サンプル・スクリプト
#if 1

	mes "サンプル開始"
	
	lb_a = *a
	lb_b = *b
	
	jump *a
	jump *a, result
	mes     result + "    " + refstr
	mes jump(lb_a) + "    " + refstr
	mes jump(lb_b)
	mes "π ≒ "+ jump(lb_b)
	
	mes "サンプル終了"
	stop
	
*a
	mes "*a"
	return "str : *a's result string."
	
*b
	mes "*b"
	return 3.141592		// 実数もOK

#endif

関数のパラメータにラベルを直接書くと、プリプロセッサに弾かれてしまうので ( バグ )、一旦変数に代入する必要があります。
※ HSP3.2 以降では、このバグは修正されています。

どうでしょう?
完璧ですね!!


おわりに

今回はちょっと遊んでみました。俗にいう黒魔術というやつです。
最後のサンプルは斬新でしたね!

では、また次回。

+参照:HSP3向けプラグイン「jump」(lzh)


by 上大

第六章へ   第八章へ