活動日誌 8/8〜8/9



 えー、ども。最近何かにつけてやる気の無いぼしゅーでございます。いや、ほんと。だから逝けないコミケのカタログ眺めて鬱入ってるくらいしかすることがないんですぐわ。...畜生。
 あー、今日は何もしなかったですよ? 飯食って麻雀打ってはいしゅーりょー。さよーなら。アドバンスもデータ待ちなので僕はすること何も無いです。他に作りたいものも無いわけでは無いけれど前述の通り夏バテ気味。うぐぅ。まぁでも全くやってなかったワケでもないので、今日はプログラムのちょっと小話でも。

 スクリプト言語を作ろうかと思って(何回目だ? まぁ今回も思うだけ)いろいろ思案していたんですが。簡単に拡張出来る言語にしようと思ったらDLLで外部ライブラリをエクスポート出来るようにするのが手っ取り早いワケですが。でもDLLの関数を呼び出そうと思ったら、そのDLLの構造がコンパイル段階で分かってないといけない訳で。なんでかって、関数の引き数はスタック領域に逆順に詰まれて引き渡されるワケだけど、これをいくつ積めばいいかをコンパイラが把握してなければスタックが正しく解放されなくてどっかでエラるという。普通は関数プロトタイプがあったり、DLL関数をGetProcAddressAPIで取得する場合でも、プログラマがその関数に渡す引き数を把握しているから問題無いワケなんですが。でもスクリプト言語のばやい、コンパイラだの、システム作るプログラマが把握しきれないところでDLL作られたり呼び出されたりするわけで。そーゆー場合大抵、もう拡張とか無しで全て組み込み関数にしてしまうか、外部関数の構造を一意にしてしまうかのどちらかになるよーですが。それじゃどんな関数でも自由に呼び出せないジャン!とゆーことで、こんなの作ってみました。VC++6.0推奨ってことで

////////////////////////////////////////////////////////////////////////

typedef	int (__stdcall *fn_t)(int );	// 適当な関数型

typedef	enum
{
	TYPE_DWORD,	// 32bit
	TYPE_QWORD,	// 64bit
}	TYPE;

typedef	struct	_FUNC
{
	fn_t	pfn;	// 関数ポインタ
	int	nsize;	// 引き数の数
	TYPE*	ptype;	// 引き数の型のリスト
	long*	parg;	// 引き数のリスト

	_FUNC()
	{
		pfn	= NULL;
		nsize	= 0;
		ptype	= NULL;
		parg	= NULL;
	}
	_FUNC(fn_t p, int n)
	{
		init( p, n );
	}
	virtual	~_FUNC()
	{
		free( ptype );
		free( parg );
	}
	void	init(fn_t p, int n)
	{
		pfn	= p;
		nsize	= n;
		if( n > 0 ) {
			ptype	= (TYPE*)malloc( sizeof(TYPE) * n );
			parg	= (long*)malloc( sizeof(long) * n );
		} else {
			ptype	= NULL;
			parg	= NULL;
		}
	}
}	FUNC;

////////////////////////////////////////////////////////////////////////

int	fcall(FUNC* pfn)
{
	__asm
	{
		mov	edx,	pfn
		mov	ecx,	dword ptr [edx + 8]
		cmp	ecx,	0
		je	LABEL_CALL
		mov	esi,	dword ptr [edx + 12]
		mov	edi,	dword ptr [edx + 16]
		mov	eax,	4
		mul	ecx
		add	esi,	eax
		add	edi,	eax
LABEL_MAIN:
add esi, -4 add edi, -4 mov eax, dword ptr [esi] cmp eax, TYPE_DWORD jne LABEL_QWORD push dword ptr [edi] jmp LABEL_NEXT LABEL_QWORD: mov eax, dword ptr [edi] push dword ptr [eax + 4] push dword ptr [eax] LABEL_NEXT: loop LABEL_MAIN LABEL_CALL: mov edx, pfn call dword ptr [edx + 4] } } ////////////////////////////////////////////////////////////////////////

 まぁ実際これもどんな関数でも呼び出せるワケでは無いんですが。規約としては、1.呼び出し規約は__stdcall、2.戻り値は32bit、3.1つの引き数は最大64bit、4.可変引き数は取らない、の4つだけで、後はどんな引き数を取ろうと呼び出すことが出来ます。
 まぁつっても、どんな引き数を渡せばいいかはちゃんと分からなければいけないので、FUNCという構造体でそれを動的に指定してやるワケです。fcallはFUNC構造体を受け取ると、リストの後ろから順番に引き数をスタックに積み、関数を呼び出してくれます。ここで引き数リストは、DWORD型の場合は値を、QWORD型の場合は変数へのポインタを保持しているものとします。つーか、doubleとかfldあたりで積まれるのかと思ってたのでびっくり。まぁ確かにこっちの方が面倒でなくてよひですが。
 こんな感じで使用。

////////////////////////////////////////////////////////////////////////

int	__stdcall		st(int n, double f, conct char *s)
{
	return	printf( "%n, %f, %s\n", n, f, s );
}

void	main( void )
{
	int	n	= 645;
	double	f1	= 3.2;
	char	s[]	= "hello, wold!"
	FUNC	fn( (fn_t)st, 3 );
	fn.ptype[0]	= TYPE_DWORD;	fn.parg[0]	= (long)n;
	fn.ptype[1]	= TYPE_QWORD;	fn.parg[1]	= (long)&f;
	fn.ptype[2]	= TYPE_DWORD;	fn.parg[2]	= (long)world;
	fcall( &fn );
	return	0;
}

////////////////////////////////////////////////////////////////////////

 もうちと頑張れば、どんな関数でも呼び出せるよーになりますが。関数Typeを作ってそこに__stdcallか__cdeclか指定するようにすれば1は略解決、4も関数が可変引き数って分かっていればリストをムリに長くすればなんとか。3は引き数型を引き数サイズ(4バイト単位)にして、逆向きに積めばOK。問題は2ですか。戻り値が32bit以上の場合、どーやら戻り値を格納してもらうメモリ領域を確保し、そのアドレスをまずスタックにpushしてから引き数を積むようなので、そーゆー処理を書けばなんとかなるでしょーか。でもまぁ、32bit以上の戻り値を受け取ることなんぞそうないし、64bit以上の引き数もポインタで渡せって感じだし、可変引き数も無くてもいいだろうし、そもそもDLL書くときゃたいてい__stdcallだろってことでこれでいーよーな気がします。

 まぁどーせ暇人の与太なので、暇な人だけ付き合ってくだされってことで。
 また機会があればアドバンスのソース解説なんぞでもやりますよー。でわ〜。いやー、今日は文章が多いのでとても絵なんて載せられませんな。はっは。

written by ぼしゅー


※ 注意 ※
 ぼしゅーのページは諸事情により移転しています。暫定移転なのでURLは変えていません。作品のβ版が欲しい場合は作品紹介等から行って下さい。


[戻る]