[index > 外部関数を作る]

外部関数を作る・1 - 基本的な構成のものを作る

最低限のBASICとアセンブラとC言語(BAStoCに対応する場合のみ)の知識があればOKです。

ここでのサンプルはmove16.fnc v0.00です。 文中のソースコードはこれのソースファイルからの引用です。

各項目/要素の詳しい説明はしていません。 必要に応じてX-BASIC/ぺけBASIC用外部関数の資料を参照してください。


仕様を考える

ここでは68040で新設されたMOVE16命令を有効活用するための関数を作ることにします。

ただ、外部関数自体が68040以降専用では例として不適当ですから、 68030以前の場合はほかの命令で同等の処理をしてやることにします。

ということで、こんな感じの関数を作ろうと思います。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int move16(dst;int, src;int, length;int)
引数 :
        dst    : コピー先の先頭アドレス(16バイト単位)
        src    : コピー元の先頭アドレス(16バイト単位)
        length : コピーする長さ(16バイト単位)
戻り値 : dst

 srcが指すアドレスからlengthバイトをdstが指すアドレスからの領域にコピーします。
src, dst, lengthはいずれも16バイト単位に切り下げられます。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

[戻る]


関数定義を書く

外部関数は、BASICが関数名や引数、戻り値の型などを知ることができるようにするために、 いくつかの情報を含んでいます。これを用意します。

まず、ファイルの先頭にインフォメーションテーブルと呼ばれる領域を用意します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  3 :			.cpu	68040
  4 :	
  5 :			include	FDEF.H		* XC の INCLUDE ディレクトリ
  6 :	
  7 :			include	iocscall.mac	* LIBC の include ディレクトリ
  8 :						* XC のものを使う場合はソース中の IOCS __なんとか を
  9 :						* IOCS _なんとか に書き換えてください
 10 :	
 11 :	MPUTYP:		.equ	$cbc		* MPU 種別 (1b)
 12 :	
 13 :	
 14 :			.text
 15 :	
 16 :	* インフォメーションテーブル
 17 :			.dc.l	init		* BASIC 起動時/! 命令からの復帰時
 18 :			.dc.l	run		* run 命令実行時
 19 :			.dc.l	end		* end 命令/exit(short) 実行時
 20 :			.dc.l	sys		* BASIC 終了時
 21 :			.dc.l	break		* BREAK キー押下時
 22 :			.dc.l	ctrl_d		* CTRL+D キー押下時
 23 :			.dc.l	res1		* 未定義1(rts を指す)
 24 :			.dc.l	res2		* 未定義2(rts を指す)
 25 :			.dc.l	token		* トークンテーブル
 26 :			.dc.l	pat		* パラメータアドレステーブル
 27 :			.dc.l	eat		* 実行アドレステーブル
 28 :			.dcb.l	5,0		* 未定義3
 29 :	
 30 :	* 一応外部関数名を入れておくとよい(特に必要ありませんが)
 31 :			.dc.l	'mv16'		* 外部関数名
 32 :			.dc.l	'0.00'		* バージョンナンバ
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 47 :	* 各イベント発生時に呼ばれるルーチン
 48 :	init:		* BASIC 起動時/! 命令からの復帰時
 49 :	run:		* run 命令実行時
 50 :	end:		* end 命令/exit(short) 実行時
 51 :	sys:		* BASIC 終了時
 52 :	break:		* BREAK キー押下時
 53 :	ctrl_d:		* CTRL+D キー押下時
 54 :	res1:		* 未定義1(rts を指す)
 55 :	res2:		* 未定義2(rts を指す)
 56 :			rts
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

FDEF.HはXC(C Compiler PRO-68k)付属の外部関数用定数定義ファイルで、 MPUTYPはIOCSワークに格納されているMPU種別のアドレスです。

initからres2まではすべてrts命令を指すようにします。 initからctrl_dは高度な外部関数を作るときに使うものなので、ここでは無視して構いません。

最後の関数名はあってもなくてもいいのですが、とりあえずつけています。

そして、token, pat, eatに関数定義を用意します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 35 :	pat:
 36 :	* パラメータアドレステーブル
 37 :	* トークンテーブルと同じ順に並べること
 38 :			.dc.l	move16
 39 :	
 40 :	
 41 :	eat:
 42 :	* 実行アドレステーブル
 43 :	* トークンテーブルと同じ順に並べること
 44 :			.dc.l	_move16
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136 :	* パラメータIDテーブル
137 :	* 1つ目の引数から順に並べ、最後に戻り値の型を書く
138 :	move16:
139 :			.dc.w	int_val		* 第1引数(コピー先アドレス)の型(int型)
140 :			.dc.w	int_val		* 第2引数(コピー元アドレス)の型(int型)
141 :			.dc.w	int_val		* 第3引数(コピーするバイト数)の型(int型)
142 :			.dc.w	int_ret		* 戻り値の型(int型)
143 :	
144 :	token:
145 :	* トークンテーブル
146 :			.dc.b	'move16',0
147 :			.dc.b	0	* 終端コード
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

ここで、tokenとpatとeatに書く関数定義の順番が一致していないといけません。 この外部関数は関数がひとつしかないので、そのまま書くだけです。

patのリストには、実際に引数と戻り値の型を定義しているリストのアドレスを定義します。 その型定義のリストは、先頭から第1引数、第2引数、…、戻り値の型の順に並んでいます。 引数がない関数の場合は、戻り値の型だけ定義します。 戻り値を持たない関数の場合はvoid_retを最後に定義します。

と、このように多少ややこしいですが、これによって、 実行ファイルに関数定義を含ませることができ、かつ、 ひとつの外部関数に複数の関数を格納できるという仕組みです。

[戻る]


関数本体を書く

まず、引数を読み込みます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 58 :	_move16:
 59 :	* move16関数
 60 :	* int move16( dst;int, src;int, length;int )
 61 :	*	dst	転送先先頭アドレス
 62 :	*	src	転送元先頭アドレス
 63 :	*	length	転送する長さ
 64 :	*
 65 :	*	戻り値	dst
 66 :	* dst, src, length とも、16 で割った余りは無視されます。
 67 :		* 引数を読み込む
 68 :			move.l	par1+6(sp),d0
 69 :			andi.b	#$f0,d0
 70 :			movea.l	d0,a0		* a0 dst
 71 :			move.l	par2+6(sp),d0
 72 :			andi.b	#$f0,d0
 73 :			movea.l	d0,a2		* a2 src
 74 :			move.l	par3+6(sp),d1
 75 :			lsr.l	#4,d1
 76 :			subq.l	#1,d1		* d1 length/16-1
 77 :			bcs	move16_quit
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

引数はすべてint型なので、par[1-3]+6(sp)でアクセスできます。 初めの2つの引数は読み込んだ引数の下位4ビットをクリアして、16で割った余りを切り捨てます。 3つ目の引数はループカウンタとして使うので、4ビット右シフトして、 DBRAループ用にさらに1を引きます。 lengthに0〜15を指定していた場合は処理してはいけないので、 ここでmove16_quit(終了処理)にジャンプします。

引数を読み込んだので、本処理に移ります。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 79 :			move	d1,d2		* d2.w dbra ループ回数(下位ワード)
 80 :			swap	d1		* d1.w dbra ループ回数(上位ワード)
 81 :		* スーパーバイザモードへ移行する
 82 :			suba.l	a1,a1
 83 :			IOCS	__B_SUPER	* d0 ssp
 84 :		* MPU チェック
 85 :			cmpi.b	#4,MPUTYP.w
 86 :			bcs	move16_000to030
 87 :	
 88 :	move16_040to060:
 89 :	@@:		move16	(a2)+,(a0)+
 90 :			dbra	d2,@b
 91 :			dbra	d1,@b
 92 :			bra	move16_touser
 93 :	
 94 :	move16_000to030:
 95 :	@@:		move.l	(a2)+,(a0)+
 96 :			move.l	(a2)+,(a0)+
 97 :			move.l	(a2)+,(a0)+
 98 :			move.l	(a2)+,(a0)+
 99 :			dbra	d2,@b
100 :			dbra	d1,@b
101 :	
102 :	move16_touser:
103 :		* ユーザーモードへ移行する
104 :			tst.l	d0		* 関数実行前にすでにスーパーバイザモードだった場合に
105 :			bmi	@f		* スキップする細工(外部関数では必要ありませんが…)
106 :			movea.l	d0,a1
107 :			IOCS	__B_SUPER
108 :	@@:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

ここは別に外部関数だからどうということはないので、ただ書くだけです。 まずスーパーバイザモードに移行し、 MPU種別を調べて68030以前だったらmove.l×4の方のルーチンを使い、 その後ユーザーモードに戻しています。

処理が終わったら戻り値を設定して、関数を終了します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
110 :	move16_quit:
111 :		* 終わる
112 :			lea	return_area(opc),a0	* a0 戻り値領域
113 :			move.l	par1+6(sp),6(a0)	* 戻り値を格納する
114 :			moveq	#0,d0		* d0 が 0 以外だと外部関数エラーになる
115 :			rts
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131 :	* 戻り値領域
132 :	return_area:
133 :			.dc.w	0		* 0 固定
134 :			.dc.l	0,0		* 戻り値を下位に詰めて代入する
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

…見たまんまです。move16関数は常に正常終了して、dstを返すだけなのでこれだけです。

完成したmove16関数は以下のようになります。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 58 :	_move16:
 59 :	* move16関数
 60 :	* int move16( dst;int, src;int, length;int )
 61 :	*	dst	転送先先頭アドレス
 62 :	*	src	転送元先頭アドレス
 63 :	*	length	転送する長さ
 64 :	*
 65 :	*	戻り値	dst
 66 :	* dst, src, length とも、16 で割った余りは無視されます。
 67 :		* 引数を読み込む
 68 :			move.l	par1+6(sp),d0
 69 :			andi.b	#$f0,d0
 70 :			movea.l	d0,a0		* a0 dst
 71 :			move.l	par2+6(sp),d0
 72 :			andi.b	#$f0,d0
 73 :			movea.l	d0,a2		* a2 src
 74 :			move.l	par3+6(sp),d1
 75 :			lsr.l	#4,d1
 76 :			subq.l	#1,d1		* d1 length/16-1
 77 :			bcs	move16_quit
 78 :	
 79 :			move	d1,d2		* d2.w dbra ループ回数(下位ワード)
 80 :			swap	d1		* d1.w dbra ループ回数(上位ワード)
 81 :		* スーパーバイザモードへ移行する
 82 :			suba.l	a1,a1
 83 :			IOCS	__B_SUPER	* d0 ssp
 84 :		* MPU チェック
 85 :			cmpi.b	#4,MPUTYP.w
 86 :			bcs	move16_000to030
 87 :	
 88 :	move16_040to060:
 89 :	@@:		move16	(a2)+,(a0)+
 90 :			dbra	d2,@b
 91 :			dbra	d1,@b
 92 :			bra	move16_touser
 93 :	
 94 :	move16_000to030:
 95 :	@@:		move.l	(a2)+,(a0)+
 96 :			move.l	(a2)+,(a0)+
 97 :			move.l	(a2)+,(a0)+
 98 :			move.l	(a2)+,(a0)+
 99 :			dbra	d2,@b
100 :			dbra	d1,@b
101 :	
102 :	move16_touser:
103 :		* ユーザーモードへ移行する
104 :			tst.l	d0		* 関数実行前にすでにスーパーバイザモードだった場合に
105 :			bmi	@f		* スキップする細工(外部関数では必要ありませんが…)
106 :			movea.l	d0,a1
107 :			IOCS	__B_SUPER
108 :	@@:
109 :	
110 :	move16_quit:
111 :		* 終わる
112 :			lea	return_area(opc),a0	* a0 戻り値領域
113 :			move.l	par1+6(sp),6(a0)	* 戻り値を格納する
114 :			moveq	#0,d0		* d0 が 0 以外だと外部関数エラーになる
115 :			rts
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131 :	* 戻り値領域
132 :	return_area:
133 :			.dc.w	0		* 0 固定
134 :			.dc.l	0,0		* 戻り値を下位に詰めて代入する
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

[戻る]


実行ファイルに対応する

外部関数はインフォメーションテーブルなどの独自の構造を持っていますが、 ファイルのフォーマット自体は.Xファイルと同じですから、 拡張子を.Xにしてしまえば無理やり実行することができてしまいます。 ファイル名を変えたぐらいで暴走してはたまりませんから、 その場合に対応するルーチンを作っておきます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
118 :	* コマンドとして起動されてしまった場合への対処
119 :	* これだけのために doscall.mac をインクルードしたくないので
120 :	* DOS コールを .dc.w 即値 で書いています
121 :	ExecAsCommand:
122 :			move.w	#2,-(sp)	* 標準エラー出力(>NUL 対策)
123 :			pea	ExecAsCommand_mes(opc)
124 :			.dc.w	$ff1e		* __FPUTS
125 :			move.w	#1,(sp)
126 :			.dc.w	$ff4c		* __EXIT2
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149 :	ExecAsCommand_mes:
150 :			.dc.b	'BASICの外部関数です。実行できません。',13,10,0
151 :	
152 :	
153 :			.end	ExecAsCommand
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

見ての通り、誤って実行されてしまった場合はエラーメッセージを表示して即終了するだけのルーチンです。

[戻る]


BAStoC環境を作る・その1

BAStoC用にmove16.defとmove16.hをまず作成します(ライブラリは後回し)。

move16関数は引数、戻り値ともすべてint型で、高度なことは特に行わないので、 move16.defとmove16.hはそれぞれ以下のようになります。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
move16.def
  1 :	I	move16(I,I,I)	:	(%,%,%)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
move16.h
  5 :	#ifndef __move16_h__
  6 :	#define __move16_h__
  7 :	
  8 :	extern int move16 (int, int, int);
  9 :	
 10 :	#endif
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

わけのわからないところはすべておまじないです。(ぉ

[戻る]


BAStoC環境を作る・その2

ライブラリを作成します。 外部関数と違ってプログラム起動時の初期化ルーチンなどを書く必要がないので、 関数そのものだけを記述します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  3 :			.cpu	68040
  4 :	
  5 :			.xdef	_move16
  6 :	
  7 :			include	iocscall.mac	* LIBC の include ディレクトリ
  8 :						* XC のものを使う場合はソース中の IOCS __なんとか を
  9 :						* IOCS _なんとか に書き換えてください
 10 :	
 11 :	MPUTYP:		.equ	$cbc		* MPU 種別 (1b)
 12 :	
 13 :	
 14 :			.text
 15 :			.even
 16 :	
 17 :	_move16::
 18 :	* move16関数
 19 :	* int move16( dst;int, src;int, length;int )
 20 :	*	dst	転送先先頭アドレス
 21 :	*	src	転送元先頭アドレス
 22 :	*	length	転送する長さ
 23 :	*
 24 :	*	戻り値	dst
 25 :	* dst, src, length とも、16 で割った余りは無視されます。
 26 :		* 引数を読み込む
 27 :			movem.l	4(sp),d0-d2
 28 :			andi.b	#$f0,d0
 29 :			movea.l	d0,a0		* a0 dst
 30 :			andi.b	#$f0,d1
 31 :			movea.l	d1,a2		* a2 src
 32 :			lsr.l	#4,d2
 33 :			subq.l	#1,d2		* d2 length/16-1
 34 :			bcs	move16_quit
 35 :	
 36 :			move.l	d2,d1		* d2.w dbra ループ回数(下位ワード)
 37 :			swap	d1		* d1.w dbra ループ回数(上位ワード)
 38 :		* スーパーバイザモードへ移行する
 39 :			suba.l	a1,a1
 40 :			IOCS	__B_SUPER	* d0 ssp
 41 :		* MPU チェック
 42 :			cmpi.b	#4,MPUTYP.w
 43 :			bcs	move16_000to030
 44 :	
 45 :	move16_040to060:
 46 :	@@:		move16	(a2)+,(a0)+
 47 :			dbra	d2,@b
 48 :			dbra	d1,@b
 49 :			bra	move16_touser
 50 :	
 51 :	move16_000to030:
 52 :	@@:		move.l	(a2)+,(a0)+
 53 :			move.l	(a2)+,(a0)+
 54 :			move.l	(a2)+,(a0)+
 55 :			move.l	(a2)+,(a0)+
 56 :			dbra	d2,@b
 57 :			dbra	d1,@b
 58 :	
 59 :	move16_touser:
 60 :		* ユーザーモードへ移行する
 61 :			tst.l	d0		* 関数実行前にすでにスーパーバイザモードだった場合に
 62 :			bmi	@f		* スキップする細工
 63 :			movea.l	d0,a1
 64 :			IOCS	__B_SUPER
 65 :	@@:
 66 :	
 67 :	move16_quit:
 68 :		* 終わる
 69 :			move.l	4(sp),d0
 70 :			rts
 71 :	
 72 :			.end
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

スタックには引数の情報などは格納されず、4バイトを単位として引数が格納されています。 また、破壊してもいいレジスタはd0-d2/a0-a2のみで(この関数ではこれらのみ使っています)、 戻り値はd0に格納します。「外部関数エラーです」のような仕掛けもありません。 あとは外部関数と同じなのがわかると思います。

[戻る]


あとがき

ということで、関数はひとつしかないし、面倒な(うっとうしい)処理もない簡単なものでしたが、 これでひととおりの環境を構築できました。

次回はC言語用のライブラリを外部関数にしながら、 インフォメーションテーブルのinitなどのもっと外部関数らしい(?)部分を作ります。

[戻る]


[index > 外部関数を作る]

Written by mor.

最終更新は2001年10月31日(水)です。