不定記

1998年12月 / 1999年1月 / 2月


1999年1月

1999/01/29

untar.comでファイル名を間違えて生成する事があったバグを修正した。ついでに、同名ファイルがあった時の処理を、SkipからRenameに変更。SkipやOver writeと違って、Renameなら間違えてもやり直しが効くからね。
これで全部のバグが無くなったとは思わないけど、とりあえず使えるようになったはず。使えないと私が困る。

で、仕事。
何を誤ったか、阿呆なコードを書いて時間を潰した。

class Base       // 抽象class
{
public:
    Base(void) {/* ..... */ GetVariableStruct(); /* ..... */}
    virtual ‾Base() {}
protected:
    virtual void GetVariableStruct(void)=0;
};

class Derived
{
public:
    Derived(void) {}
    virtual ‾Derived() {}
protected:
    virtual void GetVariableStruct(void) { // ..... };
};

最初、こんな感じのコードを書いたら、リンカにBase::GetVariableStruct(void)の実装が見つからないと怒られた。実際はtemplateが入り混じってややこしい状態だったので、妙だとは思いつつ空の実装を与えて誤魔化したのだが、これが間違いの元。
意図としては、共通の初期化処理を基底classで行い、異なる部分を派生classが実装する事で、コードの再利用を計るというものだったのだが、抽象基底classのconstructorで純粋仮想関数を呼び出すのは無茶だった。

オブジェクトの構築は、基底 → 派生の順で行われる。つまり、Base::Base(void)が呼び出された時点では、オブジェクトのDerivedの部分はまだ初期化されていない。この為、Base::Base(void)はBaseオブジェクトの初期化関数として振舞う。という事は、Base::Base(void)中のGetVariableStruct()呼び出しは、Derived::GetVariableStruct(void)ではなくBase::GetVariableStruct(void)を実行している事になるわけだ。
本来実装が要らないはずのBase::GetVariableStruct(void)で、実装が見つからないとなったのはこれが理由で、それに気付かず空の実装を与えたものだから実に妙な動作をしてくれた。

結局、Base::Base(void)の中身(初期化子を除く)をBase::Initialize(void)というprotectedメンバに移し、Derived::Derived(void)からBase::Initialize(void)を呼び出すようにした。Base::initialize(void)が呼ばれるときにはオブジェクト全体の初期化(の必要な分)が終わっているので、GetVariableStruct()呼び出しはDerived::GetVariableStruct(void)にマップされる。
constructorから仮想関数を呼ぶときは、こ〜ゆ〜間抜けを演じる可能性があるので、十分注意しないと時間を無駄にする事になるという話。よく考えたら、Effective C++に書いてあったねぇ。

1999/01/27

untar.comに機能を追加。
unixのやたらに長いファイル名をMSX-DOSの8.3型式に変換すると、時々同じ名前になってしまう事がある。ver1.00では、そんな時に上書きと無視しか選択出来なかったのだが、今回のver1.01では名前の変更も出来るようになった。

で、いきなりプログラムの話になるのだが、名前の変換にはバッファが必要である。メインルーチンは静的な領域をファイルスコープ内に持てばいいが、サブルーチンがこれをやるとプログラム全体がグチャグチャになってくる(事が多い)。で、サブルーチンでは動的なバッファを確保するようにしてみた。

        xor     a
        ld      h,a
        ld      l,a
        add     hl,sp
        ex      de,hl
        ld      hl,-BUFFER_SIZE
        add     hl,sp
        ld      sp,hl   ;buffer in stack
        push    de
        ; 適当な処理
        ; HL+0〜HL+(BUFFER_SIZE-1) を自由に使える
    ; もちろんスタックも通常通り使える
        pop     hl
        ld      sp,hl   ;release buffer

こうすると、バッファのラベルを考える必要がなくなるという嬉しい副作用もある。ついでに再入可能になるので、割り込みなんかでも使えなくはない。

1999/01/25

ネットワーク・トレース取得処理。 上の行のネットワーク・トレースの部分をShiftJISの1byteカナに書き換えて、秀丸エディターで読み込むと文字化けする。1byteカナをEUCの2bytesコードと間違えるんである。仕事で扱っているコードには1byteカナのコメントが大量に使われており、前記の理由から秀丸は使い物にならないのだが、仕事を離れて考えると拙作Think BlueがちゃんとJIS・EUCへ変換出来るかどうか興味深いところである。

そんなわけで試してみたら、Think Blueはしっかり変換してくれた。1byteカナが先頭に来た場合について、少ない脳味噌を絞った甲斐があったというもんである。あんまり絞りすぎて、判別のアルゴリズムを思い出せなくなってたりするのがアレだが。ソース見ても意味不明だしなぁ。

1999/01/23

S1990VDP.COMの出力結果を解析するプログラムをMSX-BASICで書いていて思ったのが、MSX-BASICのエラー処理機構と、標準C++の例外処理機構はよく似ているという事。
不都合があると例外を投げ(MSX-BASIC:ERROR文/C++:throw文)、それを例外処理ハンドラ(MSX-BASIC:ON ERROR GOTO/C++:catchブロック)が捕捉する。発想としては全く同じである。異なるのは、MSX-BASICが何でも大域的なのに対して、C++は例外処理に至るまできちんと構造化されている点。本質は同じとはいえ、言語の使い勝手という観点から見ると、この違いは大きい。

C++の例外処理機構は、安全なsetjmp()/longjmp()として説明される事が多いけど、使われ方で考えるなら古典的BASICのエラー機構の比喩で説明するのもいいかも知れない。

1999/01/21

VC++6.0続き。
一日使ってみて感じたのは、ヘルプの重さ。VC++5.0からヘルプがHTML化され、異常に重くなったのだが、今回はそれに輪を掛けて重い。
例えば....APIの引数を調べようと思ってF1キーを押す。待つ事7〜8秒。やっとヘルプシステムが起動する。そこから更に数秒待って、ようやく望みの物が表示されるという按配。ヘルプシステムを最初に起動させておけば最初の待ち時間は無くなるが、それでもVC++5.0よりは少しマシといった程度で、VC++4.1の普通の(決して軽くはない)ヘルプに慣れている身にとって移行を考えてしまう代物である。

それはともかく、またしてもClass Viewにバグ発見。名著Writing Solid Codeが泣くぞ。

class Parent
{
//.....
    class Child
    {
    //.....
    };
};

上のような入れ子クラスを作ると、class treeの最初のレベルにChildクラスが現れる。これは明らかにおかしな表示である。同時に、Parentクラスの子クラスとしても表示されるので、ChildクラスとParent::Childが存在している様に見えるという妙な状況。発生条件ははっきりしないんだけど。

1999/01/20

会社にVisual Studio 6.0 Enterprise Editionが届いたので、自分のマシンに入れてみた。
適当にクリックして、ほけ〜っと待っていれば勝手に終わるので楽。が、VC++とMSDNのヘルプを入れただけなのに、600MB以上使っている。既にVC++4.1/5.0が入っているので、全部合わせるとVC++だけで1GB位あるかも知れない。

今まで書いた物を適当にコンパイルしていると、時々見慣れない警告が出るのが有難い。警告で指摘されたものの中にはバグもあったし。
が、ファイルを保存しなくてもClass Viewに反映されるという新機能がうまくいっていない部分がある。何だか知らないが、classの変換演算子(CRegString::operator LPCTSTR()とか)の定義を.hでなく.cppに置くと、Class Viewに大域関数$S1()とか$S2()が表示される(事がある)。開発には支障ないのだが、なんだか気持ち悪い。

一通り試してから、forの初期化文内で宣言した変数のスコープを調べてみた。

    int main(void)
    {
        for(int i = 0; i < 10; ++i)
            NULL;
        return i;
    }

上記のコードは、標準C++に準拠したコンパイラに通すと、return i;で変数未定義エラーになるはずである(int iのスコープはforブロックに留まる)。しかし、VC++6.0では何事もなくコンパイルできてしまう(for(int i = 0;〜はint i; for(i = 0;〜と同じ)。Microsoftは、標準(と言語の使い勝手)より過去のコードを選んだらしい。

1999/01/19

untar.comが完成。
例によって三日でできた。最近は、三日で終わらないと、終わらせられなくなっているような気がしなくもなし。そんなわけで、unixの標準アーカイバtarで作られたファイルをMSXで展開します。

でもSymbolic Linkへの対応はいいかげん。MSX-DOSのファイルシステムじゃ、どうしょうもないです。

1999/01/18

CreateDesktop()で新しいDesktopに表示する(そのままじゃ見えないけど)様にしても、アクセス違反は出るのでありました。
そんなわけで、やっぱり再設計。これは私の担当じゃない(し、新人にやらせる仕事でもない)。
CreateDesktopネタ()がうまくいっていれば、SwitchDesktop()で新しいDesktopに切り替えて、ちゃんと画面も出す予定だったのだが。とゆ〜か、画面が出ないと今回の調査の意味がないし。

ちなみに、LogonUser()に渡すパスワードの問題も、簡単で安全な解を考えていたんだけど、もはや意味無し。まぁ、今回の件でNT4.0のUser Interface周りの知識が手に入ったので、個人的には悪くなかったけど、会社員としては....。

1999/01/16

久しぶりに日本橋へ。
アルキ電子商会という小さなジャンク屋で、GIRLY BLOCKというMSX用ROMを買う。THE LINKSマーク付きで300円。他に、テープの野球ゲーム(セ・リーグ篇とか書いてある)を見つけたけど、いまいち食指が動かなかった。

プログラミング言語C++ 第3版を二宮で購入。税込み7350円。高い様だが、ページ当たりの値段はプログラミング言語C 第2版より安くなっている(紙も薄い)。もっとも、この手の本が厚くなるのは喜べる話ではないが。
ちなみに、hello, worldへたどり着くまで、プログラミング言語C 第2版では7ページ読めば済むが、プログラミング言語C++ 第3版では50ページ近くを読破しなければならない。でも、第2版に比べると、だいぶ解り易くなっているような気がするので、最初からこれを読んで勉強するのも悪くないと思う。

1999/01/14

LogonUser()とCreateProcessAsUser()だけでは、やはり駄目。
そんなわけで更なる抜け道を考えてるんだけど、上記の関数に加えてCreateDesktop()を組み合わせれば、或いはうまくいくかも知れないという希望が出て来た。勘が教えるところの成功確率は25%くらいだけど。

まぁ、もしこれをクリアできたしても、まだ問題(LogonUser()に渡すパスワードの暗号化)が残されているわけで、前途多難な今日この頃。でも、パズルを解いてるみたいで楽しいね。

1999/01/12

久しぶりに残業した。11月の上旬に1時間だけ残ったのが最後だったから、実に2ヶ月ぶりである。
それというのも、ポシャったはずのNT Serviceネタが復活した為である。あのとき、現在の設計のままサービス化するのは不可、やるなら根本から再設計の必要あり、という結論になったのだが、最近になって再設計せずに済ます方法(とゆ〜か可能性)をちょっと思い付いたところに、営業の方からは再設計してでも対応しろと言ってきた。

そんなわけで、本当に現在の設計でなんとか出来るのか調査しているわけだが、締切りがきつい。話を聞いたのが11日の午後で、14日までに結論を出せというんである。そんなスケジュールでやるのは厳しいので、22日までに延ばしてもらったが、本当に結論が出せるのか、怪しいもんである。

ちなみに、LogonUser()とCreateProcessAsUser()を使って対応するつもりなんだけど、うまく行く可能性は40%くらいである。文章力不足でちゃんと表現出来ないんだけど、プログラマとしての勘が、たぶん駄目だぞ〜と言っているので。

1999/01/08

去年の夏に書いたネタを蒸し返すのもなんだが、C++(と多分C)ではいくらでも予約語を連ねられる事が判明。
ちょっと用があってMFCのヘッダを眺めていたら、#define new DEBUG_NEWなる記述を発見。これが何を意味するかと言えば、プリプロセッサがソース内のnewキーワードを全てDEBUG_NEWに置き換えてしまうという事である。つまり、予約語を別の何か置き換えられるわけだ。

こうなると、例えば#define ifといった具合に予約語を消去するようプリプロセッサに指示すれば、if return for whileみたいな無意味なコードがいくらでも書ける。そんなわけで、あのネタは無効。つまらん終わり方だね。

1999/01/06

以前template化された継承は使い道がないと書いた。が、前言撤回。少しだけ使えるかも知れない。
例えば、ある関数を仮想にすべきか悩んでいたとしよう。そんな場合に、仮想にするか否かをclassを定義した後で決められる機構として、template化された継承は有効である。
具体的にはこんな感じ(↓)。

class Base
{
public:
    Base(void);
    virtual ‾Base();
    function();         // 仮想にすべきか否か
};

      ↓

template <class T> class BaseT : public T
{
public:
    BaseT(void);
    virtual ‾BaseT();
    function(void);     // 仮想にすべきか否かはclass Tによって決定
};

class SBase             // function()は静的のまま
{
};

class VBase             // function()を仮想にする
{
public:
    virtual function(void);
};

typedef BaseT<SBase> BaseStatic;   // function()は非仮想(静的)
typedef BaseT<VBase> BaseVirtual;  // function()は仮想(動的)

こ〜ゆ〜のが必要な場面というのはかなり限定されていて、よほど効率が重要でなければ関数は仮想にしておくべきである。
しかし、Interfaceの後付けといい、仮想関数化の決定遅延といい、後ろ向きな効果しかないのかね、この手法は。まぁ、設計に融通を利かせるための手法だから、後ろ向きになるのはやむを得ないか。

1999/01/04

借りて来たSEGA SATURNのゲームで少し遊んでみた。
まずはVirtua Fighter2から。VS COMで30分ほどやってたら、左手の親指が痛くなった。Joyballと同じ具合に力を入れてしまう為に、変な負担が掛かってるらしい。やはり、アクション系はJoyballがないと遊べない。
そんなわけで、SEGA SATURN版Joyballが出るまでVirtua Fighter2は封印。

次、スーパーリアル麻雀 PV。でも全然リアルじゃないぞ、このゲーム。テンパれば大抵上がれるし、やたらと染め易いし。脱ぎ麻雀にリアルさを求めても仕方がないのかも知らんけど。
この麻雀に慣れちゃうと確実に弱くなるような気がしたので、こいつも封印。なんだかなぁ。

1999/01/03

帰省ついでに大学時代の友人の家に遊びに行った。
実際のところは友人所有のAptivaにFreeBSDを入れる為に行ったわけだが、これが中々楽しい。AT互換機素人でunixど素人の私がやるんだから楽というわけではないのだが、トラブルを一つ一つ解決していくのが面白いんである。なんとゆ〜か手頃な難しさで、倉庫番の30面辺りまでに楽しい苦しさがあるのと似ている。

そんなわけでCD-ROM driveが認識されない〜ってところから、X Windows Systemが立ち上がってNetscape Navigatorが一応動くようになるまで持っていくのに約6時間。ハード素人のOSど素人にしては悪くないような気がしなくもない。

ついでにSEGA SUTURNのゲームをいくつか借りて来た。面白かったら自分の分も買う予定。


1998年12月 / 1999年1月 / 2月

Web master: Nakayama Atsushi
E-mail: anaka@yo.rim.or.jp