4.1 はじめに

この章では、BMSCR構造体の使い方とWin32APIの使い方、第1章で説明を後回しにしたスタックフレームについて説明します。 LOADLIB.DLLを利用したモジュールが公開されているサイトを見ると、Win32APIを利用する事でHSPでもいろいろな事ができるのが分かると思います。 是非Win32APIの使い方をマスターして下さい。

4.2 BMSCR構造体とは

BMSCR構造体には、HSPが管理するウインドウ画面についての情報が格納されています。 DLLにBMSCR構造体へのポインタが渡される場合は、現在の操作先画面(gsel命令で指定したウインドウID)の情報が渡されます。具体的にどんな情報が格納されているかは、HSPの公式サイトにある「HSPからのDLL呼び出し方法リファレンスマニュアル」を参照してください。

4.3 BMSCR構造体の宣言

PVAL構造体、PVAL2構造体の宣言と同様に、BMSCR構造体を宣言しましょう。 HSPの公式サイトにある「HSPからのDLL呼び出し方法リファレンスマニュアル」の中で、BMSCR構造体は以下のように宣言されています。

#define objkazz 64
typedef struct BMSCR
{
        //              Bitmap buffer structure
        //
        int             flag;                   // used flag
        int             sx;                     // X-size
        int             sy;                     // Y-size
        int             palmode;                // palmode
        HDC             hdc;                    // buffer HDC
        BYTE            *pBit;                  // bitmap pointer
        BITMAPINFOHEADER *pbi;                  // infoheader
        HBITMAP         dib;                    // bitmap handle(DIB)
        HBITMAP         old;                    // bitmap handle(OLD)
        RGBQUAD         *pal;                   // palette table
        HPALETTE        hpal;                   // palette handle
        HPALETTE        holdpal;                // palette handle (old)
        int             pals;                   // palette entries
        HWND            hwnd;                   // window handle
        HANDLE          hInst;                  // Instance of program
        int             infsize;                // *pbi alloc memory size
        int             bmpsize;                // *pBit alloc memory size

        //              Window object setting
        //
        int             type;                   // setting type
        int             wid;                    // window ID
        short           fl_dispw;               // display window flag
        short           fl_udraw;               // update draw window
        int             wx,wy,wchg;             // actual window size x,y
        int             xx,yy;                  // buffer view point x,y
        int             lx,ly;                  // buffer view size x,y
        int             cx,cy;                  // object cursor x,y
        int             ox,oy,py;               // object size x,y,py
        int             texty;                  // text Y-axis size
        int             gx,gy,gmode;            // gcopy size

        HBRUSH          hbr;                    // BRUSH handle
        HPEN            hpn;                    // PEN handle
        HFONT           hfont;                  // FONT handle
        HFONT           holdfon;                // FONT handle (old)
        COLORREF        color;                  // text color code

        HANDLE          hCld[objkazz];          // buttonのhandle
        int             owid[objkazz];          // buttonのjump ID
        int             owb;                    // handleのindex

        int             textspeed;              // slow text speed
        int             cx2,cy2;                // slow text cursor x,y
        int             tex,tey;                // slow text limit x,y
        char            *prtmes;                // slow message ptr
} BMSCR;

「HSPからのDLL呼び出し方法リファレンスマニュアル」では、C言語でHSPのプラグインを作るという前提で解説されています。 従って、上記のプログラムをそのまま書き写してもMASM32は理解してくれません。 MASM32では以下のように記述します。 ちなみにMASM32では、typeという名前は予約語になっていて使えないので、代わりに_typeという名前にしました。 また、cxという名前もcxレジスタと重なっていて使えないので、代わりに_cxという名前にしました。

objkazz equ             64

BMSCR   struct
;                       Bitmap buffer structure
;
        flag            dword   ?               ;used flag
        sx              dword   ?               ;X-size
        sy              dword   ?               ;Y-size
        palmode         dword   ?               ;palmode
        hdc             dword   ?               ;buffer HDC
        pBit            dword   ?               ;bitmap pointer
        pbi             dword   ?               ;infoheader
        dib             dword   ?               ;bitmap handle(DIB)
        old             dword   ?               ;bitmap handle(OLD)
        pal             dword   ?               ;palette table
        hpal            dword   ?               ;palette handle
        holdpal         dword   ?               ;palette handle (old)
        pals            dword   ?               ;palette entries
        hwnd            dword   ?               ;window handle
        hInst           dword   ?               ;Instance of program
        infsize         dword   ?               ;*pbi alloc memory size
        bmpsize         dword   ?               ;*pBit alloc memory size

;                       Window object setting
;
        _type           dword   ?               ;setting type
        wid             dword   ?               ;window ID
        fl_dispw         word   ?               ;display window flag
        fl_udraw         word   ?               ;update draw window
        wx              dword   ?               ;actual window size x
        wy              dword   ?               ;actual window size y
        wchg            dword   ?
        xx              dword   ?               ;buffer view point x
        yy              dword   ?               ;buffer view point y
        lx              dword   ?               ;buffer view size x
        ly              dword   ?               ;buffer view size y
        _cx             dword   ?               ;object cursor x
        cy              dword   ?               ;object cursor y
        ox              dword   ?               ;object size x
        oy              dword   ?               ;object size y
        py              dword   ?               ;object size py
        texty           dword   ?               ;text Y-axis size
        gx              dword   ?               ;gcopy size
        gy              dword   ?               ;gcopy size
        gmode           dword   ?               ;gcopy size

        hbr             dword   ?               ;BRUSH handle
        hpn             dword   ?               ;PEN handle
        hfont           dword   ?               ;FONT handle
        holdfon         dword   ?               ;FONT handle (old)
        color           dword   ?               ;text color code

        hCld            dword   objkazz dup(?)  ;buttonのhandle
        owid            dword   objkazz dup(?)  ;buttonのjump ID
        owb             dword   ?               ;handleのindex

        textspeed       dword   ?               ;slow text speed
        cx2             dword   ?               ;slow text cursor x
        cy2             dword   ?               ;slow text cursor y
        tex             dword   ?               ;slow text limit x
        tey             dword   ?               ;slow text limit y
        prtmes          dword   ?               ;slow message ptr
BMSCR   ends

4.4 スタックフレーム

第1章で説明を後回しにしていた、スタックフレームについて説明します。 第2章と第3章では、レジスタの値を退避しておき、後で元に戻すためにpush命令やpop命令を使いました。 しかしスタックには、この他にも重要な役割があります。

HSPからプラグインを呼び出したり、C言語のプログラムが関数を呼び出したりする時は、引数がスタックにpushされてから呼び出されます。 呼び出された側は、スタックの中身を参照することで引数を参照する事ができます。 「えーっ、第2章や第3章ではそんな事してないよ」と思うかもしれませんが、第1章のサンプルを見直してみてください。 プログラムを簡単にするためのおまじないを使っていない方のプログラム(ASMHSP2.ASM)を見ると、確かにスタックの中身を参照していますね。 まず「push ebp」でebpレジスタを退避しておき、「mov ebp,esp」でespレジスタの値をebpレジスタに代入します。 この時スタックの中身は下の図のようになっています。 この後、「push esi」でesiレジスタの値を退避するとespの値は減少しますが、ebpの値は変化しません。 従って、ebpレジスタを利用して引数を参照する事ができるのです。

ASMHSP2.ASM
.486
.model		flat
.code

_DLLMain@12	proc	public
		push	ebp
		mov	ebp,esp
		mov	eax,1
		leave
		ret	12
_DLLMain@12	endp

_asmhsp@16	proc	export
		push	ebp
		mov	ebp,esp
		push	esi
		mov	esi,[ebp+ 8]
		mov	eax,[ebp+12]
		add	eax,[ebp+16]
		add	eax,[ebp+20]
		mov	[esi],eax
		mov	eax,0
		pop	esi
		leave
		ret	16
_asmhsp@16	endp

end		_DLLMain@12
「mov ebp,esp」の直後のスタックの中身

このASMHSP2.ASMを見て、「あれっ?」と思われた方もいるかもしれません。 push ebpがあるのにpop ebpがないからです。 ではどこでebpの値を元に戻しているのかというと、leave命令でebpの値を元に戻しています。 leave命令は、「mov esp,ebp」と「pop ebp」を一度に行ってくれる命令です。

さて、スタックには、値を一時的に退避する、引数の受け渡し、の他にもう一つの役割があります。 それはローカル変数です。 次にプロシージャ(関数)が呼び出された時まで値を覚えておく必要がない一時的に使用する変数は、通常はスタックから確保します。 それには、「push ebp」、「mov ebp,esp」の直後に、ローカル変数として使用するバイト数をespレジスタから引きます。 例えば、ローカル変数を4バイト使用する場合にはespから4を引きます。 この場合、スタックの中身は下の図のようになります。

ローカル変数の使用例
.486
.model		flat
.code

_DLLMain@12	proc	public
		push	ebp
		mov	ebp,esp
		mov	eax,1
		leave
		ret	12
_DLLMain@12	endp

_asmhsp@16	proc	export
		push	ebp
		mov	ebp,esp
		sub	esp,4

		何らかの処理

		leave
		ret	16
_asmhsp@16	endp

end		_DLLMain@12
「sub esp,4」の直後のスタックの中身

このように、スタックの中にある、引数やローカル変数が格納されている領域の事をスタックフレームと言います。 スタックフレームを扱うにはこのような複雑な手順を踏まなければなりませんが、MASM32ではproc命令を使う事でASMHSP.ASMのように自動的に行ってくれます。 引数の受け取りは、既に述べたようにproc命令に引数を書き並べる事で自動的に行う事ができます。 また、ローカル変数の確保もlocal命令(これについては後で述べます)を使う事で自動的に行う事ができます。

4.5 BMSCR構造体とWin32APIを利用したサンプル

BMSCR構造体とWin32APIを利用したサンプルとして、HSP標準のpset命令と同じ働きをするbms_pset命令を作ってみます。 まずは以下のソースを見て下さい。 これは、HSPSDKに付いているサンプル(HSPSDK.CPP)の一部をMASM32に移植したものです。

BMSTEST.ASM
.486
.model          flat,stdcall
.code
option          casemap :none

include         \masm32\include\windows.inc
include         \masm32\include\gdi32.inc
include         \masm32\include\user32.inc
include         hspdll.inc
includelib      \masm32\lib\gdi32.lib
includelib      \masm32\lib\user32.lib

DLLMain         proc    p1,p2,p3
                mov     eax,1
                ret
DLLMain         endp

bms_send        proc    uses ebx ecx esi bmscr:ptr,x,y,sx,sy
                local   hdc,opal
                mov     esi,bmscr
                cmp     [esi].BMSCR.fl_udraw,0
                jnz     @f
                ret
@@:             invoke  GetDC,[esi].BMSCR.hwnd
                mov     hdc,eax
                cmp     [esi].BMSCR.hpal,NULL
                jz      @f
                invoke  SelectPalette,hdc,[esi].BMSCR.hpal,0
                mov     opal,eax
                invoke  RealizePalette,hdc
@@:             mov     ebx,x
                sub     ebx,[esi].BMSCR.xx
                mov     ecx,y
                sub     ecx,[esi].BMSCR.yy
                invoke  BitBlt,hdc,ebx,ecx,sx,sy,[esi].BMSCR.hdc,x,y,SRCCOPY
                cmp     [esi].BMSCR.hpal,NULL
                jz      @f
                invoke  SelectPalette,hdc,opal,0
@@:             invoke  ReleaseDC,[esi].BMSCR.hwnd,hdc
                ret
bms_send        endp

bms_pset        proc    export uses esi bmscr:ptr,xx,yy,dummy
                mov     esi,bmscr
                invoke  SetPixel,[esi].BMSCR.hdc,xx,yy,[esi].BMSCR.color
                invoke  bms_send,esi,xx,yy,1,1
                mov     eax,0
                ret
bms_pset        endp

end             DLLMain

include命令は、HSPのinclude命令と同様に指定したファイルを読み込む命令です。 windows.inc、gdi32.inc、user32.incには、Win32APIの一部が宣言されています。 上記のサンプルで利用しているWin32APIは、これら3つのファイルをインクルードすれば利用できます。 しかし、上記のサンプルで利用していないAPIを利用する場合、利用するAPIによっては他のincファイルもインクルードする必要があります。

hspdll.incはhspdll.hをMASM32用に移植したもので、4.3で示したBMSCR構造体の宣言と、3.4で示したPVAL構造体、PVAL2構造体の宣言が書いてあります。 今後PVAL構造体、PVAL2構造体、BMSCR構造体をMASM32で利用する時は、こちらからhspdll.incをダウンロードしてご利用下さい。

includelib命令は、リンクするライブラリを指定します。 これも、利用するWin32APIによって必要なlibファイルが異なります。 どのWin32APIを使うのにどのlibファイルが必要になるかは、MASM32をインストールしたフォルダ(通常はc:\masm32)の中のLIBというフォルダにある、WIN32API.CSVというファイルに書かれています。

それではbms_psetの中身を見てみましょう。 proc命令の行に、uses esiという宣言がありますね。 実は、このように引数の前にusesと書いてレジスタの名前を書く事で、そのレジスタを自動的に退避してくれるのです。 3章のサンプルのようにproc命令の後にpush命令を書いて、ret命令の前にpop命令を書いても同じ結果になります。 ではなぜ3章ではこの機能を使わなかったかというと、push命令とpop命令について知っておいて欲しかったからです。

「mov esi,bmscr」で、BMSCR構造体へのポインタをesiレジスタに代入しています。 これは3章でPVAL2構造体を扱った時と同じですね。 その次の行を見ると、invokeという見慣れない命令があります。 このinvokeというのは、引数をスタックにpushしてからプロシージャを呼び出す命令です。 push命令を並べて引数をpushしてからcall命令でプロシージャを呼び出しても同じですが、invokeを使った方が見た目がスッキリして見やすくなります。 但し、procやlocalと同様invokeもMASM32特有の擬似命令なので、他のアセンブラでは使えない場合があります。 hspsdk.cppの中のbms_psetを見ると、SetPixelを呼び出してからbms_sendを呼び出していますね。 bmstest.asmの中のbms_psetと見比べると、この2行がinvoke命令を使っている2行と対応しているのが分かると思います。

さて、bms_sendの中身を見てみましょう。 先程説明したlocal命令が出てきました。 local命令では、変数の大きさを明記しなかった場合一つの変数につき4バイトのメモリが確保されます。 従って、hdcとopalという名前の4バイトの変数が確保されます。

BMSTEST.ASMの中のbms_send
bms_send        proc    uses ebx ecx esi bmscr:ptr,x,y,sx,sy
                local   hdc,opal
                mov     esi,bmscr
                cmp     [esi].BMSCR.fl_udraw,0
                jnz     @f
                ret
@@:             invoke  GetDC,[esi].BMSCR.hwnd
                mov     hdc,eax
                cmp     [esi].BMSCR.hpal,NULL
                jz      @f
                invoke  SelectPalette,hdc,[esi].BMSCR.hpal,0
                mov     opal,eax
                invoke  RealizePalette,hdc
@@:             mov     ebx,x
                sub     ebx,[esi].BMSCR.xx
                mov     ecx,y
                sub     ecx,[esi].BMSCR.yy
                invoke  BitBlt,hdc,ebx,ecx,sx,sy,[esi].BMSCR.hdc,x,y,SRCCOPY
                cmp     [esi].BMSCR.hpal,NULL
                jz      @f
                invoke  SelectPalette,hdc,opal,0
@@:             invoke  ReleaseDC,[esi].BMSCR.hwnd,hdc
                ret
bms_send        endp
HSPSDK.CPPの中のbms_send
void bms_send( BMSCR *bm, int x, int y, int sx, int sy )
{
	HDC hdc;
	HPALETTE opal;
	if (bm->fl_udraw==0) return;
	hdc=GetDC( bm->hwnd );
	if (bm->hpal!=NULL) {
		opal=SelectPalette( hdc, bm->hpal, 0 );
		RealizePalette( hdc );
	}
	BitBlt( hdc, x-bm->xx, y-bm->yy, sx, sy, bm->hdc,x,y, SRCCOPY );
	if (bm->hpal!=NULL) {
		SelectPalette( hdc, opal, 0 );
	}
	ReleaseDC( bm->hwnd,hdc );
}

HSPSDK.CPPの方を見ると、まずBMSCR構造体の中のfl_udrawが0かどうかを調べ、0ならば呼び出された場所へreturn命令で戻っていますね。 BMSTEST.ASMの方もやっている事は同じです。 まずcmp命令でBMSCR構造体の中のfl_udrawが0かどうかを調べ、0でなければjnz命令でret命令を飛ばします。 0ならば呼び出された場所へret命令で戻ります。

次にGetDCというWin32APIを呼び出し、戻り値をhdcに代入しています。 戻り値はeaxレジスタに入っているので、mov命令でeaxレジスタの値をhdcに代入します。

次に、BMSCR構造体の中のhpalがNULLであればSelectPaletteとRealizePaletteを呼び出しています。 NULLというのは、windows.incやstdlib.hの中で定義されている定数です。 後で出てくるSRCCOPYも、windows.incやwin.hの中で定義されている定数です。

さて、次のBitBltというWin32APIを呼び出している部分に注目してください。 2番目の引数は、xからBMSCR構造体の中のxxの値を引いた値です。 3番目の引数は、yからBMSCR構造体の中のyyの値を引いた値です。 しかし、invokeの引数に「x-[esi].BMSCR.xx」と書いたり、「y-[esi].BMSCR.yy」と書いたりする事はできません。 引数を予め計算しておく必要があります。 そこで、ebxレジスタとecxレジスタを使って引数を計算しています。

次に、BMSCR構造体の中のhpalがNULLであればSelectPaletteを呼び出します。 次にReleaseDCを呼び出し、bms_sendが呼び出された場所へ戻ります。

4.6 BMSTEST.ASMの動作確認

意図した通りに動くかどうか、以下のスクリプトで確認してみてください。 点を1個書いただけでは分かりにくいので、repeat命令を利用して横線を引いてみました。
BMSTEST.AS
#uselib "bmstest.dll"
#func bms_pset bms_pset 2

repeat 100,10
	bms_pset cnt,10
loop
stop
PVALTEST.ASの実行結果

4.7 おわりに

この章では説明する事が多く、1章、2章、3章と比べて長くなってしまいしました。 しかし、4.1で述べたように、Win32APIを利用するといろいろな事ができます。 理解しづらい部分もあるかと思いますが、分からない事があれば質問するなどして理解してください。