+===========+ |P/ECE研究室| +===========+ P/ECE研究記録 2001年 ==================== * Fri Dec 28 21:31:41 JST 2001 Naoyuki Sawa (29日更新分) - 「送る」と「アプリケーションから開く」の違い 今回は、P/ECEではなく、Windowsの話題です。 mini ISDのバグがひとつ取れました。 右クリックして「送る」からの書き込みならちゃんと動くのに、 「アプリケーションから開く」からの書き込みだとエラーになっていた原因。 それは、 ・「送る」の場合はファイル名は"〜"で囲まれないが ・「アプリケーションから開く」だと"〜"で囲まれる というものでした。 例えば、「c:\usr\piece\app\pex\tank.pfs」を書き込む場合、「送る」なら minisd.exe c:\usr\piece\app\pex\tank.pfs と起動されます。これに対し、「アプリケーションから開く」を使った場合は minisd.exe "c:\usr\piece\app\pex\tank.pfs" と起動されます。 拡張子関連付けの場合も、「アプリケーションから開く」の場合と同様です。 これって、こういう仕様なのでしょうか?僕は、知りませんでした。 「アプリケーションから開く」の形式に対応する方法としては、 "〜"で囲まれたファイル名をきちんと解析すべきなのですが、 とりあえず、ファイル名の切り分け文字として、空白文字に加えて ダブルクオーテーションも含めてしまいました^^; * Thu Dec 27 22:39:04 JST 2001 Naoyuki Sawa - 極小フォント正式記載 またまたP/ECEのアップデートです。もはや絶句。恐るべきパワーだ... さて、以前の話題にも取り上げましたpceFontSetType(2)の極小フォントが、 APIリファレンスに記載されていました。これで安心して使えますね。 そして...猪名川で売ろう!がリリースされました。 アクアプラスさん、目玉ゲームなのに、アナウンスが地味すぎです^^; トップページのP/ECEの画面にバーン!と貼り込むぐらいやんないと。 * Thu Dec 27 07:46:43 JST 2001 Naoyuki Sawa - 逐次処理プログラム#2 細胞分裂アオミドロンのバージョン0.01を公開しました。 ぜんぜん未完成でお恥ずかしい限りですが、 実はもっとお恥ずかしいのはソースコードなのです! 以前、逐次処理プログラムのスケルトンをご紹介しましたが、 このゲームはまさにそれを使って書いてあります。 main()から始まってべた〜っと流れる、 P/ECEにあるまじき最悪の設計をご堪能ください。 * Tue Dec 25 05:44:09 JST 2001 Naoyuki Sawa - PCEWAVEINFOはauto変数にしてはいけない なにをいまさら...って感じですが、僕は今日気がついたので^^;書きます。 ADPCM再生を簡単に行うため、次のような関数を用意したとしましょう。 void sound_play(int ch, const void* data) { PCEWAVEINFO winfo; winfo = *(PCEWAVEINFO*)((char*)data + 8); winfo.pData = (char*)data + 8 + sizeof(PCEWAVEINFO); pceWaveDataOut(ch, &winfo); } しかしこのプログラムは、遅かれ早かれ、異常動作を起こします。 その理由は、サウンド再生完了時に、winfo.statにPW_STAT_ENDが 書き込まれるからです。実際には、サウンド再生完了時にはとっくに sound_play()関数からリターンしていて、winfoの実体はありません。 そのアドレスには、別の関数のローカル変数などが存在するでしょう。 つまり、別の変数にいきなりPW_STAT_ENDが書き込まれることになり、 プログラムは異常動作を起こしてしまうのです。 sound_play()関数は、次のように書き換えると正しく動きます。 void sound_play(int ch, const void* data) { static PCEWAVEINFO winfo[4]; winfo[ch] = *(PCEWAVEINFO*)((char*)data + 8); winfo[ch].pData = (char*)data + 8 + sizeof(PCEWAVEINFO); pceWaveDataOut(ch, &winfo[ch]); } 再生チャンネルごとにPCEWAVEINFOを分ける必要はないのですが、気分の問題です。 より正確には、開発キット付属のadpcm/hello.cをご覧下さい。 そう、付属のadpcm/hello.cサンプルコードには、解答が載っていたのです。 にもかかわらず、僕がこの件でハマってしまった原因は、APIリファレンスにあります。 P/ECE APIリファレンスには、次のように記述されています。 > ver1.07 以降は PW_TYPE_CONT を指定しない場合に限り > *pwave は即座に解放されます。 これでは、PCEWAVEINFOはスタック上に取れると思ってしまいますよね?ね? そもそも、再生終了時にPW_STAT_ENDを書き込んでくれなくて結構です。 ちょっとぐちっぽくなってしまいました。ごめんなさい。 メリークリスマス! * Sun Dec 23 07:24:23 JST 2001 Naoyuki Sawa - memset()の引数にご用心 P/ECE付属のコンパイラで、次のようなコードをコンパイルしてみましょう。 memset(vbuf, sizeof vbuf); これ、明らかに memset(vbuf, 0, sizeof vbuf); の書き間違いなのですが、しかしエラーなくコンパイルできてしまいます。 もちろん、正しく動作しません。 なぜ、こうなるか。 memset()はstring.hで宣言されているのですが、 string.hでの関数宣言形式が、古い形式で書かれているからです。 つまり、 void* memset(void* s, int c, size_t n); ではなく、 char* memset(); と宣言されています。 C言語はC++言語と違い、引数リストが空で宣言されている関数は、 実際の定義や使用時に、どんな引数を指定してもエラーになりません。 この場合がまさにそれに当ります。 同じくstring.hで宣言されているstrcpy()やstrlen()など多数の関数、 time.hで宣言されている関数も、どんな引数を指定しても コンパイルエラーにならないので、注意が必要です。 特にstring.hで宣言されている関数は、多用するものばかりなので、 引数を間違う可能性も高く、非常に危険です。 ところでmemset()の宣言、戻り値も違っているようですが、 昔のmemset()はvoid*じゃなくってchar*を返していたのでしょうか? 手元のK&R第2版では、既にvoid*を返すようになっていますが... * Sat Dec 22 01:09:08 JST 2001 Naoyuki Sawa - BIOS ver1.14 今週末もP/ECEのアップデートがありました。 こんな手厚いサポートして、メーカーは大丈夫なんでしょうか? 余計なお世話でしょうが、心配してしまいます。 今回のアップデートで、P/ECEコミュニケータがドラッグ&ドロップに対応した模様。 僕のmini ISDは、用済みとなってしまいました。 あとは、P/ECEからWindowsへのドラッグ&ドロップを実装して差別化するしかないのですが、 アプリケーションからエクスプローラへのドラッグ&ドロップって、やり方知らない...^^; う〜む、ちょっと勉強が必要です。 BIOS ver1.14からBIOS ver1.14へのアップデート(つまり同一バージョンの上書き)時に、 アヤシイ振る舞いがありましたので、トラブルシュート掲示板に報告しました。 P/ECE上のファイルを全て消さないと、同一バージョンの上書きに失敗するというものです。 うちだけの現象ならいいのですが。 * Wed Dec 19 17:18:36 JST 2001 Naoyuki Sawa - __FILE__マクロが正しく展開されない pcc33は、__FILE__マクロを正しく展開しない模様です。 例えば、こんな関数を含むプログラム... const char* test() { return __FILE__; } を、 pcc33 -c sample.c でコンパイルしてみると、 "sample.c"ではなく"sample.$"に展開されます。 pcc33は内部でgcc33を呼び出しているので、 よりピュアなgcc33を直接使って gcc33 -c sample.c としてみると、ちゃんと"sample.c"に展開されます。 たぶん、pcc33が前処理を行った一時ファイル、 sample.$に対してgcc33が呼び出されているのでしょうね。 実際にはgcc33を直接使うことは少ないと思うので、 当面、ランタイムエラーメッセージなどに表示される ファイル名がおかしいという点は我慢せざるを得ないようです。 あ、いま気が付きましたが、前処理を行った後のファイルで __FILE__が展開されているのなら、きっと__LINE__も同じかな? もしそうならば、行番号が変わるのはきびしいなぁ... 要、調査です。 * Sun Dec 16 20:00:58 JST 2001 Naoyuki Sawa - ソルダム/セキーロ そろそろ実際にP/ECEでゲームを作らねば。 まずは研究という名目で、ソルダム/セキーロ(JARECO 1992)で遊ぶ。 遊ぶ遊ぶ。おもしれ〜。 このゲーム、非常にマイナーな部類に入りますが、 ONE OF MY BEST 落ちものパズルゲームなのです。 落ちものパズルというと、右も左も連鎖・連鎖の風潮であった当時、 わが道を行く地道で淡々としたゲーム性、 スピーディなソルダムと、じっくり攻略セキーロ、の2モード。 十年近く経った今でも、まったく色あせておりません。 まあ、90%ぐらい曲依存ゲーなのは否めませんが... 皆様も街中で見かけたら、ぜひ遊んでみることをお勧めします。 まず見かけないけど^^; さて、もう1ゲーム行きますか。...はっ!僕は何をやってるんだ? * Sun Dec 16 09:04:57 JST 2001 Naoyuki Sawa - NULLポインタへの書き込み P/ECEのCPU、S1C33にはメモリ保護機能がないので、 テキトーなアドレスを読み書きしても、その場ですぐに 「アドレス0x12345678がReadになれませんでした。」(by Win2K) というようなエラーは表示されません。 後々、じわじわと効果が(悪い効果ですが)現れてきます。 これではとてもデバッグがやりづらい... むちゃくちゃなアドレスへの書き込み *(char*)0x00000123 = 45; や、NULLポインタの読み出し a = *(char*)NULL を検出する方法はちょっと思いつかないので、まずは NULLポインタへの書き込みだけでも検出することにしましょう。 int save000; void pceAppInit() { save000 = *(int*)NULL; ... } void pceAppProc() { ... if(*(int*)NULL != save000) die("Write address 0"); } ちなみに現在のカーネルでは、0000〜0005の位置には、 NULLポインタ呼び出し検出用のジャンプ命令が置かれています。 このうち0000〜0003を保存しておいて、書き換えられていないか 時々チェックするわけです。 S1C33と同様にメモリ保護機能を持たない、 8086やZ80用のCコンパイラが採っていた方法を参考にしました。 * Fri Dec 14 21:30:49 JST 2001 Naoyuki Sawa - 逐次処理プログラム 僕はズボラーなプログラマなので、main()だけで全部やりたいのです。 もっとgotoを使いたいのです〜! というわけで、逐次処理プログラムのサンプルです。 http://www.piece-me.org/archive/sample-20011214.c オフィシャル開発掲示板にて発言させていただいたところ、 「main()があると安心する」というご意見をいただきました。 そういえば、main()の正しい定義は、 int main(); または int main(int argc, char* argv[]); または int main(int argc, char* argv[], char* envp[]); ですが、上記のサンプルでは void main(); としていました。これはハズカシイので、さっそく治したのがこちら。 http://www.piece-me.org/archive/sample-20011214a.c main()からの戻り値が、pceAppReqExit()への引数になります。 もっとも、現在のところpceAppReqExit()への引数は無視されており、 つまり、全く無意味な修正です^^; あ、別にmain()から制御を返さなくても、 int main() { /* いろいろ処理 */ pceAppReqExit(終了コード); yield(); /* これでおしまい */ /* ここへは来ません */ } これで終了できます。 #define exit(n) pceAppReqExit(n); yield(); と定義しておくと、ちょっといいかもしれません。 * Wed Dec 12 23:10:35 JST 2001 Naoyuki Sawa - 強制リセット(つづき) エラーメッセージを表示して停止する関数の実装例です。 STARTボタンとSELECTボタンを押すと、メニューに戻ります。 void die(const char* fmt, ...) { #ifdef PIECE asm(" xcall pceLCDDispStop ; pceLCDDispStop(); xld.w %r12, _def_vbuff ; pceLCDSetBuffer(_def_vbuff); xcall pceLCDSetBuffer xcall pceLCDDispStart ; pceLCDDispStart(); xld.w %r12, 2 ; pceFontSetType(2); xcall pceFontSetType xld.w %r12, 3 ; pceFontSetTxColor(3); xcall pceFontSetTxColor xld.w %r12, 0 ; pceFontSetBkColor(0); xcall pceFontSetBkColor xld.w %r12, 0 ; pceFontSetPos(0, 0); xld.w %r13, 0 xcall pceFontSetPos xld.w %r4, die_L10 ; pceFontPrintf(fmt, ...); xld.w [%sp], %r4 xjp pceFontPrintf die_L10: xcall pceLCDTrans ; pceFontLCDTrans(); die_L20: ; do { xcall pcePadGetDirect ; pad = pcePadGetDirect(); xcmp %r10, 0xC0 ; } while(pad != (PAD_C | PAD_D)); jrne die_L20 xld.w %r4, [0xC00000] ; system reset jp %r4 "); #else /*PIECE*/ va_list ap; va_start(ap, fmt); fprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); abort(); #endif /*PIECE*/ } * Tue Dec 11 22:06:04 JST 2001 Naoyuki Sawa - 強制リセット SELECT+STARTの0.5秒長押しでかかるリセットは、 実はリセットではありません。 カーネルがSELECT+STARTの0.5秒長押しを検出したら、 アプリケーションの代わりにpceAppExit()を呼んでいるだけです。 つまり、全く普通のアプリケーション終了手続きとなります。 アプリケーション内部で予期しないエラーを検出した場合など、 強制リセットをかけたいことがあります。 APIにはそのような機能がないようなので、 直接リセットベクタへ飛ぶことにしました。 asm("xld.w %r4, [0xC00000]"); asm("jp %r4"); ただし、これでは周辺回路がリセットされないので、 多少危険なのですが、いまのところちゃんと動いています。 P/ECEカーネルは周辺回路のコールドリセット状態を仮定せず、 きちんと再初期化してくれているみたいです。 * Tue Dec 11 21:53:29 JST 2001 Naoyuki Sawa - アイテムフルコンプリートです ゆうべ、アイテムフルコンプリートしました。 これのどこがおもしろかったのか、じぶんでもわからないのですが、 ともかく、なぜか、とても、とても、たのしいゲームでした。 ぼくも、こんなたのしいゲームがつくれるプログラマに なりたいです。 * Mon Dec 10 12:25:03 JST 2001 Naoyuki Sawa - 極小フォント pceFontSetTypeの引数は、マニュアルには、 0: 5x10 通常フォント (日本語あり) 1: 8x16 拡大フォント (日本語なし) しか載っていないが、 2: 4x 6 極小フォント (日本語なし) もある。 TRAP画面でレジスタダンプに使うためのフォントのようだが、 ユーザーアプリケーションでも普通に使える。使おう。 ただし日本語はない(半角カナもない。これはタイプ1も同様)。 スコア表示などにちょうど良いかもしれない。 * Sun Dec 9 02:18:55 JST 2001 Naoyuki Sawa - 空白を含むファイル名は削除できいない 書き込みはできる(ismPFFSWrite)が、削除できない(ismPFFSDelete)。 システムアップデートで無理やり消すしかなくなる。危険! * Sat Dec 8 19:52:24 JST 2001 Naoyuki Sawa - APIリファレンス誤植 pclSpriteBGClearの説明で、 「bg1のプレーン(擬似コンソールとしての文字表示用プレーン)の、 コンソール範囲のキャラクタをクリアします。」とあるが、 bg0の間違いではないか。(1.12a) * Sat Dec 8 19:42:43 JST 2001 Naoyuki Sawa - stddef.hはインクルードしない stddef.hをインクルードすると、wchar_tの多重定義エラーになる。 原因不明。 そういえば、S5U1C33000C_J.pdfの4ページの一覧にも含まれていない。 あまり重要なヘッダファイルではないようなので、外して様子見。 * Sat Dec 8 19:35:14 JST 2001 Naoyuki Sawa - 便利なシステム定数 DISP_X = 128 画面の横ピクセル数 DISP_Y = 88 画面の縦ピクセル数 BG_CX = 32 BGの横キャラ数 BG_CY = 32 BGの縦キャラ数 * Sat Dec 8 18:50:03 JST 2001 Naoyuki Sawa - va_start()について stdarg.hをインクルードしただけでは、_BOUNDARYが未定義になる。 smcvals.hをインクルードしなければいけない。 S1C33アーキテクチャ依存のビット数などが定義されているようだ。 * Sat Dec 8 18:50:43 JST 2001 - errnoが未定義になる ユーザープログラム中で定義しなければいけない。 BlackWingsやタンクバトルのソースでも、そうやってる。 S1C33 Family C Compiler Package の仕様だ。 S5U1C33000C_J.pdfの104ページを参照。 ただし、errnoを変更する可能性のある標準関数を使わなければ、 (例えば、P/ECE専用ライブラリ関数だけを使っていれば) そもそもerrnoが必要でないので、未定義エラーにはならない。