MASATOの開発日記 2001年3月


3/14

圧縮を行うstreamに似たクラスZを作ってみようと思いました。
ファイル出力を行うstreamに被せるようにしてクラスZを被せ、 クラスZに対して出力すれば圧縮されたデーターがファイルに出力されるという 仕組みです。こう言う時は、streamに出力するstreamクラスを派生させて、 その派生クラスで圧縮処理をする、というのが普通の方法だと思いますが、 C++標準ライブラリにも、MFCにも、streamに出力するためのstreamというものが無い! JavaにはFilterInput(Output)Streamというそのためのクラスがあるのですが・・・ (というかJavaの入出力関連クラスはまず数の多さでドキュメントを見た人を圧倒します) そこで自作となるのですが、まあたいして大変なものでもないので、これは簡単に作れそうです。 ですが、MFCではこういったstreamにあたるクラスがCFileとCArchiveの2つもあります。
streamに出力するためのstreamと言ってもそのstream自体が2種類あるわけで どちらをどう使ったら良いのか?という問題が発生しました。
まずはCArchiveに出力するCArchiveを作ろうとしてみたら、 CArchiveにはvirtual関数が無いのです。ReadもWriteも非virtualです。 これでは派生させてもどーにもなりません。
ライブラリとして公開しているんですから派生させて使うことも 少しは考えて設計して欲しいものです。 まあ愚痴を言っていてもしょうがないので、 CFileを派生させることにしました。こちらはReadもWriteもvirtualです。 では出力先をどうするか?CFileにするか?それともCArchiveにするか? 両方出力できるようにするというのも良いですが、なんかスマートではありません。 CFileにしか出力できないようにすると、Serialize関数等で、 CArchiveしか与えられなかった場合には使えなくなってしまいます。 (CArchive::GetFile()で出来ない事は無いでしょうが好ましくないと思いました。)
というわけで、CArchiveに出力するようにしました。 これならば出力先がCFileであっても、CArchiveを被せれば使えます。 というわけで、CArchiveに出力するCFileクラス、というものを圧縮クラスZの基底クラスと することにしました。これでうまく出来たかどうかはまた後日・・・

(Visual C++)実行可能なデーターを出力する方法(その1)。

LHAやCABのツールは、自己解凍形式のファイルを出力する事が出来ます。 実行できるデーターを出力するようなツールは他にも色々あるでしょう。 今回紹介するのは、このような「実行できるデーター」を出力する方法です。

前もって言っておきますが、今回紹介する方法が正しいという保証はどこにも ありません。LHAやCABが同じ方法で自己解凍形式のファイルを出力しているかどうかも分かりません。 ですが、この方法で作った自己実行形式のファイルは、いくつかの環境で無事に実行でき、 AntiVirusソフトにもVirusとはみなされておりません。 そこらへんを承知した上、使う方は覚悟を持って使うようお願い致します。

さて、概要ですが、実行ファイルを前もって用意しておき、 (ビルドしてexeの形にする。) リソースかどこかに保存しておいて、いざ出力!という時に 実行ファイルを出力し、データーを追加で書きこみするという方法をとります。
実行ファイルをリソースに置いて扱う方法は、 こちらが参考になるかもしれません。
(リソースにおかなくても、exeファイルのままどこかに置いておいてもかまいません。

実行ファイル作成の手順ですが、 まず最初に、対象ファイルに、用意しておいた実行ファイルをそのまま出力します。
次に、データーを出力します。データーの先頭には、 データー識別子(正しいデーターか認識できるように)を置いておきましょう。
最後に、書きこんだデーターのサイズを書きこみましょう。
これで完成です。 肝心の前もって用意しておく実行ファイルの内容ですが、それについては次回説明します。

実のところ、APIのUpdateResourceが使えれば簡単そうなのですが、 これはWindows95系は未サポートとMSDNにあります。(NT系はOK) 私はNT系のOSは持ってませんので、これではちょっと使えないという事で もっと原始的な方法を取る事にしました。

3/8

とあるクラスAを実装した。しばらく後にクラスBを実装しようとしたら、 クラスAとの共通点が結構ある。これをくくり出してクラスXを実装し、 クラスAとBをXの派生クラスとした。

という事をよくやる。本当はクラスAを実装する前にクラスXを実装するべきなのだが、 Aを作っている頃はそこまで頭が回らない。 こういう時、どの位共通点があったらくくり出し作業をするべきなのだろうか? 変数1つと関数1つならば?2つならば?今は1つだが今後増える可能性があるときは? というわけでいつも悩みます。
AとXを分けると、今後別のプロジェクトでAを使用するとき、Xまで使わなくてはならない。 つまり、クラスはそのクラスだけで完結させたいという思いがある。 しかし、AとXを分ければ、今作成中のBはもちろん、今後作るかもしれないクラスCやDにも Xは流用できるかもしれない。
つまり、オブジェクト指向を目指す思いとクラスの数を減らそうという思いは 相反してしまうわけです。いやあたりまえなんでしょうけれど。 結局はある程度のところで適当に決断してAとXに分けるかそれとも分けないかを 決めざるを得ないわけです。いや決断って難しいですね。 クラスAが将来長く使われたり拡張されたりする可能性がある場合は分け、 共通部分が少なく、Aの将来性が少ないと見こんだ時には分けないようにしてますが、 当てが外れてしまうこともしばしば。
Aを作る前にXの必要性を考えられるようになるといいですね〜。 しかし、クラスAを1つ作るよりも、AとXの両方を作るほうが明らかに時間がかかりますので、 今必要なのはAだけだと思っているときに、果たしてXを作る決断を下せるかどうか。 すぐ動くコードを書く事は、大概オブジェクト指向と相反してしまいますね。 結局結論が出ません。日々悩みつつコードを書くこのごろ。実は日々やっているのはQuake2であり コードを書くのは稀であることは秘密。

(Visual C++)スレッド間イベント通知方法。

あるスレッドで何かした事を、別のスレッドで知りたいことは良くあります。
例えば、メインスレッドで中断ボタンを押したことを作業スレッドで感知するといったようなことです。
BOOL型のグローバル変数を用意し、FALSEで初期化しておいて、中断ボタンが押されたらTRUEにし、 作業スレッド側では頻繁にその変数をチェックする、という事で一応実現できます。
この場合は、変数にvolatileを付けておかないと、正しくチェックされないかもしれませんので注意しましょう。

しかし、この方法は場合によってはうまく動かないとの指摘を受けた事があります。例えばCPUが複数あった場合などです。 うまく動かない場面は見たことは無いですが、グローバル変数を使うよりも、 マルチスレッド自体を管理するOSの機能を使った方が確実であることは確かでしょう。

今回は、MFCのCEventクラスを使う方法を紹介します。 シグナル状態、というのはいわゆるフラグが立った状態のことです。ONになったスイッチと 考えると分かりやすいでしょう。非シグナル状態というのはその逆です。

まず、スレッド間(A、Bとします)で共通のCEvent型の変数を用意しましょう。
CEventのコンストラクタの第1引数はたぶん初期値(MSDNの説明が意味不明なので確信無し)で、 第2引数で手動か自動かを決めます。今回は手動イベントとします。
CEvent event(FALSE, TRUE);
スレッドAで、スレッドBになにか通知したいことが起きたら、 このeventをシグナル状態にします。それにはSetEventを使います。
event.SetEvent()
スレッドBで、このイベントがONになったことを知るには、 WaitForSingleObjectを使います。
これは、対象がシグナル状態になるまで待つ関数でもありますが、 待つ時間を0に設定することにより、シグナル状態かどうか調べる関数となります。
if (::WaitForSingleObject(event, 0) == WAIT_OBJECT_0){
    // イベントがシグナル状態の時の処理。
}
これでスレッドBは、スレッドAからのイベントを受け取ることが出来ます。
ちなみに自動イベントにしますと、他のスレッドがWaitForSingleObjectで イベントの通知を受けた瞬間、イベントが非シグナル状態となります。 手動イベントは、ResetEventで明示的に非シグナル状態にするまでは シグナル状態でありつづけます。


masato@mb.kcom.ne.jp