パソコンサンデーしてみる。

バイナリにパッチまでしてようやくDOSでBASICが使えるようになったのにそれを使わないのはもったいない。QBasicもそれなりに便利だけど、教育上良くないとも言われる旧来のBASICにもそれなりの趣がある、と思う。

関連
IBM DOS (PC DOS)のBASIC

使えるようになったものの別売のBASICのマニュアルが無いからなんとなくしか使えない。自分が知らない世界の8ビットだとか16ビットだとか言ってた当時のBASICといえばMS-BASICベースかそれ準拠の古典的BASICだというところまでは推測がついても、このころのBASICは機種依存の「方言」が多くて共通項だけではいまいちおもしろいプログラムを作れない。

使えると楽しくなるステートメントを探ってみた。

・テキストカラー
テキストと背景の色指定ができないとBASICの魅力が半減する。趣があるIBM 5550のグリーンディスプレイを再現するのも一興。標準のスクリーンモード(カラーテキスト)でカラー表示ができる。アトリビュート指定はできないっぽい。

COLOR 前景色[,背景色] 0黒、1青、2緑、3水色、4赤、5紫、6橙、7白 +8で明るい色

CLSすると背景色で塗りつぶされる。

・グラフィック
ぎざぎざなグラフィックが使えないとBASICの魅力が半減する。640×480×16色パレットが使える。

スクリーンモード
SCREEN 0 テキスト
SCREEN 11 モノクログラフィック
SCREEN 12 カラーグラフィック


※COLORステートメントとの関連
SCREEN 11では使用不可
SCREEN 12では背景色が指定不可


描画命令
よくあるLINEやCIRCLEの他にもDRAWステートメントが使える。これは引数にテキスト形式でタートルグラフィックスで描画内容を指定できる。

IBM 5550ステーションのサンプルを移植した。

・サウンド
矩形波の音が出ないとBASICの魅力が半減する。固定のビープ音と1chのPCGが使える。PCGはPLAYステートメントで引数にテキストで演奏内容を指定すると同期/非同期で実行される。

例:
・BEEP
・PLAY "MB T172O3L16DEF#GABO4C#L4D"
MB バックグラウンド再生 / MF同期再生
Tnnn テンポ nnn=32~255
On オクターブ n=0~6
Lnn 音価 nn=1~64
A~G 音名 #、+ / -を後に付けると半音上げ/下げ

IBM 5550のサンプルを無理矢理動かしてみた。一応鳴る。

・シリアル通信
BASICは割と初期からデバイスをファイルとして扱う慣習が確立されている。これができるとIPネットワークに頼らずとも外界のデバイスを制御できて応用範囲が圧倒的に広がる。

OPEN “デバイス名:パラメータ” AS #n でデバイスを開くとファイル番号で入出力ができる。

動作サンプル

シリアルをパイプで接続してputtyと通信した。

・マウス入力
マウスも使えると何かと便利だけどIBM BASIC本体では扱えないのでデバイスドライバの支援が必要。DOSでMOUSEドライバを読み込んでおいてからINT 33Hをコールする。

参考
https://github.com/mbirth/gwbasic/blob/master/MOUSE.BAS

100 DEFINT A,B,C,D
110 SCREEN 12:CLS:LOCATE 1,1,0
120 DEF SEG=0
130 MSEG=&H100*PEEK(&H33*4+3)+PEEK(&H33*4+2)
140 MOUSE=&H100*PEEK(&H33*4+1)+PEEK(&H33*4)+2
150 IF MSEG=0 AND (MOUSE-2)=0 THEN 190
160 DEF SEG=MSEG
170 IF PEEK(MOUSE-2)=&HCF THEN 190
180 GOTO 200
190 PRINT "Mouse driver not found.":END
200 AX%=0
210 CALL MOUSE(AX%, BX%, CX%, DX%)
220 IF AX=-1 THEN 240
230 PRINT "Mouse driver not enabled.":END
240 PRINT "Mouse driver status=";BX
250 AX=10:BX=1:CX=2:DX=5
260 CALL MOUSE(AX%, BX%, CX%, DX%)
270 AX=1
280 CALL MOUSE(AX%, BX%, CX%, DX%)
290 AX=3
300 CALL MOUSE(AX%, BX%, CX%, DX%) 
310 LOCATE 2,1,0:PRINT USING "X=###   Y=###   BTN=#";CX,DX,BX
320 IF BX=1 THEN P=INT(RND(1)*16):CIRCLE(CX-10,DX-10),10,P:PAINT(CX-10,DX-10),P
330 IF INKEY$="" THEN 270
340 END
クリックしたところに円を描く。

見直したらなんかまとまりが無い。もうちょっとちゃんと調べよう。

QBasicからMS-C/C++7を呼ぶ。

QBasicとMS-C/C++7をリンクしたいのに、まっとうな方法ではできないっぽい。

関連項目:
QuickBasic4.5とCで遊びたいのに。

かつて「BASICは遅い」と言われていて、I/OとかOh!なんたらとかのマイコン雑誌に掲載された投稿プログラムだとBASICソースの中にDATA文でマシン語のコードが埋め込んで初期化中にPOKE命令でそのマシン語コードを適当なメモリに書き込んで必要なときに呼び出す、という手法が使われていた。この方法だとBASIC処理系以外のアセンブラやリンカが必要無いから手軽だけど、マシン語コード修正の度にDATA文に埋め込みなおすのも面倒だし、8ビットマイコンの絶対アドレスならとにかく、x86だとセグメントアドレスのことも考慮しないといけなくて、できればBASIC以外の部分は単体のマクロアセンブラやCコンパイラを使って書きたい。さらにQBasicの場合インタプリタしかなくてCALL ABSOLUTEで動的にアドレスを指定する必要があって、QuickBasicのようにコンパイラで静的リンクが使えない分、マシン語リンクのハードルが高い。

ここではQBasicより先にCで書いたルーチンをメモリに常駐させてからQBasicを子プロセスとして起動し、呼び出されるルーチンのアドレスは環境変数でQBasicに渡すことにした。

呼び出されるCのコード。FARCALLを想定して/ALでラージモデルを使った。/Gsでスタックチェックは無効にする。
INSTFN.CPP

/*
instfn.cpp
cl /AL /Gs instfn.cpp
*/
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

extern "C" int instfn(int* ptr) {

        // __asm int 3
        int data = *ptr;

        static char msg[]="Called instfn()\r\n$";
        __asm {
                push    ax
                push    dx
                push    ds
                mov     ah,9
                mov     dx,seg msg
                mov     ds,dx
                mov     dx,offset msg
                int     0x21
                pop     ds
                pop     dx
                pop     ax
        };

        return data*2;
}

int main(int argc, char* argv[], char** envp) {
        int ret = 0;
        int i, envnum, envsize, pos;    // カウンタ,現在の環境変数の数(末尾NULL分を含まない),現在の環境変数のサイズ(各末尾のNULを含む),dest用ポインタ

        // 新しくヒープで確保する環境変数のポインタ列と実体
        char** newenvp = NULL;
        char*  newenv  = NULL;

        // 追加する環境変数
        int addnum, addsize;
        char* addenv[] = {"INSTFN=xxxx:xxxx", NULL};
        void* instfnp = instfn;

        // INSTFN環境変数を整形する
        // 子プロセスからinstfn()を呼べるようにアドレスをセットする
        sprintf(addenv[0], "INSTFN=%04lX:%04lX", (((long)instfnp)>>16)&0xffff, ((long)instfnp)&0xffff );

        // 現在の環境変数の個数newenvpとサイズenvsizeを数える
        for(envsize=0, envnum = 0; envp[envnum] != NULL; envnum++) {
                envsize += strlen(envp[envnum])+1;      // +1は末尾のNUL分
        }
        // 追加の環境変数の個数addnumとサイズaddsizeを数える
        for(addsize=0, addnum=0; addenv[addnum] != NULL; addnum++) {
                addsize += strlen(addenv[addnum])+1;    // +1は末尾のNUL分
        }

        // spawn* に渡す環境変数のポインタ列と実体を確保
        newenvp = (char**)malloc((envnum+addnum+1) * sizeof(char*));    // +1は末尾のNULL分
        newenv  = (char*)malloc((envsize+addsize) * sizeof(char));

        // 現在の環境変数をヒープ内にコピー
        for(pos=0, i=0; i<envnum; i++) {
                newenvp[i] = strcpy(newenv+pos, envp[i]);
                pos += strlen(envp[i])+1;       // strcpyは末尾NULをコピーするが、strlenは末尾NULをカウントしない
        }
        // 追加の環境変数をヒープ内にコピー
        for(i=0; i<addnum; i++) {
                newenvp[i+envnum] = strcpy(newenv+pos, addenv[i]);
                pos += strlen(addenv[i])+1;     // strcpyは末尾NULをコピーするが、strlenは末尾NULをカウントしない
        }
        newenvp[envnum+i] = NULL;       // iは追加の環境変数の数+1を指している
        
        // 子プロセス実行
        ret = _spawnvpe(_P_WAIT, argv[1], (char const**)&argv[1], (char const **)newenvp);

        // ヒープを開放
        free(newenvp);
        free(newenv);

        printf("terminate child process (%d)\n", ret);
        return ret;
}

呼び出すQBasicのコード
CALLFN.BAS

DIM INSTFN AS STRING, segm AS LONG, offs AS LONG
DIM dat AS INTEGER

'Get INSTFN entry point from environment vars
INSTFN = ENVIRON$("INSTFN")
IF LEN(INSTFN) <> 9 THEN PRINT "No INSTFN= environment": END

segm = VAL("&h" + LEFT$(INSTFN, 4))
offs = VAL("&h" + RIGHT$(INSTFN, 4))
PRINT "fn="; RIGHT$("0000" + HEX$(segm), 4); ":"; RIGHT$("0000" + HEX$(offs), 4)

dat = &H7890

'Call INSTFN entry point
DEF SEG = segm
CALL absolute(BYVAL dat, offs)

DEF SEG

SYSTEM

コンパイルと実行。※スクリーンショットでは間違っているが日本語モードでは動かないのでQBasic実行前にCHEV USで英語モードにしておく。

:INSTFN.CPPコンパイル
cl /AL /Gs instfn.cpp

:INSTFNを実行して常駐
instfn command.com

:QBasicを実行してCルーチンを呼出すテスト
qbasic /run callfn.bas

Cのコードに埋め込んだDOSコールでメッセージを表示する。Cのコードとリンクすると言いながらCの標準ライブラリが安定して動かなかったから結局アセンブラになってしまった。こんなんだったら最初からアセンブラで組んだほうが良かったかもしれない。

Cソースの// __asm int 3 の部分はコメントアウトするとDEBUGでデバッグするためのブレークポイントになる。この場合はINSTFN.EXEの実行をDEBUG INSTFN.EXE COMMAND.COMとしてデバッガ上で実行すると呼び出されたときにデバッガで停止できる。

テスト用として、CからINSTFNを呼び出すコード。
CALLFN.CPP

/*
cl /AL /Gs callfn.cpp 
*/
#include <stdio.h>
#include <string.h>

#define FNENV   "INSTFN="

// INSTFN=seg:offの文字列から関数ポインタを取り出して呼び出すテスト
extern "C" typedef int(*FN)(int*);       // 呼び出す関数プロトタイプ

int callfn(char** envp) {
        char* pos;
        int i;
        long seg=0, off=0;

        FN fn;
        int dat;

        // envpからINSTFN=を探す
        for(; *envp!=NULL; envp++) {
                if(strncmp(*envp, FNENV, strlen(FNENV))==0) {
                    break;
                };
        }
        if(*envp==NULL) {       // 見つからなかった
                printf("%s environment not found.\n", FNENV);
                return 0;
        }
        pos = *envp+strlen(FNENV);      // 先頭からsscanfするとおかしい。
        sscanf(pos, "%04X:%04X", &seg, &off);
        fn = (FN)((seg << 16) | off);   // 関数ポインタのアドレスを組み立てる
        printf("pos=%s, seg=%04lX, off=%04lX, fn=%lx\n", pos, seg, off, (long)fn);

        // 呼び出し
        dat = 1122;
        dat = (*fn)(&dat);
        printf("ret=%d\n", dat);

        return dat;
}

int main(int argc, char* argv[], char** envp) {

        // 呼び出すテスト
        callfn(envp);

        return 0;
}

Cのコードを呼び出してインラインアセンブラのコードに渡ればあとは何とかなるだろうってことで、今回はこれでおしまい。1980年代ならネタにもなったかもしれないが、時すでに2020年代。

QuickBasic4.5とCで遊びたいのに。

MSDNで入手できるQuickBasic4.5、これで何かしたいと思って、とりあえずMS C/C++7.0とリンクして何かしようと思ったら、何かうまくいかない。

スタックチェックルーチンが重複しているらしい。いろいろやってみたけど解決しないので、アーカイブされている文書をきちんと確認してみたらどうもMS C 6.0の時代からQuickBasic4.5とは互換性が無いらしい。

Microsoft KB Archive/61337 – BetaArchive Wiki

Microsoft Basic Professional Development System (PDS) version 7.00, QuickBasic version 4.50, and earlier versions of Basic are not compatible with Microsoft C Professional Development System version 6.00 or Microsoft QuickC version 2.50 or 2.51. You must obtain Basic PDS 7.10 to be compatible with these versions of C and QuickC.

MSC5.10なんて持ってないし、MS-C/C++7.0とQuickBasic4.5をリンクさせるにはCのルーチンを常駐プログラムにしてコールアドレスをなんとかしてBASICに渡してやるとかしないとダメそう。構想はできるけど面倒そう。

太陽光発電は黄昏どき?

最近の芸人は太陽光発電事業をやっているらしい。

芸能人・有名人が○○で×店舗経営!!みたいのは掃いて捨てるほどあるわけで、それよりは太陽光発電所有でアパートオーナーなんてまだ庶民的だ。

流れ星・瀧上 太陽光パネル1基2000万円を4基保有…東電に売電し高額収入

うがった見方をするなら、2019年度認定済みの物件が20年満期で使える期限が今年いっぱいに迫ってきたから、芸人使ってステマ開始ってところだろう。制度上は2021年度以降は20年の期限が1年ずつ減っていくだけで、すぐ売り止めになるわけではないとしてもパッケージとしては販売価格を年5%ほど減らしていかないと売れなくなるから、地主から土地の販売契約とりつけて土地付き物件売ってる業者にしてみれば旨味が毎年減っていく。ステマしてでも早く売りさばきたいはず。自分は販売業者の内部事情なんてものは知らないけど、現時点で条件の良い登録済み物件はローンが組める人の間で「取り合い」になっているだろうし、2021年末にもなってきたら残り物件は日当たりが悪いとか場所が悪いとかなんらか不満がある「売れ残り」になってくる。

おもしろいのはヤフーのコメント欄。調べもしないで憶測であーだこーだ書いてる人の多いこと。そのコメントは世界中から(日本人しか見ないけど)読むことができるのに、好き勝手書いて無知を晒している。世間一般とはこういうものだ。

デイリーの記事では「1基200万円の高額収入」とか書いてあるだけで、2000万円物件のキャッシュフロー(収支=もうけ)が1基1年30~40万円だとはっきり言ってないあたりがやはり宣伝だなって感じだし、それを知ったとしても2000万円かけてたった30~40万円だけ、と騙されてるみたいに感じる人もいるだろう。ただ30~40万円が20年間休まず入ってくることに意義を見いだせない小学2年生で習う掛け算ができない人は、契約書にサインして年数回の草刈りで手に入れられたはずの600~800万円を失っていることになる。そこまで計算高いなら1000万円が当たるのを夢見て労働賃金を「投資して」サマージャンボと年末ジャンボ宝くじを20年間買い続けるが良い。そして同じ20年間で2000万円物件を4件所有する滝上は3000万円以上の自由にできるお金を手に入れることになる。

ちなみに年2回1万円づつ20年間宝くじを買い続けて、割と当たっていると仮定して30%の回収率だとしたら、合計40万円投入して11万円ほど返ってくる計算。他のことに使えた29万円は夢と消える。

他にもコメントを見てると再生可能エネルギー発電促進賦課金ムカツクみたいな論調も多い。設計時の耐用年数を過ぎた環境に良い原発を酷使して使い続けたいのだろうか。再エネ賦課金が売電した人に転嫁されると知ってるんだったら、さっさと自宅の屋根でも庭でも実質ゼロ円かそのゼロ円よりも安い太陽光パネルを取り付けて売電したら再エネ賦課金に払うよりも良い単価で電気を買ってくれるんだし、くだらないコメントを書くエネルギーがあるなら、それを再生エネルギーを作る知恵にまわすほうがエコ。すべての家庭でできるわけではないけどそんな不公平は世界中どこでも同じ。

スタンド充電器の中を確認。

ICOMのハンディトランシーバー用の急速充電器、スタンド型だから置いて使うときなど便利なのに基本別売でそこそこ高価だからオプション商法などと揶揄される。

現在唯一のハンディ機ICOM IC-T70もそのパターンでこの機種専用のBC-191が入手できなくなる前に買った。

まあ、ごく普通。

アイコム製の機器はこのあたりのカッチリ感がアルインコ製より良い感じ。アルインコのDJ-X7なんかはスタンドチャージャーが附属してるけどなんかグラグラしてて接触不良気味になったりする。

BC-191は本体ときちっと勘合して気持ちがいい。でも値段が高いのに中身が接点だけとか有り得ないし、とりあえず分解してみた。

回路のトレースはしたくないくらいの集積度。ルネサスの8ビットマイコンμPD789112、富士通のレギュレータIC MB3759、JRCのオペアンプNJM2904 なんかの国内メーカーの集積回路が載っている。基板も多層基板で受動パーツ類もしっかりした印象。アマチュア無線機用としてはもったいないくらいの作り。

アイコムのメイド・イン・ジャパンへのこだわりを感じた。