仕事がなくて暇なので、会社で古いC MAGAZINEを漁っていたら、lzh
型式で圧縮・展開をするライブラリを見つけた。が、ライブラリが収録されているはずの付属ディスクが見当たらない。誰かが使ってそれっきりのようで、完全に行方不明である。無念。
でもって、DDJの最新号を読んでいたら、面白い記事を発見。BASICのDRAW文のモトネタは、あのLOGO
らしい。LOGO
とゆ〜と亀を使ってお絵描きといったイメージしかなかったのだが、サンプルを見てみると、なるほど(冗長な)DRAW文である。MSX-BASICのDRAW文は省略が激しくてLOGO
には見えないが、やってる事は全く同じなのであった(Sub setのよ〜な気もするけど)。
WindowsNTのサービスでハマったことについては以前書いた。call back関数にはthisポインタが渡されないから、staticなメンバとして定義するか、Cの関数として書くかの選択をするしかなく、その時はCで書いた。
これは、サービスをラップするクラスのインスタンスが2つ以上生成されるのに備えたつもりだったのだが、VC++のヘルプを読んでいたら同じサービスのインスタンスが2つ以上生成されることはない
という文章を発見。結局、staticメンバとして書いておけば良かったのだが、今となっては書き直す気も起きない。
Perl本を捜しに梅田の紀乃國屋へ。
散々迷った挙句、定番初めてのPerl 2nd Edition
に落ち着いた。CGIだけが目的ならそれに特化した本を買ってもよかったのだが、会社のマシンにWindows用のPerl処理系Active Perl
を入れたので一通りの知識を得られそうな本を選んでみた。日常のテキスト処理(結構色々ある)はC/C++でやるよりPerlの方が楽そうだし。
ついでに面白そうな本を物色していたら、JIS hand book
の最新版を発見。適当に見ていたら、日本語の文字コードの規格JIX X208:1997でShift JISとISO-2022-JPが定義されていた。ISO-2022-JPは国際規格だから収録されて当然と言えば当然(JIS 7bit符号系のsub setだし)だが、Microsoftの私企業規格であるShift JISが入っているのはかなりイヤな感じ。パソコンの地位が上がった事の証明という見方も出来るけど。
Perl本だが、ざっと目を通した感じでは、やたらと多機能で使い易くなったBASICみたいである。今まで簡単なテキスト処理にはMSX-BASICを使ってきたから、心理的には入り易い。また仕事でC/C++を使う身としては、安全なC的使い方もできる辺り、中々に魅力的である。
UU DecodeもUU EncodeもFILE.MACを使う型式に書き換えたらうまく動かなくなった。元のアルゴリズムを変えないよう、注意深くやったつもりだったが、数知れぬ落とし穴が相手ではいくら注意しても足りないもんである。
UUEでは原因が二つ。一つは、ファイル読み込みルーチンが成功した場合、返値をZ,A=0とすべきなのにNZ,A=1になっていた。この返り値はメインルーチン側でフルに使っているので、当然マトモには動いてくれない。
もう一つは、拡張ファイルハンドルの渡し忘れ。FILE.MACではDOS1/DOS2共通のインターフェイスを提供する為に、拡張ファイルハンドルという物を使っているのだが、これを_SEEKに渡し忘れていた。改造前のコードではFCB内のファイルポインタを直接いじっており、ファイルを操作している感覚が薄かった為につい書き足し忘れたらしい。
UUDでは、ディスクバッファをフラッシュするつもりでファイルをクローズするという間抜けをやっていた。FILE.MAC化する前は、ほぼすべてをDOS1ファンクションでこなしていたため、_CLOSE=_FRUSHとして書かれていた(DOS1の場合、両者は同じファンクション)。DOS2ファンクションを使うFILE.MACでは、_CLOSEは本当にファイルを閉じる。この為に拡張ファイルハンドルが無効になり、次のアクセスでエラーが発生していた。
どんなバグではまったか記録しておくのは良い事だと思う今日この頃。とゆ〜か、記憶が持たん。
UU DecodeをFILE.MACを使った型式に書き直していて気付いたのだが、カレントディレクトリを指定するつもりでA:.
などとやっても、カレントがルートになっている場合はうまくいかない。.
はサブディレクトリ自身を指す特別なエントリで、MSX-DOSの場合、ルートディレクトリには存在出来ないらしい(CD-ROMを除く)。
d:.
を使ってカレントディレクトリを取得する手間を省こうとしたのだが、上記の理由により当然うまくいかない。結局時間の無駄であった。不定記のネタにはなったけど。
半月前に申し込んだカードが届いた。
こうなればやることは一つしかない。EsTermでRIM-NETに接続し、オンラインサインアップ。1時間後にはlynxでネットサーフィン(死語?)していた。
が、BabooやG-NET Homepageに繋がらない。なぜかIPが表示され、そこから先に進めないんである。lynxのバージョンが古くて、HTTP1.1の何かの機能に対応していないらしい。
GMLに戻るつもりだったのだが、G-NET Homepageにアクセスできないので参加の仕方が分からない。それでも勘と記憶でなんとか登録できた。
Webの世界に復帰した。
今までも社内のサイトにはアクセスできていたのだが、外部には許可制のプロクシを介してしか出ていけないため、どうしょうもなかった。しかし、既に許可を得ている先輩が自分のマシンをプロクシサーバとして運用しており、これを間に挟めば外部にIPが届く。とゆ〜わけで、今日から存分に利用させて貰うことになった。
って、本当は先週復帰しているはずだったのだが、マシン環境が吹っ飛んで再構築に今日までかかってしまったのであった。
で、早速Baboo BBSを覗いてみたら、先週一瞬復帰した時に書いたBASIC環境におけるPage0へのBlock転送
へのレスが付いていた。LDIR/RETをWRSLTでPage0に書いてCALSLTで呼び出す
という、ただそれだけのアイデアなのだが、意外と思い付かないものらしい。
ちなみに、3FFEH〜3FFFHにLDIR・Page1の4000HにRETを書いて3FFEHをCALSLTすると、Page0全体を転送対象に出来る。もちろんLDIRは消えてしまうのでもう一度やる時にはWRSLTし直さないといけないのだが。
更にLDIR/RETの替わりにJP lmを書けばもうなんでもアリになるが、そこまでやる必要性はないような気がする。
a = (a++ % 8); ネタだが、この式は処理系依存であることに気が付いた。
以下にray−netへの書き込みを引用してみる。
な〜んか違和感あるなぁと思って考えてたら、重大な事に気付きました。
a = (a++ % 8); を a = 1; の場合について考えてみます。
この式を評価するには、まず代入演算子'='の両辺を評価する必要があります。
左辺'a'は、変数aに対する代入可能な左辺値と評価されます。
右辺'(a++ % 8)'は、定数1と評価され、その後にa++が評価されます。
この時点で代入演算の両辺が揃い、a = 1とa++の評価が可能になります。
問題は、Cの評価順序不定という言語仕様によって、a = 1とa++のどちらが先に評価されるかわからないという点にあります。
#評価順序と演算子の優先順位は別物です。念の為。
a=1が先に評価されるのであれば、a = 1; a++; でaの値は2になります。
一方、a++が先に評価されるのであれば、a++; a = 1; でaの値は1になります。
どちらになるかは処理系によって異なります。
例えば、Visual C++ ver5.0ではa = 2になりますが、CINTというC/C++インタプリタではa = 1になります。
これに気付くまですっかり忘れていたのだが、a = (a + 1) % 8; を a++; a %= 8; と書く癖を私につけたのは、この評価順序不定という嫌な仕様なのであった。つまり、評価順序に依存しない式を書けば、この問題には巻き込まれないという考え方を採用したわけだ。この手の問題は、逃げておくのが一番楽だし。
録画しただけで見るのを忘れていたlain
というアニメの第一話を見た。
近未来を舞台が舞台で、ネットやパソコン(Personal Computer!)が重要な役割を持っているらしい。で、主人公が授業を受けているシーンが妙におもしろかった。
プログラミング言語の授業らしく、先生が黒板になにやら書いているのだが、その内容がrintf("%c>%
てな感じでどう見てもC。未来においてもCは健在なんだなぁとか、cout << "Hello, world.¥n";にはなってないのねとか、黒板に書くなんて馬鹿な真似をしないでネットワーク上に同じものを置いておけばいいのにとか、物語とは全然別の事を考えてしまう。
別の場面で主人公の父親がIDらしきものを入力する場面があるのだが、そこで使われていたのがTbink Bule Count One Tow
だった。当然、コードウェイナー・スミスのThink Blue, Count Two
(邦題青を心に、一、二と数えよ
)を意図的にtypoしたものと思われ、モトネタを共有する者(TB.COM参照)としては妙に嬉しかったりするわけだ。それだけなんだけど。
SEVのencoderを作る為にUU Encodeのソースを解析していたら、バグを発見。
HLとDEが一致していれば処理Bを実行するという内容なのだが、不可思議な比較方法を使っていてしかもバグバグである。
LD A,H CP D JR Z,JUMP1 LD A,L CP E JR Z,JUMP1 処理A ;異なる場合 RET JUMP1: 処理B ;一致した場合 RET
これはたぶん、次のようなコードを書くつもりだったのだろう。
LD A,H CP D JR NZ,JUMP1 LD A,L CP E JR NZ,JUMP1 処理B ;一致した場合 RET JUMP1: 処理A ;異なる場合 RET
処理Aと処理Bの書く順番を間違えた事に気付いたが、順番を修正せずに条件の方を修正しようとして混入したバグだと思われるが....間抜けだなぁ。
このバグの発見により、UUE.COMがver1.02になってUUD.COMとうまく並んでくれた。別に意味はないけど。
で、SEV Encoderも2時間半で完成。まだタイムスタンプの処理が終わってないけど、とりあえずSEV型式に変換できる。今回、完成まで無茶苦茶早かったな。
まいちさんのMS-DOS用binary <-> text変換ソフトであるsimple encoder 'SEV'をMSX-DOSに移植してみた。
とりあえずdecoderから始めたのだが、これが解析も含めて3時間ほどでできてしまった。まず、Cのソースにざっと目を通した後、BASICでフォーマットの解析。ソースがあるから解析というよりは確認に近い。
10 DEFINT A-Z 20 FOR I=0 TO 2 30 READ A$ 40 FOR J=0 TO 8 50 C=ASC(MID$(A$,J*8+1,1)) 60 IF C>&H80 THEN C=C-67 ELSE C=C-33 70 FOR K=1 TO 7 80 X=ASC(MID$(A$,J*8+K+1,1)) 90 IF X>&H80 THEN X=X-67 ELSE X=X-33 100 C=(C*2) AND &HFF 110 X=X OR (C AND &H80) 120 PRINT CHR$(X); 130 NEXT 140 NEXT 150 NEXT 160 END 170 DATA !Dャアヲックァ!ィA]カキァャ!イOォ_.+D!ャアヲックァィ!A]カキオャア!ェOォ_.+D!ャアヲックァィ!A]カキァッャ!・Oォ_**P 180 DATA !KAクッキイ、!MAカキオキイ!クッAKP.+!Dャアヲックァ!ィA]ァャオィ!ヲキOォ_**!PKAェィキヲ!コァAKP.+!Dャアヲックァ 190 DATA !ィA]カシカP!カキ、キOォ_!*PKAカキ、!キAKP.+D!ャアヲックァィ!A]ャイOォ_!***PKAク!キャーィAKP!.+Dャアヲッ
170〜190行のDATA文はSEVで変換したテキストを部分的に抜き出したもので、これが元通り表示されれば正しいアルゴリズムであると言える(上のリストは1byteカナが2byteカナに変換されている)。アルゴリズムそのものはray−netの書き込みで分かっていた上に、細かい部分はソースを見ればいいんだから理想的な移植環境である。
で、上の方法でいける事が分かったので、アセンブラ版に取り掛かる。今回は一から作る事はせずに、UU Decodeのソースを流用する事にした。"begin"を"SEV"に置き換えて、変換部分を書き直せば終わるだろうという目論見である。これが大正解で、3時間程で書き終わった上に、一発で正常動作するという珍事が待っていた。
次はencoderだが、元とすべきUU Encodeが妙な事をやっているので、decoderよりは手間取りそうである。とゆ〜か、まずUU Encodeを解析しないとどうにもならなかったりするわけだ。
ray−netでCのネタがあったので、ちょっと考えてみた。
元は、BASICでいうところの A=(A+1) MOD 8 をCでどう書くかというネタで、a = a++ % 8だとうまくいかなくて、a = ++a % 8 なら意図通りの動作をするというもの。もちろん、++演算子の評価順序を知っていれば原因はすぐに解る。
さて、これを自分で書く場合はどうするか? 仕事なら a++; a %= 8; と書くだろう(とゆ〜か書いた記憶がある)が、趣味でやる場合は記述に凝っても文句を言う人間はいない。とりあえず a が二回出てくるのが嫌なので ++a %= 8; というのを考えたのだが、LSI-Cでは通らないらしい。Visual C++ ver5.0で試して見ると、C++モードでは意図した通りの動作をするが、Cモードではエラーが出た。
プログラミング言語C++
やARMの日本語版を見た限りでは、C++における前置++演算子の結果は左辺値(後置は値)になるので、C++モードでコンパイルできるのは納得できる。とゆ〜か、代入を伴う演算子はオブジェクトの参照を返すように定義されるはずだから、予想はついていた。
が、Cでは値そのものが返るだけで、左辺値にはならないらしい。なんでこんな仕様になってるのか知らないが、今まで ++a %= 8; 的記述を見たことがなかった理由がなんとなく理解できた。
Windowsで、DLLからDialog boxを出そうとして苦戦。
EXEからDialog boxを出すのは散々やったけど、今回DLLで試してみたら出ない。それどころか、いきなりナントカ違反でプロセスごと落ちる。DLLではWindowを開けないのかと思って、試しにMessage Boxを使ってみたらこっちは問題無く出てくる(当然)。
しばらく考え込んで、どうもDLL内に配置されるDialog boxのリソースを読めていないんじゃないかとの結論に達した。その線でVC++のヘルプを当たって見ると、DLLのインスタンス(ロードされたアドレス)を得る関数を発見。リソースを捜しにいくのに、DLLじゃなくてEXEを調べてるんだろ〜な〜と思ったのだが、これが甘かった。なぜかインスタンスをDLLのものに切り替えても警告は出続ける。
更に考え込むが、この方向で間違っているとは思えない。で、しばらく適当にファイルを眺めて気が付いた。DLLは一つじゃない。実はこのプログラム、MFCの共有ライブラリ(つまりDLL)を使っている。前述のインスタンス取得関数は、このMFC DLLのインスタンスを返してくれるのであった。
てなわけで、MFCのヘルプをたどっていくと、MFC DLLとユーザーDLLのインスタンスを切り替えるマクロを発見。無事に動くようになったのだが、この為に費やした時間を考えると、Windowsを使いたくなくなるのは事実だったりする。と、己の未熟さ故の過ちをOSのせいにしたくなるほど面倒な作業だったんである。