Windows programmings (6) - 対話するプログラム -		2001.February

1. 概要
 対話、と言っても人工知能な話ではありません。そーいえば昔、CP/M-80でmu-Lisp
 で書かれた精神カウンセラーアプリとかありましたな。
 英語が良く分からないんで、適当に答えていたら「真面目にやれ」とかなんとか
 言われてしまった記憶があるけど……。

 それはともかく。実行すると必要なパラメータをユーザに入力するよう求めるタイプ
 のアプリケーション2種です。

 1つはユーザからの入力をじっと待つタイプ
 2つ目は何か他のことも実行しつつ、ユーザの入力も監視するタイプ

 2つ目なプログラムの代表はゲームです。まぁ今更DOS窓用のリアルタイムゲームなんか
 需要はないでしょうが。。。え?rogue(NetHackでも可)があるって?同士よ!
 うちでもそれは生き残ってます!

 てな余談は置いておいて。では行きます。

/* fgets.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define LEN 4 // input buffer size, not include '\0' int main(void) { char str[LEN+1]; // input buffer, include '\0' int l, kbd; fpos_t pos; kbd = fgetpos(stdin, &pos); // check keyboard or redirected file do{ int ch; // EOF is int value, not char printf("Input [max %d]?: ", LEN); // out prompt if ((ch = fgetc(stdin))==EOF) break; // re-direct trap else ungetc(ch, stdin); fgets(str, LEN+1, stdin); // wait for {return} l = strlen(str); if (!kbd) putchar('\n'); // if redirected, print CRLF { // dump buffer - for debug int i; for (i=0; i<=l; i++){ printf("%02X ", str[i]); } putchar('\n'); } if (*(str +l-1) == 0x0A){ // user inputs less than buffer *(str +l-1) = '\0'; // replace 0x0A to '\0' l--; // never count 0x0A as string length } else{ // user inputs greater than buffer, flush input buffer for(;;){ ch = getchar(); if ((ch==0x0A)||(ch==EOF)) break; } } printf("%s, length=%d\n", str, l); } while(atoi(str)!=0); return 0; } /* EOF */
ポイントを箇条書きにすると: 1. 文字列の終端、'\0'の存在にいつも気を付ける 2. {Enter}した時の0x0Aがあったりなかったりする 3. 0x0Aがないってことはバッファ長より入力した文字列の方が長いということなので、   余分な入力バッファ中の文字列は捨てないとまずい 4. gets()ではなくfgets()を使っている 5. キー入力だけではなく、ファイルをリダイレクトされることも考慮してる gets()は使ってはいけない関数なのです。理由は、文字バッファの長さを超えてユーザが 文字列を入力するとプログラム領域を破壊してしまうからです。 ちなみに、ループ終了条件としてatoi()の戻り値を用いてますんで、10進な文字列を 入れてね。 #アルファベティカルな文字列入れたらどないでっか?ってのは、まぁやれば分かり  ます。大丈夫。OSが自爆したりはしませんよ(^^;; #リダイレクトされる場合、最後の行に改行がない。とかいうのにも対応してます。  お試しあれ しっかし。何かこきたねぇソースやのぉ。おまけにこれ、unixとかだと動かないの では?(0x0Aなんかを直接扱っている辺りがとっても妖しい) あ。途中、いきなり'{'と'}'で囲っているブロックが出てきますが(dumpしてるとこ)、 これは正当な手法です。びっくりしてはいけません。誉められるべき手法かどうかは ともかくですが、覚えておいて損はない書き方でしょう。 #しかし。この手のプログラムって、実は書くのはこれが生まれて初めて。対話  するより引数で一気に指定するタイプの方が個人的には好きだから
/* kbhit.c */ #include <conio.h> #if defined(__TURBOC__) // alias definitions for BC++ #define _cprintf cprintf #define _getch getch #define _cputs cputs #define _kbhit kbhit #endif int main(void) { int wait=0, ch; //setbuf(stdout, NULL); // stop buffering do _cprintf("Hit any key [wait for %d times]\r", wait++); while (!_kbhit()); ch = _getch(); switch (ch){ case 0: // Extended key flag (from IBM PC), F1 - F10 // Only BCC, F11, F12, Arrows... comes here! _cputs("\r\nDetect zero"); case 0xE0: // Dummy shift code (from IBM PC/AT), F11, F12, Arrows... _cprintf("\r\nCode is %X\r\n", _getch()); break; default: _cprintf("\r\nYou hit %c!\r\n", ch); } return 0; }
こちらのポイントは: 1. 使用している_c*()関数は非ANSI関数である 2. BC++だけ定義が違うのでコンパイラ別に定義されているシンボルを使って差異を   吸収している 3. 後、BC++だけ挙動が微妙に違いますな。まぁ非ANSIな所なのでこれをBugと言う   のは失礼ってもんでしょうが(処理系依存なので、どうやっても[あるいはやらなく   ても全く問題はない。実際、MacintoshのMPWにconio.hは存在してない]) 4. conio.hな入出力関数とstdio.hな入出力関数を混在して使ってはいけません。   これはバッファリングが非互換だからです。混ぜて使うと非同期に文字が出力   されたりします 5. 改行のやり方がprintf()とかとは違う コンパイラの種別分けにはこんなシンボルも使えます。どれがどれ用かは書かなくとも 明白と思いますがいかがでしょう? _MSC_VER __BORLANDC__ __GNUC__ __LCC__ 1キーで2文字以上返るというのはIBM-PCの仕様です。 (BC++では同じだけど)、F1〜F10までは0x00 + キーコードなのに、F11, F12は 0xE0 + キーコードなのは、昔のIBM-XTってマシンにはF11, F12が無く、後で追加 されたからだ。とかいう歴史的経緯があったりします。 でもそれは何故?と、問われても答えられません。そうなってるからだ。という事です。 私はIBMな人ではありませんので。 #う〜ん。この手のリアルタイム系もCで組んだのは初めてですねぇ。いやぁ、  こういうのを書くと自分の勉強になります(笑) (EOF)