[MSX] Z88DKでC言語プログラムからBIOSを呼び出す #MSX #MSX0

Z88DKでC言語プログラムからBIOSを呼び出す方法

Z88DKではC言語内にアセンブラのソースを書くことができ、C言語の関数・戻り値と連動させることもできます。

Z88DK自体のインストールは、別エントリを参照ください。

[MSX] Z88DKを使ってC言語でマシン語プログラムをつくる #MSX #MSX0
https://mobileff.blogspot.com/2023/12/msx-z88dkc-msx-msx0.html

アセンブラのソースをCソース内に記述する

C言語ソース内に#asm#endasmで囲った内部にはアセンブラのソースを書くことができます。 EQUなどの定数定義は、ソースコードの先頭に書いておけば、以後は関数内でも参照することが可能です。 アセンブラの記法はZASMのものがそのまま使えるような印象です。

定数定義の例

#asm
WRSLT	EQU	0014H ;MAIN-ROM BIOS CALL WRSLT
CALSLT  EQU 001CH ;MAIN-ROM BIOS CALL CALSLT
ENASLT	EQU	0024H ;MAIN-ROM BIOS CALL ENASLT
CHGET	EQU	009FH ;MAIN-ROM BIOS CALL CHGET
CHPUT	EQU	00A2H ;MAIN-ROM BIOS CALL CHPUT
CLS		EQU 00C3H ;MAIN-ROM BIOS CALL CLS
EXPTBL  EQU 0FCC1H ; EXPTBL 
SYSTEMCALL	EQU	0F37DH ; DISK-BASIC SYSTEM CALL
CREATE_FILE	EQU	016H ;
SET_DMA		EQU	01AH ;
OPEN_FILE	EQU	00FH ;
RND_WRITE	EQU	026H ;
RND_READ    EQU 027H ;
CLOSE_FILE	EQU	010H ;
#endasm
  

関数内でアセンブラソースを書く

関数内でアセンブラを実行する場合、関数定義内で#asm#endasmで囲みます。

void msx_cls() {
#asm
    CALL    CLS  ; BIOS(CLS)をコール(定数CLSはEQUで定義済みの前提)
	RET          ; リターン
#endasm   
}

関数の引数をアセンブラと連携する

関数の引数は、以下のようにして任意のレジスタに格納して使うことができます。

void msx_chput(char c) {
#asm
    LD      IX, 2    ; SPに加算したい値をIXにロード
    ADD     IX, SP   ; IXにSPの値を加算(SP+2をIXに代入)
    LD      A, (IX)  ; (IX)のメモリ内容をAレジスタにロード
    CALL    CHPUT    ; BIOS(CHPUT)をコール(定数CHPUTはEQUで定義済みの前提)
	RET
#endasm    
}

スタックポインタSPの2バイト後ろから引数の内容が格納されていきます。 ただし、引数が複数あるときは、引数順と逆順に格納されていくので、取り出しかたに注意が必要です。

void msx_ldir_mem2dma(char* dma_buffer,unsigned short int record_size,unsigned short int read_address) {
#asm
    ; 関数に渡された引数の値を取り出し
    LD      IX, 2
    ADD     IX, SP
	LD		D, (IX+5)  ; 引数 dma_bufferの上位バイト
	LD		E, (IX+4)  ; 引数 dma_bufferの下位バイト
	LD		B, (IX+3)  ; 引数 record_sizeの上位バイト
	LD		C, (IX+2)  ; 引数 record_sizeの下位バイト
    LD      H, (IX+1)  ; 引数 read_addressの上位バイト
    LD      L, (IX)    ; 引数 read_addressの下位バイト
	LDIR
#endasm
}

このように最後の引数を(IX)とし、前の引数は(IX+1)(IX+2)・・・のように指定して格納していきます。

関数の戻り値をアセンブラと連携する

戻り値については、int固定のようで、リターン直前にHLレジスタに格納された値が戻り値となります。 BIOSコールの場合は結果がAレジスタ(1バイト)で得られるものも多いですが、その場合はHレジスタに0をロードし、LレジスタにAレジスタをロードしておくと良いと思います。

char msx_chget() {
#asm
    CALL    CHGET
    LD      H,0x00
    LD      L,A  
	RET
#endasm
}

その他

関数内でアセンブラソースとCソースを混在させることも可能とは思いますが、関数内のアセンブラと関数内のCプログラムで値を連携する方法は調べていないのでよくわかりません。

利用例

上記に出てきたような関数を作って、テキスト表示と文字入力をMSX BIOSコールで作ってみると以下のようになります。

mymsx.h

void msx_chput(char c);
void msx_chput_CRLF();
void msx_cls();

void msx_print(char[] str);
void msx_println(char[] str);

char msx_chget();

char* msx_input();
char* msx_input_line_headless();
char* msx_input_with_prompt(char[] prompt);

mymsx.c

#include <stdlib.h>
#include "mymsx.h"

#asm
CHGET	EQU	009FH ;MAIN-ROM BIOS CALL CHGET
CHPUT	EQU	00A2H ;MAIN-ROM BIOS CALL CHPUT
CLS		EQU 00C3H ;MAIN-ROM BIOS CALL CLS
#endasm

char* input_buffer = NULL;

void msx_chput(char c) {
#asm
    LD      IX, 2
    ADD     IX, SP
    LD      A, (IX)
    CALL    CHPUT
	RET
#endasm    
}

void msx_chput_CRLF() {
    msx_chput(0x0D);
    msx_chput(0x0A);
}

void msx_cls() {
#asm
    CALL    CLS
	RET
#endasm   
}

void msx_print(char[] str) {
    for(int i=0;i<256;i++) {
        if(str[i]==0) {
            return;
        }
        msx_chput(str[i]);
    }
}

void msx_println(char[] str) {
    msx_print(str);
    msx_chput_CRLF();
}

// 1文字入力
char msx_chget() {
#asm
    CALL    CHGET
    LD      H,0x00
    LD      L,A  
	RET
#endasm
}

// 1行入力
char* msx_input() {
    msx_chput('?');
    if (input_buffer == NULL) {
        input_buffer = (char*)malloc(sizeof(char)*256);
    }
    for(int i=0;i<256;i++) {
        input_buffer[i]=0;
    }
    int index=0;
    while(index<256) {
        char s = msx_chget();
        if (s==0x0D) {
            msx_chput_CRLF();
            input_buffer[index]=0;
            return input_buffer;
        }
        if (s==0x08 && index>0) {
            index--;           
        }
        input_buffer[index++]=s;
        msx_chput(s);
    }
    return input_buffer;
}

// 1行入力
char* msx_input_line_headless() {
    if (input_buffer == NULL) {
        input_buffer = (char*)malloc(sizeof(char)*256);
    }
    for(int i=0;i<256;i++) {
        input_buffer[i]=0;
    }
    int index=0;
    while(index<256) {
        char s = msx_chget();
        if (s==0x0D) {
            input_buffer[index]=0;
            return input_buffer;
        }
        input_buffer[index++]=s;
    }
    return input_buffer;
}

char* msx_input_with_prompt(char[] prompt) {
    msx_print(prompt);
    return msx_input();
    // return prompt;
}

hello.c

#include <stdlib.h>
#include "mymsx.h"

void main() {
    msx_println("Hello!MSX0!");
    char* str = msx_input_with_prompt("INPUT TEXT=");
    msx_print(str);
}

コンパイルは、以下のようなコマンドを使います。

zcc +msx -create-app hello.c mymsx.c -subtype=disk -o hello.bin -DAMALLOC -lm

コンパイル後に、作成されたhello.msxをMSX本体に転送します。 Disk BASICでは、

bload "hello.msx",r

でプログラムを実行できます。 ※hello.binというファイルも生成されていますが、こちらはBSAVE形式ではないため、BASICで直接呼び出すことはできませんのでご注意ください。

コメント