Program of the air

■ Program of the air とは?

Program of the air は、僕が 1991 年にリリースした、通信環境を構築するための基本ソフトウェアです。MS-DOS(IBM PC互換機および PC-9800シリーズ)上で動作します。

Program of the air は、単体のアプリケーションではなく、air 開発環境と air 仮想機械からなるプラットホームの総称です。air 開発環境で開発したソフトウェアは、air 仮想機械上で実行させることができます。air 仮想機械は NEC PC-9800 シリーズ用と DOS/V 用が用意されているので、ソフトウェアは双方で動作させることが可能です(ネイティブなハードウェアコールを使っていない場合)。

仮想機械さえ用意すれば、UNIX や Macintosh のような全く別のプラットホームで動作させることも可能な設計になっています。

air.exe/air.ovl - air virtual machine

air 仮想機械です。実際には仮想 CPU コードを実行する機械と、通信環境を構築するために強化された API 群、マルチウィンドウシステム、他のアプリケーションをチャイルドプロセスで実行する際にメモリをスワップアウトさせるメモリマネージャなどから構成されています。

通信用のドライバは分離されているので、後から拡張カードに対応した専用のドライバを組み込んで使うことができます。実際、拡張 RS-232C カードを開発した会社から専用のドライバがリリースされたり、ユーザの方がより高機能な通信ドライバを開発されたことがあります。

air 仮想機械のアプリケーションとして有名なのは、NIFTY SERVE 専用通信環境である craft です。air 用のソフトウェアはプロセス間通信ができるので、craft は多くのアドインソフトウェアと一緒に動作します。

acc1.exe - air c compiler

air 開発環境では ANSI C をベースとした air c を利用します。

air c は、ANSI C に準拠しており、データ構造も文字や数値、配列だけでなく、構造体や共用体までサポートした本格的なものです。もちろん、関数の定義も ANSI C と同様です。

air c でソースコードをコンパイルして、air 形式のオブジェクトを作成します。さらに、Native C(MS-C、Turbo C、LSI-C86)の関数を直接コールすることができるので、速度を稼ぎたい場合やハードウェアを直接叩きたい場合は CPU 依存コードで作成して、air c の仮想コードと直接リンクすることが可能です。

もちろん、分割コンパイルをサポートしています。

aas.exe - air assembler

air c compiler が出力した仮想アセンブルコードを、アセンブルしてオブジェクトコードを出力します。

ald.exe - air loader

air assembler が出力したオブジェクトや Native C で作成したネイティブコードのオブジェクトをリンクして air 実行形式を作成します。air loader で作成されたプログラムは、air virtual machine 上で動作させることができます。

■ air virtual machine の構造

air software
air virtual machine layer
Virtual Machine, API, Window System(CoWS)
native cpu call
air driver layer
Memory Manager, Serial Driver, Timer Driver
MS-DOS
Hardware (IBM PC Compatible or NEC PC-9800 Series)

■ Program of the air 実行例

craft image
craft の実行例
craft は2万行のソースコードから構成されている。

オーバーラップウィンドウが装備されており、プログラムで独立に操作することができる。

イベントのマッピングは独自方式で、メイン通信部の RMAP(受信マッピング)、SMAP(送信マッピング)のほかに、それぞれのウィンドウが KMAP(キーボードマッピング)を持っている。

マッピングテーブルは 256 要素からなり、それぞれ流れるコードに対応する。このテーブルに、要素とは別の文字コードを割り当てると、その割り当てられたコードに変換される。一方、文字コードではなく関数へのポインタを割り当てると、その関数がイベントハンドラとしてコールされ、ハンドラの戻り値が新しいコードとして採用される。

このようなテーブルを複数ラッピングすることが可能で、たとえば受信コードの変換テーブルと、受信イベントの監視テーブルを同時に使用することで、それぞれ独立してに機能を実行させることが可能となる(この場合、受信コード変換がされた後にイベント監視テーブルが呼び出される)。

なお、当初はマウスイベントを捕捉する MMAP(マウスマッピング)も存在していたが、RS-232C のシリアル割り込みと相性が悪かったため、後に取り外された。

■ Program of the air ダウンロード

ファイル 内容
airpc120.lzh air 実行環境 for DOS/V
air120.lzh air 実行環境 for PC-9801 Series
aird100.lzh air 開発環境(MS-DOS Generic)
airm099.lzh air 開発マニュアル(LaTeX)

■ air プログラムの例

air c のプログラムは次のような感じになります。
NIFTY SERVE へのログイン処理を行っています(craft のソースより抜粋)。


/*
 *login.r
 *
 * NIFTY SERVE ログイン処理
 *
 */

#include    "stdlib.h"
#include    "nlib.h"
#include    "ctype.h"
#include    "craft.h"
#include    "cftlib.h"
#include    "cftwin.h"
#include    "cftntv.h"
#include    "message.h"
#include    "selector.h"
#include    "codec.h"

void hangup( void )
{
    alert("ホストの反応がありません.回線切断中...");
    cutline();
}

static void read_new( void )
{
    int last,tmp;
    char last_new[128];
    char fname[FILENAME_MAX];
    char buf[128];
    FILE* fp;

    strcpy(last_new, stamp->news.title);
    if(last_new[0] == 0)
        strcpy(last_new,"**はじめてのお知らせ**");

    last = 0;
    wait( 30 ) {
    case    "= = = = = = = = = ":
    case    "番号またはコマンド":
    case    last_new:
	break;
    case    "\n":
	if(tmp = atoi(sysbuf))
	    last = tmp;
	if(last == 1) {
	    *strchr(sysbuf,'\r') = 0;
	    strcpy(stamp->news.title,sysbuf+6);
	}
	continue;
    case    "\n>":
	sendch('\r');
	continue;
    }
    if(last) {
	stsworking("お知らせ読み取り");
	waitprompt(0);
	send("SET PAGE OFF\r");
	waitprompt(0);
	sendf("%d-1\r",last);
	recv(10,"\n");
	sprintf(fname, "%sNEWS",set->path.log);
	if(fp = logopen(fname)) {
	    glog_off();
	    endseek(fp);
	    head(buf,"NEWS");
	    fprintf(fp,"%s\r\n\r\n",buf);
	}
	else
	    alert("%s をオープンできません",fname);
	wait( 30 ) {
	case	"E:終了)\r\n>" :    /* 続き */
	    fseek(fp, -31, SEEK_END);
	    sendch('\r');
	    continue;
	case	"\n表示終了":
	case	"\n◆%*◆\r":
	    break;
	timeout:
	    continue;
	}
	if(fp)
	    lclose(fp);
    }
}
int login_nifty( char* id, char* encoded_password )
{
    char fname[FILENAME_MAX];
    char buf[256];
    char password[PASSWORD_LEN];
    FILE* fp;
    char* p;
    int ret;
    struct msg {
	int type;
	char* id;
    } m;

    stsworking("オートログイン");

    decode(password, encoded_password, PASSWORD_LEN);

    ret = 0;
    wait( 30 ){
    case "on-ID  --->" :
        send("SVC\r") ;
        continue ;
    case "er-ID  --->" :
        sendf("%s\r",id);
        continue ;
    case "rd --->" :
	if(password[0])
	    sendf("%s\r",password);
	else
	    cputch('\a');
        continue ;
    case    "\n−−メールが%*(未読分" :
	if(set->mode.mailread != MAIL_READNONE)
	    ret = 1;
        continue ;
    case    "\n−メールが" :
        if(set->mode.mailread == MAIL_READALL)
            ret = 1;
        continue ;
    case    "\n前回LOG OUT " :
	if(set->mode.login_check) {
	    recv(10,"\n");
	    *strchr(sysbuf,'\r') = 0;
	    if(strcmp(sysbuf+12,stamp->logout) > 0) {
		puts(p = "◆前回、別な通信環境で LOG OUT したことになっています◆");
		sprintf(buf,"%sERROR.LOG", set->path.log);
		if(fp = fopen(buf,"a")) {
		    head(buf,"LOGIN");
		    fprintf(fp,"%s\n%s\n",buf,p);
		    fclose(fp);
		}
		cputch('\a');
		cputch('\a');
		cputch('\a');
	    }
	    else
		puts("LOG OUT 時間は正常です");
	}
	continue;
    case   "\n☆%*☆\r\n":
	sprintf(fname, "%sFLASH",set->path.log);
	if(fp = logopen(fname)) {
	    endseek(fp);
/*
	    head(buf,"FLASH");
	    fputs(buf,fp);
	    fputs("\r\n",fp);
*/
	    wait( 30 ) {
	    case "\n ":
		continue;
	    case "\n%?":
		break;
	    }
	    fseek(fp,-1,SEEK_END);
	    fputc(' ',fp);
	    lclose(fp);
	}
	continue;
    case   "\n今週の%*\r\n\r\n":
	if(set->mode.read_new)
	    read_new();
	else {
	    recv(10,"\n"); /* first news */
	    *strchr(sysbuf,'\r') = 0;
	    if(strcmp(sysbuf + 6, stamp->news.title) != 0) {
		strcpy(stamp->news.title, sysbuf + 6);
		sprintf(fname, "%sNEWS",set->path.log);
		if(fp = logopen(fname)) {
		    glog_off();
		    endseek(fp);
		    head(buf,"NEWS");
		    fprintf(fp,"%s\r\n\r\n",buf);
		    fprintf(fp,"%s\r\n",sysbuf);
		    recv(30,"\n    ");
		    glog_on();
		    lclose(fp);
		}
		else
		    alert("%s をオープンできません",fname);
	    }
	}
	continue;
    case    "\n>" :
    	send("SET DLM ON\r");
    	recv(2,'\n>');
        stsposition("TOP");
        break ;
    case    "只今の時間、定期%*します。\r\n":
    case    "−保守作業の為し%*ません−\r\n":
	alert("ホストが停止しています.回線切断中...");
	cutline();
	ret = -1;
	break;
    timeout:
        if(recv(1, "%?"))
            continue ;
	hangup();
	ret = -1;
	break;
    }
    if(ret != -1) {
	m.type = MSG_SETLOGINID;
	m.id = id;
	msgsend("main.rx",&m);
    }
    return ret;
}


戻る COUNTER