第A1回 Windows2000をゲットせよ(VirtualProtectは死のかほり) 00/02/20

ようやく復活です。東京に来ていきなり監禁されてました。9日で済んでよかったです。これが9年だったらシャレなりませんからねぇ〜。(なんのこっちゃ…) 一応、こっち来て、シナリオ書きとしてプロデビューしました。いまのまま順調に行けば、私がシナリオを書いたゲームソフトが7月ぐらいにデジキューブを通してコンビニに並びます。(笑) うーん。恥ずかしいなぁ。そんなわけで、しばらく、執筆活動(そんないいもんじゃあないけどさ)に励みます。

そういや、いまだyaneuraoGameSDKが私の飯の種だと思っている人がいてるのですが、あんなしょーもないプログラムで飯が食っていけるんなら世話ないですよ!この業界、そんな甘くはありましぇん。あれは、某ゲームを作ることになって急ぎでプログラムしただけなのよー。美しくも正しくもない。おまけに、「俺様使ってやってんだから俺様の言ったバグなおして俺様の希望どおりの機能つけろよ」みたいなメールはもう勘弁して欲しいのです(笑) そういう人に限って、プログラミングがてんで素人だから、バグレポなんか、バグレポになってなかったりするの。親切な人は、現象を再現できる5行ぐらいのソースとSDKのここをこう直すといいという指摘まで書いてきてあるのに、その人ったら汚いコーディングのインデントもめちゃめちゃの何百行にものぼるソースをいきなり送りつけてきて、「なんか知らんけど動かん〜。お前なおせ〜。なおさんかい〜」だとか(笑) いくら仏のやねうらお(誰が仏や…)だって、怒りますよ。

さて、やねげではネイティブコードを生成後、VirtualProtectで実行属性を与え、そいつを実行するような処理にしていたのですが、こいつの動作がどうもおかしいのです。Windows2000RC3ではこれを使っているとプロセスの終了処理がうまくいかなくなることがあるし(おそらくWindows2000RC3側のバグ。RC3には、この他にもプロセスの終了処理が甘いところがある。製品版を買わすための陰謀か?)、Windows98でも、そこのメモリにアクセスするとメモリ保護違反になることがあります。VirtualProtectがページ単位で属性を変更するので、実行属性を解除するときに、それと同じページに存在していた奴が巻き添え食うようです…。よくわからんのでVirtualAllocで実行属性を与えるように変更とりあえず、それはそれで完成。(明日アップします) 昨日買ってきたWindows2000Professionalでも無事動いてます。

その問題のWindows2000ですが、アップグレードには、NTからのバージョンアップと95/98からのアップグレードと2つのパッケージがあって、前者は13,800円(秋葉原/ソフマップ実売価格)ですが、後者は1万ちょっと高い値段で売られています。さてここで問題。雑誌についていたWindows2000RC2からのアップグレードは、どちらのパッケージを買えばいいのでしょうか?

わからなかったので、初心者のふりしてソフマップのねーちゃんに聞きました。「あのーー。わたしですねーー。雑誌についてた、あある・しーー・に、というのをいれたんですけどねー、どうすればいいんでちょうか」と。ソフマップのねーちゃん、答えて曰く「WindowsNTからのバージョンアップを買いなさい、さすれば救われん」と。

ちゅーか、それで誰が95/98からのアップグレード買うんよ?Windows2000RC2なんて500円の雑誌にも付いてたのに…。それで、WindowsNTからのアップグレードを買って帰ることにしました。実験的に、自宅のWindows98マシンにインストールしてみたんですが、なんとすんなりWindows2000になってしまったではありませんか!WindowsNTのCD入れろだとかそういうのも一切なし。すんなりWindows2000です。

えっ。ひょっとして、CDの内容はWindowsNTからのバージョンアップも95/98からのアップグレードも同じなんですか??ライセンスだけの問題なんですか??

うーん。そんなんでいいんかなぁ…。そんなわけで、今日の格言は13,800円でWindows2000をゲットせよ!でした。(なんじゃいな、今日の格言て…)


第A2回 デストラクタはvirtualにせよ(こんなん常識?) 00/02/25

いま、一緒に仕事をさせていただいている原画マンのTさんは、以前、このホームページのOPの女の子を描いてくださった方です。彼の前の職業は、銀行関係。それをどう間違ったか、エロゲーの原画マンへと…なかなか凄い人生を歩んでおられる彼ですが、それだけにとどまりません。

たとえば、彼は高校の読書感想文で、過去に読んで印象に残った本として、イースの本(ファルコムのイースの中に出てくるやつね)を挙げ、ありもしない本のことについて嘘八百書いて、岐阜県の読書感想文コンクールに入選し、学校の朝礼で表彰されたというつわものです。もちろん、感想文のタイトルは、「イースの本と僕」ですよ。校長先生が朝礼で読みあげりゃ、このゲームを知ってる奴ら、全員、驚きますよ。えっ!イースの本て!! Tさんも、大胆なことをしたもんです。まあ、読書感想文でありもしない本を想像して書くというのは、とても斬新な発想である気はしますが、それがイースの本だったら、冗談きつすぎです(笑) しかし、それで入選してしまうんだもんな〜。凄いよな〜。

さて、今回は、内容は簡単です。いまクラスBはクラスAから派生させているとしましょう。

class B : public A {
       ~B() { ... }
};

そして、A* a = new B;と、Bをnewして、それをダウンキャストして使うとしましょう。使い終わったら、delete a;で消します。すると、Bのデストラクタは呼び出されるのでしょうか?というか、呼び出されなくては、困るような気がします。newしているのは、class Bなんですから!

ところが、これは、呼び出されません。その昔、なんかそんな話を聞いたことがあるような気がします。対策としては、基底クラスであるAのデストラクタをvirtualにしておけばよかっただけのはずです。ちゅーか、そのことを思い出すのに1時間ぐらいかかったんですけど(笑) しかし、なんちゅーダサイ言語仕様なんや…。ポイントされていないオブジェクトは、勝手に消えんかい!とは言っても、それはそれで使い道もあるような、ないような…。(どないやっちゅーに)

さて、問題。class Bからさらにclass Cを派生させ、A* a = new C;とダウンキャストして使う場合、delete a;で、Cのデストラクタまで呼び出されるようにするには、基底クラスのデストラクタをvirtualにするだけで良いのか?また、B* b = new C;とダウンキャストして使う場合、delete b;でCのデストラクタまで呼び出されるようにするには、基底クラスのデストラクタをvirtualにするだけで良いのか?

正解は、CMのあとで。


 

いま、作ってるゲームのメインキャラ、冬月志穂。
何のゲームかは秘密(笑)


正解は両方Yesです。

(派生クラスでオーバーライドする関数を宣言するときにもキーワード virtual を使えますが、省略してもかまいません。オーバーライドされた仮想関数は必ず仮想関数になります/VC++6.0オンラインヘルプより)

それでは、また来週〜。


第A3回 コンストラクタでvirtualを呼ぶな(こんなん常識?) 00/02/27

ここに来て、ホームページ更新しまくりです。何せ、一日中やることないもんで。(なんでやること無いねんって言う突っ込みは勘弁してね) 部屋の模様替えで、僕の部屋に例の原画マンのTさんがやってきました。Tさんにフォトショップの使いかた教えてもらたので、Tさんが帰ったあとはTさんのでっかいタブレットでお絵かきしてます。うーん。お金もらって、プロの先生からフォトショップの使いかたを教えてもらって、おまけに高価なタブレットとお絵かき用にメモリ384MBも積んだマシンが使えるなんて最高だね。(ちゅーか、自分の仕事しろよ!>俺)

第9B回で説明したように、C++には、プロパティという概念がありません。C++でconstなプロパティを持たせたいことがあります。たとえば、自分のクラス名を返す関数、

string GetMyName(void) { return "MyApp"; }

というのを用意して、自分のクラス名を返したとしましょう。これをメンバ関数に持つクラスを継承した場合は、この関数をオーバーライドすることにしましょう。

string GetMyName(void) { return "YourApp"; }

このようにして、この返し値を一種のconst propertyと考え、継承先で関数のオーバーライドを利用して、このプロパティを変更するということが出来ます。よって、これを以下では「プロパティ関数」と呼ぶことにします。

それならば、基底クラスのプロパティ関数は、virtualのほうが何かと便利でしょう。基底クラスのメンバ関数からも、自分のクラス名についてGetMyNameして、自分のクラス名を正しく知ることが出来るからです。

そういうプログラムを書いていて、基底クラスのコンストラクタからGetMyNameを呼ぶプログラムをたまたま書いたのですが、それがうまく動かないのです。どうも、基底クラス側のGetMyNameが呼び出されているようです。

んー。これは、C++の言語仕様なのでしょうか?VC++6.0で試したところ、仮想関数テーブルが初期化されるタイミングは、基底クラスのコンストラクタが初期化された後になるようです。すんげーダサイ気がするんですけど、そういうもんなんでしょうか…? またもや30分ぐらい悩まされました。いい加減、オブジェクト指向のかっちょいい言語を作るかも知れません。


第A4回 マルチCD再生に挑戦(してみた:p) 00/02/28

最近、CD−Rがブームなので、CDドライブが二つついてるパソコンが多いです。私のもそうです。この2つにそれぞれCDを入れて、同時再生ってのは出来るんでしょうか?

ドライブ指定はMCIをOPENするときのパラメータに

MCI_OPEN_PARMS open;
open.lpstrDeviceType = "cdaudio";
open.lpstrElementName = strDriveName.c_str();
hr = mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE
|MCI_OPEN_ELEMENT,(DWORD)&open);

ドライブ名を入れておけば出来ます。ドライブ名を指定して、2つのCDドライブを順番に再生すればあらま不思議なんと同時再生が出来てしまいます。(別に不思議じゃないって…) このへん、Windows3.1からWindows95になったときに変更されたようです。(関連項目→Microsoftのページ、Article ID: Q137579「How to Address Multiple CDAudio Devices in Windows NT」

ところで、この音量は、どうやって変えるんでしょうか?「Windows95APIバイブル3」を見る限りでは、

auxSetVolume(m_uDeviceID,dw);

で変わりそうなものなのですが、これでは変わりません。(Windows2000/NTでは。95/98ならこれで変わったような気が…:p)

そもそもCD再生というのは、普通、サウンドカード側のオーディオ入力に接続して、そこでミキシングして再生をします。サウンドカード側が対応していれば、そのミキシングする段階での音量調整は可能になるわけですが、CDドライブ自体がソフト的な音量調整をサポートしていることは、あまり無いです。(ほとんどのCDドライブではハード的なボリュームつまみが前面についているだけです)

ということは、auxSetVolumeで変更出来るはずもなく、オーディオミキサーのプロパティを調整しに行く必要がありそうです。


第A5回 CD再生と同期をとる(ことは出来るのか?) 00/02/29

ゲームとかで、音に合わせたオープニングは、実にかっちょいいです。

音は別段、WAVE再生でも構わないのですが、CDDAで再生させてあるものと同期は取れるのでしょうか?

手元にあるドライブで実験してみました。

まず、MCI_OPEN(CDを回転させる)します。そこからMCI_PLAYで特定のトラックを再生するまでの時間を計ります。最初にヘッドのあったトラックから近接していれば、再生されるまで約0.7秒。

ならば、ひとたびMCI_PLAYで再生させ、その直後にMCI_STOP(PAUSE動作は、CDではサポートされていないのでPAUSEとSTOPは同じ)をかけておきます。これでヘッドのシークに掛かる時間は抑えられます。このあと、MCI_PLAYすれば、再生までに0.2秒。60FPS×0.2秒=12フレーム。無視できる量ではありません。

どのみちドライブごとに個体差があることも考慮に入れると、まずMCI_PLAYで再生を開始させて、そこから同期を取るような処理が必要になります。(これは難しくない。ただMCI_PLAYの実行終了タイミングが再生開始タイミングとぴったり合うのかは疑問。万全を期すためにはCDから再生位置情報を取得し続けるほうが良いかも)

次に、必要なのは、コマ落ちの処理です。無圧縮のものを垂れ流ししているだけならば処理落ちしたときは次のところまでスキップすればそれで済むのですが、前のフレームから差分圧縮をしていたり、前のフレームに描画されていることを前提として前の画像に対して画像処理を行なっていたりすると処理落ち対策は困難です。結局、シーケンスポイントを設けて、その部分で仕切りなおしというような処理が必要だと思われます。

つまり、ある程度以上の処理落ちをしている期間は、内部の画像バッファから画面に転送する処理は省略して、現在の再生すべきポイントに追いつくように努力し、次のシーケンスポイントに差し掛かったらそこにスキップするような動作です。

実際、昨日、その実験をしていたら、うまく動いています。これで、OP/EDはすべてぴったり音に合わせたアニメーション付きというのが可能になります。(やります!)  でも最近はMPEG MOVIEなのかなぁ…(そっちのほうがお手軽だしなぁ…)

まあともかく、私がいまから手がける年内発売の作品に取り入れて行きたいです。6月ごろ発売のタイプゲームと、7月ごろ発売の双子のアダルトゲーム「Twin Love」と、8月ごろ通販専用で売るギャルゲーと、10月ごろ発売のアクションもの「盗撮マニア2」、冬コミで発売予定のもの4本(内容は秘密。しかし冬コミ、力入れすぎかなぁ:p)です。年間生産12本ペース。普通の人の6倍ぐらい働いてると思うんですけど…(笑) しかも、年末にはドリキャスとプレステとワンダースワンの開発が控えてるし…うーん。倒れてそう。こんだけやって年収二千万にも満たなかったら、あたしゃ化けて出てやるよ。(どこにじゃい!)


第A6回 MIDI再生は簡単か?(へっぽこプログラマでごめん!) 00/03/02

MCIを使わずにMIDIファイルを再生するのは非常に面倒です。もちろん、DirectMusicを使えば驚くほど簡単です。いまならば、

http://www.microsoft.com/japan/

のディベロッパーのページのほうで詳しい記事が入手できます。ただし、DirectMusicはDirectX6以降だったと思うんで、DirectX3あたりで動くことを前提にプログラムされている方からすれば、もってのほか!なのでしょう。

MCIで再生するのはお手軽ですし、お勧めです。細かい制御(音量調整・フェード等)には弱いですが…。

またもや、自分で実装してないのに偉そうなことを言うな!とか突っ込みが入ると嫌なので、趣味でMCIを使わずにMIDI再生をするルーチンも書いてます。処理的には、再生タイミングをあらかじめすべて計算して(演奏開始何msの位置でこのメッセージを出力というようなデータを事前に用意しています。STLのvectorを使って実装)おくほうが、その後の実装が容易です。(メモリは多少食いますがWAVEファイルなんかに比べれば微々たるものでしょう)

「MCIを使わずに再生するぐらい簡単なのにどうしてファル○ムは、やらないんだろう…」と某氏がぼやいていましたが、某氏のような一流のプログラマならともかく、やねうらおのようなへっぽこプログラマにとっては、そんなに簡単じゃあないと感じます。調べないといけない資料の数も少なくはないです。あらゆる環境でMCI再生と同じレベルの演奏精度と安定動作を要求されるとしたら、作るのに1ヶ月ぐらい潰れてしまうような気がします。(私には1ヶ月かけても、同じだけのレベルにする自信はありませんが) まあ、とりあえず動くだけのものならば3日もあれば作れるにしても、そこからが大変です。やっぱ敷居高いなーというのが正直な感想です。

さて、今回は、第A3回の内容について、いろいろメールをいただきました。一番わかりやすいAKIOさんの意見を紹介させていただきたいと思います。

ぼくも数回はまったんですが、よく考えるとこうでないと怖くて使えなくなり
ます。

 まず、実装の問題としてvtableの初期化をコンストラクタで行うから、
基本クラスのほうで派生先の仮想関数を呼べないのは当然であるこ
とはおわかりだと思います。ここを工夫して「仮想関数だけ先に初期
化するように仕様変更」することも難しくないでしょう。(ぼくもそのほう
がいいと思ってました。)

 ところが、そうしてしまうと派生クラスの仮想関数が基本クラスのコ
ンストラクタで呼ばれて場合は、そのクラスのコンストラクタが呼ばれ
る前に呼ばれしまいます。RSPの呼びかたがまさにそういう呼び方
です。あとはご推察できると思いますが、初期化されていないメンバ
に仮想関数がアクセスした時点で落ちてしまいます。

#もちろん仮想関数がメンバにアクセスしなければ大丈夫ですけど。

私もあれを書いたあとに気づいたのですが、初期化されていないメンバにアクセスできてしまいますね。これは、確かに危険です。第A3回の例は、派生クラス側の仮想関数は定数プロパティを実現するためのものなので、メンバアクセスはしないから問題ないとも言えますが、やはり良くはないですね。

私の場合、コンストラクタ/デストラクタで何もかもやろうとするプログラミングスタイルを採るので、今回のように、その初期化タイミングには、よく悩まされます。本当は、コンストラクタではあまり何もせず、初期化したいタイミングで、初期化のためのメンバ関数を呼ぶのがC++的には、適切なスタイルだと思います。(MFCとか基本的に2 phase-constructionですしね)

それでも、何もかもコンストラクタで一発で初期化してしまおうとする場合には、ポインタにして

CXObject        m_oXObject;

必要に応じて m_oXObject  =  new CXObject; とやるのが普通ですが、それではすべてポインタになってしまい、解放を明示的に行なわなければなりません。実にうざったいです。このへんJavaとか良く出来てるな〜と思います。

もうちょっと突っ込んでこの問題について考えてみましょう。

C++でも、実践的には、あるフェーズで初期化しなければならないものをすべてをクラスメンバとして持っているクラスを動的に生成して、そのクラスのライフタイムで調整することはあります。たとえば、ウィンドゥを作成した後に、そのHWNDを使って初期化し、ウィンドゥ終了前に、そのデストラクタを呼ばなければならないようなクラスが無数に存在する場合は、ウィンドゥ作成後から消滅前までをライフタイムとして持つようなクラスCAppを作り、そのクラスメンバとしてそれらを用意すれば事足ります。

ところが、そういうコーディングを行なうと、そのCAppのメンバのうち、使う前に初期化が必要なもの、たとえばCSoundというクラスがDirectSoundのインターフェースを参照しているの場合、それはどこで初期化するのでしょうか。もちろん、CSoundを使うならば、勝手にDirectSoundの初期化はやって欲しいです。具体的にはCSoundのコンストラクタで、staticメンバのm_lpDirectSoundのポインタがNULLであれば、そこで初期化してはどうでしょうか。CoCreateInstanceは結構時間がかかるので(Windowsのレジストリ構造が腐っているため)、本来ならばそういった初期化は、まとめて起動直後にやりたいところですが、これはまあ目をつぶるとします。ところが、その解放は、どうやれば自動で出来るのでしょうか?

Soundクラスのコンストラクタとデストラクタで参照カウントをとって、現存するインスタンスが0になった時点で解放することにしても良さそうなものですが、前述の通り、CoCreateInstanceはそれほど速くはないので、ループのなかで、

for (int i = 0; i<10; i++){
   if (xxx) {
         CSound sound;
         sound.LoadWave("xxxxx");
         sound.Play();
   }
}
 

とかやっていると、そこを実行するごとにCoCreateInstanceが実行されるので、すんげー遅いのです。これでは実用になりません。CAppのデストラクタにCSound::m_lpDirectSoundを解放する関数を呼び出すコードを書かないといけないような設計では、非常にダサい気がします。(初期化は自動なのに…) ちゅーか、いまちょっと悩んでいます(笑) 馬鹿者!やねうらおはそんなことを言ってるから、いつまでたっても、クソソフトしか作れないんだ!という方、是非この機会にご指南ください(笑)

今回は、もう一つ取り上げたいメールがあります。第9Fさ〜さんから、さらなるメールをいただいたのです。ここで全文紹介してもいいのですが、長くなってきたので、替わりにさ〜さんのページを紹介させていただきたいと思います。

http://www1.webnik.ne.jp/%7Esanami/codemaster/index.html

アセンブラのテーブル処理が遅いという指摘は興味深いです。以前から私もなんとなく気になっていたので、非常に興味深く読ませていただきました。さ〜さんの、さらなるご活躍を期待しています。


第A7回 お手軽MP3再生の道(ACMでみんなほくほく) 00/03/07

ygs2k/yaneuraoGameSDKの両方、ACMを使って圧縮WAVE(WAVEタグ付きMP3等もここに含まれる)とMP3ファイルの再生に対応しました。(version 1.54)

ACMを使って圧縮WAVEとMP3をデコードし、それをDirectSoundで再生しているプログラムって、他ではあまり見ないので意外と貴重(?)なのかも知れません。技術的な内容について知りたい方は、yaneuraoGameSDK1.54のyaneAcm.h/cppと、yaneSound.cppのLoadWaveFileをご覧になってください。

かつては「MP3の再生ルーチンぐらい自前で書け!」と言う人も多くいましたが、いまやフリーでなければMP3のデコーダは特許料を取られるご時世なので、市販ソフトで使うMP3の再生ルーチンはどうしてもACMを使用しなければならんのです。ACMってのは、Audio Compression Managerのことですが、要はDirectShowをインストールすればそれでMP3再生はOKです。(新しくインストールされるメディアプレイヤにCODECが付属しているようです)市販ソフトでもDirectX6以降対応とかそういったものが増えて来ているので、CODECがインストールされていなければMP3再生が利用できないとしても誰も文句は言わないのかも知れません。

最近は、WAVEとMP3と両方のファイルを用意しておき、セットアップ時にユーザーに選択させるギャルゲーも出てきています。自分のも、次回からそうしようかと思っています。

そう言えば、WindowNT4でDirectX5を動かす件について、さらささんからこんな情報をいただきました。

こんにちは、やねうらおさん。

いつも「スーパープログラマーへの道」読ませてもらってます。
その中で「WindowsNT4でDirectX5を使うことが出来れば・・・」とのことですが、
開発中だったNT5からDirectX5のDLLをコピーしたものがネットに出回っています。
DirectX5以降必須のゲームが動きましたので互換性は問題ないようです。
しかし、Microsoftオフィシャルではないので著作権的には当然問題があります。
が、やはり安定したNTの上でプログラムが組めるメリットは大きいですし、
Windows2000のライセンスを持っていれば多分問題無い(?)と思います。

入手先ですが、3月3日現在
ftp://ftp.nsysu.edu.tw/Hardware/NT_Stuff/DirectX5/NT4DX5.zip
にあるのを確認しました。

それでは。

私もMicrosoft非公式のDirectX5のDLLによって、WindowsNT4上でDirectX5が動いていることはずいぶん前から知っていました。ただ、今となってはWindows2000があるので、そっちで開発しています。MircosoftもどうしてWindowsNT4のサービスパックでDirectX7の機能を提供しないのか不思議なのですが、ひょっとするとWindows2000Professionalとの差別化を計りたいのかも知れませんね。

あと、前回メールを紹介させていただいたAKIOさんから、こんなメールをいただきました。

 これのことで、やねうらおさんはポインタ型への代入を行ってDirectSoundを
保持していますが、こいつ(ら)をテンプレートクラスにして、さらにスタティッ
クポインタリストを生成することで、COMコンポーネントであれば解決します。
 #"完全"自動じゃないんですが・・・。


つまり、

// スタティックポインタ共通クラス
struct CStaticPtrBase {
static std::vector<CStaticPtrBase*> lstStaticPtr;
CStaticPtrBase() // リストに追加
{ lstStaticPtr.push_back(this); }
virtual ~CStaticPtrBase()
{ // リストから削除
std::vector<CStaticPtrBase*>::iterator it;
it = std::find(lstStaticPtr.begin()
, lstStaticPtr.end(), this);
if(it != lstStaticPtr.end()) lstStaticPtr.erase(it);
}
virtual void Release() = 0;
};

// 個別のポインタ型用派生クラス
template < typename PTRTYPE >
class CStaticPtr : public CStaticPtrBase
{
PTRTYPE* m_pI;
public:
CStaticPtr(PTRTYPE* pI) { m_pI = NULL; *this = m_pI; }
virtual ~CStaticPtr() { Release(); }
virtual void Release() { if(m_pI) { m_pI->Release(); m_pI = NULL; } }
CStaticPtr<PTRTYPE>& operator =(PTRTYPE* pI)
{
// スマートポインタにするときは
/* if(pI != m_pI)
{ pI->AddRef(); m_pI->Release(); } */
// します。
m_pI = pI;
return *this;
}
// あとは利便用に・・・
operator PTRTYPE*() { return m_pI; }
PTRTYPE* operator ->() { return m_pI; }
// etc.
}

というのを作っておいて、

static CStaticPtr<IDirectSound> m_lpDirectSound;

とメンバを確保してやれば、全てのスタティックポインタを解放したいときに
CStaticPtrBase::lstStaticPtr 内の要素に対してCStaticPtrBase::Release()を
呼べばいいわけです。
 1箇所(CAppとか?)に「スタティックメンバの解放コード」を書かなければい
けませんが、一個一個書くよりは遥かにスマートですよね。

 ただし基本的には解放順の制御ができないので、"破棄"されたコンポーネント
を別のコンポーネントが参照するとマズいですが、CDirectSoundなどのCOMコン
ポーネントであれば解放=破棄ではないので大丈夫です。

templateで解決するのは、なかなか面白そうですね。

これとは直接関係ないですが、私がC++で欲しいと思っている機能の一つに、親クラスのメンバ参照があります。

class CParent {
    CChild m_c1;
    CChild m_c2;
    set<CChild*> m_child_list;
};

たとえば、CChildクラスはコンストラクタ/デストラクタで、インスタンスのリスト(set<CChild*>)に、自分(this)を登録/削除するとします。このようにすることで、CChildの全インスタンスにアクセスするコードが容易に書けるため、全インスタンスに対して操作する必要がある場合はこのようなテクは必須です。

ところが、そのインスタンスのリストをどこに持つかが問題なのです。普通は、staticなメンバ変数として持ちます。たいていの場合、それで事足りるのですが、staticなメンバ変数は実のところグローバルな(=グローバルスコープの)変数が、単に外部からアクセス出来ないように隠蔽されているに過ぎないので、複数のインスタンスは存在出来ないのです。これでは困ることがあります。(例:それぞれのウィンドゥ(ウィンドゥクラス)は、それぞれのメッセージフックを行なうためのチェインを持つ)

もし、子クラスから何らかの方法で親クラスのポインタを取得できたならば、上のプログラムのような場合、親クラス側で用意したインスタンスのリスト(m_child_list)に、自分を登録/削除していくことが出来ます。

このようなことが言語側の機能としてサポートされていれば、AKIOさんのプログラムも、もっと短くてスマートなものになりそうですが…。


第A8回 リトルエンディアン嘘付かない(8x86で、みんなほくほく) 00/03/11

やねうらおの生活水準はかなり低いです。会社の金でPS2とドラムマニアを買い、プログラム関連の書籍もすべて会社の経費で落とし、相当、裕福な生活をしていると思われがちなやねうらおですが、そんなのは出鱈目です。生活水準はめちゃんこ低いです。近くに吉野家とマクドナルドしかないんで、吉野家とマクドナルドを日替わりで利用しています。一日、一食です。あとは、買いだめしておいたお菓子をぼりぼり食べています。発注したはずの押すだけポット君は、いまだに届かないので、カップラーメンひとつ食べれません。おそらく、押すだけポット君が来た日には電波少年のなすびの懸賞生活のように狂喜乱舞することを、ここにお約束します。(せんでええって…)

本日のトピックは、エンディアンネスです。

上位バイト・下位バイトの順序を俗にエンディアンネスと呼びます。メモリの若い番地に下位バイトが来るのがlittle-endian。その逆がbig-endanです。インテル8x86系は前者、モトローラ680x0系は後者ですね。SwiftのGulliver's Travels(ガリバー旅行記)のなかで小人の国で、卵は太いほうから割るべきだと言うBig-endianと細いほうから割るべきだというLittle-endianとが対立する話に由来するようです。(奥村晴彦「C言語による最新アルゴリズム事典」)

まず、最初に考えていただきたいのは配置はバイト単位で逆転するということです。ガリバー旅行記の話の流れからすると、バイト単位ではなく、ビット単位で逆転してメモリにストアされているのかも知れないんですけど、どうせデータはバイト単位でしかアクセス出来ないので、そのへんの議論にはあまり意味がないことです。(たまに雑誌等でエンディアンネスを、ビット単位で逆順で配置されると説明する記事を見かけますが、エンディアンネス自体、厳密な定義のされているコンピュータ用語でもなさそうなんで、そのへんは突っ込まないようにしましょう(笑))

いまや市場はWindowsとMacOSとの戦いになっていますが、初期のころは、PC-9801とX68000やMACすなわち、8086と68000というCPUアーキテクチャの戦いだったわけです。どちらがプログラムしやすかったかと言えば、もちろん後者です。8086のアセンブラをやったことがある人ならばわかっていただけると思うのですが、64Kの壁やセグメントやら、メモリまわりはとても複雑怪奇です。仲間内でも8086は凄く体積が小さいんで64Kを越しただけで自重崩壊してブラックホールになってしまうのだ!とかわけのわからんことを言いながら、世界最高速のスプライト描画ルーチン(某VGのスプライト描画ルーチンだけ委託された:p)の開発に取り組んでいたのです。その8x86シリーズも、i386になって初めて32ビットアドレス空間がリニアに利用出来るマシンになり、やっと68000と同じ土俵に立てたという気がします。

まあ、懐古趣味はないんで、さっさと技術的な内容について説明します。このエンディアンネスですが、どちらが有利か?ということについては、あまり議論に上らないのが残念です。Big-endianのほうが、自然なような気がしますが、私はひとりのコンパイラ設計者としてLittle-endianのほうが有利だと思います。

たとえば、型変換のコストです。メモリに格納されているWORDのデータをDWORDにキャストするときLittle-endianならば、上位ワードに0を埋めるだけでキャストできます。これに対してBig-endianだと16回のシフトが必要になります。あるいは、メモリ移動が必要になります。つまり、前者は、メモリに対しては、ワードのライト作業だけで完成するのに対し、後者の場合、読み出し→シフト→書き込みという3工程必要になります。(とは言ってもPentiumでもWORDやBYTE単位の読み書きはかなりペナルティが加わります。これはまた別の事情なのでしょう) 8x86と68000のCPUアーキテクチャについて話出すと、専門的になりすぎるので、詳しい議論はここでしません。よかったら考えてみてください。

実際には、BYTE,WORDからの型変換はそれほど行なわないから、そんなの関係ないよ!と言われるかも知れません。まあ、それはそれで事実です(笑)

ところで、64bitCPU向けのWindowsの開発の噂が聞こえてきますが、64bit CPUアーキテクチャだとなおさらDWORDとQWORDを混在させ、この手の型変換は頻繁に行なう必要があるんではないでしょうか?だとすれば、間違いなくそのCPUアーキテクチャはlittle-endianでなければならないと思うのですが、いかがなもんでしょうか。


戻る