MASATOの開発日記 2000年9月


9/26

 データー(変数)をprotectedやprivateとし、publicな関数を通してでしかアクセスできない方法をデーターの隠蔽と言う・・・のかな。データーの隠蔽工作には、単にデーターを間違っていじらせない、という効果の他にも、データー構造を大幅に変更しても、それを操作するpublic関数だけを変更すれば、他のクラスには一切影響が及ばないんですね〜。つまり中身を替えても外面を同じにしておけば誰も気づかないって事です。だから、将来効率化しようとしているデーターは、別クラスにしておくと、後々楽になりまっせ〜
 

(Visual C++)CWnd::PreTranslateMessageの仕組み。

 CWinThreadにもPreTranslateMessageはありますが、今回はこれには触れません。
 Windowにはメッセージループというものがあって、GetMessageでメッセージを取得し、DipatchMessageでウィンドウにメッセージを送信する、という事をしています。(TranslateMessageはメッセージループの本質ではないので、今回は省きます)では、子ウィンドウに送られるメッセージを、親ウィンドウが前もってチェックしたいと思った場合はどうなるでしょうか。それは簡単には行きません。なぜならメッセージは親を経由する事なく、直接子ウィンドウに送られてしまうからです。ですから、子ウィンドウに、「メッセージを親にも送ってください」という指令を出すしかないわけです。ですが、いくつもの子ウィンドウを持っている場合、全部の子ウィンドウにそのように設定するのは結構面倒です。
 そこで、子ウィンドウには一切手を触れずに、親ウィンドウでメッセージをチェックするための関数がPreTranslateMessageです。PreTranslateMessageでは、いくつかの事が出来ます。
  1. メッセージを処理する。

  2. pMsg構造体の中身を見てあれこれ出来ます。
     
  3. メッセージを抹消する。

  4. TRUEを返すとそのメッセージはもうどこにも送られません。(Dispatchもされません)
     
  5. メッセージを操作する。

  6. pMsg構造体の中身を変更してしまえば、変更した内容に従ってDispatchされます。
 PreTranslateMessageが呼ばれる順番ですが、一番最初に、メッセージが送られるべきWindowのPreTranslateMessageが呼ばれます。次に、その親のPreTranslateMessageが呼ばれます。次にその親の・・・と、親へ親へとさかのぼって行きます。ちなみにTRUEを返すとその時点で止まります。ということは、あるウィンドウのPreTranslateMessageは、そのウィンドウの全ての子ウィンドウのメッセージをチェックできるという事です。そして、一番親まで遡っても、どれもTRUEを返さなければ、その時点で始めてDispatchされ、ウィンドウにメッセージが送られます。
 今回は省いていたTranslateMessageですが、これはDispatchMessage直前に実行されます。つまり、PreTranslateMessageが呼ばれた時点では、まだTranslateMessageは呼ばれていません。WM_CHARを扱う時なんかは注意して下さい。
 

9/13

 1年前に書いたコードにあったバグが今になって発現・・・なんてことがあるんですね。未定義値(整数)が、正の値であったら発現するようなバグでした。Releaseモードでしか起こらないし、実行環境によっても起きない時があるし・・・と検出が困難なバグ。自作のクラスライブラリのバグでした。Open-Read-Closeというようなファイル操作と似た感じのライブラリなのですが、OpenせずにReadすると運が悪いとは強制終了・・・。みなさん、ライブラリのpublicな関数はどのような順番で呼ばれるか分からないものです。いかなる順番で呼ばれてもしっかりとエラーを返すようにしましょう。そのためのコツは、「エラー処理は後回しにしない」事かな。後回しにすると大概いいかげんで終わるから・・・。って後回しにするのは私くらいのものかなぁ。
 

(Visual C++)時刻を文字列として求める方法。

 色々方法がありますが、いざ使うとなるとなかなか分からない物です。そんなときに便利なのがCTimeクラス。たぶんタダのラッパーだとは思うんですが、結構便利。ラッパーと馬鹿にしたものではないですね。
取得方法は以下の通り
-----
CString time = CTime::GetCurrentTime().Format("%Y/%m/%d %H:%M:%S");
-----
 なんと、たったの1行です。これで今の日付を求めると、"2000/09/13 22:50:40"となります。書式を変えたい時はFormatの引数を変えて下さい。
 

9/7

 VisualCafe3.0c(Java開発環境)なんかを使うたびにVisualC++6.0って凄い!と思います。例えばVisualCafeは全角文字で検索すると落ちます。半角文字でも後方検索をかけると落ちます。デバッグモードは遅くて使い物になりません。(起動するのが遅くて固まったかと思ったほどです。)これでも昔の開発環境よりは良いんでしょうね・・・。これと比べると、VisualC++のデバッグモードは異常に速い!たぶんハードウェアとOSの支援を受けているんでしょうね。TRACEやASSERTマクロもとても強力です。ブレークポイントをぽんと設定して実行するとそこで停止する!って当たり前ですが凄い事です。Ctrl-Zを連打するとどんどん前に戻って行くのも凄いです。もしかしたらJavaの開発環境がまだ揃ってないのかな?でもこのあとVisualC++より良い開発環境を手にしたら、VisualC++の事をめためたに言うんだろうなぁ。

 また、WindowsでJavaのプログラムを色々組んでみて、Javaでとても苦労して高速化を行ったプログラムと、まったく高速化を考えずに楽に組んだC++のプログラムを対比してみると、C++の方が10倍速くて10倍安定しているという感じです。こういうのを見てしまうとJavaを使う気が無くなってしまいますね・・・。求むJavaのNativeCompiler!有料でもいいから(学生に買える値段なら)どこか出して欲しいですね〜。というわけで今日もC++のトピックです。
 

(Visual C++)MFCのDDXとDDVを自力で使用する方法。

 なお、これらを自力でというのは、DDXとDDVは本来クラスウィザードが自動的に吐き出したコードに含まれているものです。ですので、自分でコードを書く事は普通無いです。ですがそこをあえて使うというところにロマンが・・・無いですが、出来る事が増えるので使ってみようかと。ちなみに、MFCがサポートしているかどうか分からないので、将来のバージョンでも正常に動くかどうかは不明です。
 DDXというのは、ダイアログ データ エクスチェンジの略で、コントロールと変数を対応させる機構です。例えばエディットボックスにint型の変数を対応させるような事を行います。つまり、エディットボックスの値をint型の変数に格納したり、int型の変数の値をエディットボックスに表示したりするわけです。int型と限った話ではなく、CStringやUINTやdouble等色々使えます。もちろんエディットボックスだけでなく、他のコントロールでも使えますね。クラスウィザードが扱う、コントロールにメンバ変数を対応させるというやつです。
 DDVというのは、ダイアログ データ バリデーションの略で、DDXで変数がコントロールから値を受け取った後、その値が正しいかどうかを判定する機構です。
 DDXとDDVの使い方はそれこそ星の数ほどあるので、今回は、エディットボックスと、それに対応したint型の変数についてのみ扱います。
 クラスウィザードに任せたんじゃ何が不満か、といいますと、例えば、数値を入力するエディットボックスに、何も入力させなかった時に特別の処理をやらせたい時などです。というか特別な処理をやらせたかったので調べてみたのです。それではatoiやitoaを使ってSetWindowTextでやればいいのではないか、(他にも色々な方法がありそうです)とは思いましたが、普通にDDV&DDXを使っているエディットボックスもありましたので、それとエラーメッセージを合わせたいと思ったのです。使い方には色々とありそうですが、一つ例を載せてみます。
  1. 本来ならばDDXとDDVはUpdateDataで行われます。要は、特定エディットボックス&変数用のUpdateDataを作ってしまえばよいのです。例えば次のようなメンバ関数をダイアログに追加してみましょう。

  2. -----
    BOOL CxxxDlg::UpdateEditData(BOOL save, int &val)
    {
        CDataExchange dx(this, save);

        TRY {
            DDX_Text(&dx, IDC_EDIT, val);
            DDV_MinMaxInt(&dx, val, 1, 100);
        }CATCH_ALL(e){
            e->Delete();
            return FALSE;
        }END_CATCH_ALL;
        return TRUE;
    }
    -----
    エディットエディットのIDをIDC_EDITとしました。また値の範囲は1〜100までとしました。例外処理用にMFCのマクロを流用して見ました。この関数は、値が正当で無いとFALSEを返します。ここらへんはUpdateDataと同じですね。
     

  3. では実際の使い方ですが、エディットボックスに値を表示する時は

  4. -----
    int v = 50;
    UpdateEditData(FALSE, v);
    -----
    として出来ます。
     
  5. エディットボックスから値を読み取る時は

  6. -----
    int v;
    if (!UpdateEditData(TRUE, v)){
        値が正当でなかったときの処理
    }
    -----
    とすれば良いでしょう。これで、このエディットボックスのDDX&DDV処理だけを独立させましたので、必要に応じてUpdateEditDataを呼べば、色々な処理を行えるでしょう。
 今回も使い道が限られていそうなトピックですね〜。でも汎用性があるトピックは大概他の人が書いているんです。それをわざわざ私が書いてもなんですし。CSocketとか汎用性のあるトピックも書いてみたいのですが、これ、長〜くなりそうなんです。特集にするか、気にせずまとめるか、それとも数日に分けるか・・・どうしようかなぁ。
 

9/5

 なんか最近いくつものアプリケーションを並列開発していて頭がこんがらがってきました。そこで一冊のノートを用意し、色々なアイデアは全部そのノートに書きとめるようにしました。そうすればそのノートさえ無くさなければ平気です。以前はルーズリーフに書きとめていて、これが無くなる無くなる・・・。最近部屋を整理していると遥か昔のアイデアペーパーがぽろぽろ出てきます。ゲームのアイデアなんかは取っておきたいよーな気もするんですが、数年まったく必要としなかったものを取っておいても場所の無駄のような気がして・・・。というわけで結局全部ゴミに。ノートにメモを取っておけば保存しておいてもいいかな、と思ったんですがね〜。
 

(Visual C++)CCmdTargetにコマンドメッセージを送る方法。

 MFCの重要な機能の一つであるメッセージ マップ。その中でもコマンドメッセージ(WM_COMMAND)は、結構複雑な機構で処理されているようです。例えばメニューからID_FILE_NEWというIDが付いたコマンドを選択すると、まず現在のビューがそのメッセージを受け取るかどうかを判定し、受け取れるようならば受け取り、受け取れないならばドキュメントに流します。ドキュメントもそれを受け取れないならばアプリケーション(CxxxApp)クラスにまで流れて行くようです。(正確には違います。フレームウィンドウやドキュメントテンプレートにも送られています。がこれらのクラスでコマンドメッセージは普通受け取らないので、除外しておきます)これらのコマンドメッセージを受け取れるクラスは、みんなCCmdTargetクラスの派生クラスになっています。つまり、CCmdTargetクラス自身にコマンドメッセージを送ることも可能なはずです。

 では一体どんな時にCCmdTargetクラス自身にコマンドメッセージを送る必要があるかといいますと、例えば、Viewで特定個所を左クリックした場合、とあるコマンドメッセージ(ID_MES_TESTとします)を送りたい時などです。左クリックではコマンドメッセージは発生しませんので、OnLButtonDownあたりにメッセージ送信処理を書く事になるでしょう。Viewが、このメッセージを受け取るOnMesTest関数を持っていればこれを呼んでも良いのですが、これを今は持っていないけれど将来追加するかもしれない、または今は持っているけど将来削除するかもしれない、という場合もあります。こういう場合に、View->Document->Appと流れるMFCの機構を使いたくなるものです。しかし、実はViewに(正確にはFrameに)SendMessageでWM_COMMANDを送っても何も問題無いんですよね。しっかりとMFCの機構は使われてコマンドメッセージは問題無く処理されます。
Viewクラスからコマンドを送る方法は次の通りです。
-----
GetParentFrame()->SendMessage(WM_COMMAND, MAKEWPARAM(メッセージID, 0), NULL);
-----
何故ViewじゃなくてFrameに送っているのかという理由はあるのですが、結構長い理由となりますので後で記述します。

 しかし、SendMessageには最大の問題がありまして、ウィンドウにしかメッセージを送れないのです。つまりはViewにしか送れないわけですね。ではViewで、ID_MES_TESTを受け取った時に、ある場合は処理するが、ある場合はそのままドキュメントにメッセージを流したいと思った場合はどうでしょう?あるいは、ドキュメントから、自分自身にID_MES_TESTを送りたいと思った場合は?ドキュメントはウィンドウを持っていないので、SendMessageが使えません。その時にこの手法の出番となるわけです。

その方法は簡単です。
-----
OnCmdMsg(メッセージID, 0, NULL, NULL);
-----
これだけですね。OnCmdMsgはCCmdTargetクラスのメンバ関数ですのでViewでもDocumentでもAppでも使えます。
ViewからDocにメッセージを送りたい場合は、
-----
GetDocument()->OnCmdMsg(メッセージID, 0, NULL, NULL);
-----
とすれば良いでしょう。ちなみにドキュメントにメッセージを送ってしまうと
直接メッセージハンドラ関数を呼び出しても良さそうですが、OnCmdMsgを用いた手法の利点は、メッセージ送信対象がメッセージを受け取らなくても良い、という事です。他にも、GetDocument()が、ドキュメントクラス(CxxxDoc)のポインタではなく、その基底クラスであるCDocumentへのポインタだけしか必要で無い、という利点もありますね。

 ここまで書くために色々調べたりしてみたのですが悲しいことにOnCmdMsgではなくSendMessageで十分のようです。ドキュメント自身にコマンドメッセージを送ることは推奨出来ません。何故ならドキュメントにメッセージを送ってしまった場合、そこでルーティングが止まってしまってAppには送られません。推奨するのは次のトピックの手法です。また、コマンドメッセージを次のターゲットに渡す機構も別に用意されているようです。(これは余裕があったら後日紹介します。)
 

(Visual C++)メニューからコマンドを選択するのと同じ処理を行う方法。

 メニューからどれかコマンドを選択するプログラムを書きたい!と思うときは良くあります。例えば何かしたときにドキュメントを初期化したくなった場合。メニューから新規作成を呼べばさくっと終わってしまいます。楽ですね〜。でも、これをプログラムで呼び出すとなると迷うときが結構あります。前のトピックで色々述べていますが以下の手法が一番良いでしょう。
-----
AfxGetMainWnd()->SendMessage(WM_COMMAND, MAKEWPARAM(ID_FILE_OPEN, 0), NULL);
-----
グローバル関数を使っているのでどこから呼ぶことも出来ます。

・・・。
一番無難な方法ですね。一体OnCmdMsgはどこにいってしまったのでしょうか。というかOnCmdMsgは要りませんです。はい。(T_T)
 

(Visual C++)MFCのコマンドメッセージの振り分け方。

 MSDNで「コマンドの振り分け (ルーティング)」を見てみて下さい。

・・・。
以上です。

 としてしまうと悲しいので多少解説します。SDIの場合のみに限定します。メニュー等で選択したコマンドメッセージは、基本的にはメインフレームに送られてきます。すると、このメッセージは、
「アクティブなビュー」→「メインフレーム」→「アプリケーション(CxxxAppオブジェクト)」
の順に送られます。どれか1箇所で受け止められたらそこで終わりです。
 そして、アクティブなビューに送られたコマンドメッセージは
「ビュー」→「ビューに割り当てられたドキュメント」
の順に送られます。
 ドキュメントに送られたコマンドメッセージは、
「ドキュメント」→「ドキュメントに割り当てられたドキュメントテンプレート」
となります。
これらを全て組み合わせますと、メインフレームに送られたコマンドメッセージは
「アクティブなビュー」→「ビューに割り当てられたドキュメント」→「ドキュメントに割り当てられたドキュメントテンプレート」
→「メインフレーム」→「アプリケーション」
の順番に処理されます。そして、一般的にコマンドメッセージを処理するのはビューとドキュメントとアプリケーションなので、
「アクティブなビュー」→「ビューに割り当てられたドキュメント」→「アプリケーション」
と処理されるわけです。
これが普通の流れですね。

 本日の最初のトピックでは、ビューやドキュメントにコマンドメッセージを送らないでフレームに送るべきです、と書きましたが、それの理由がここにあります。ビューにコマンドメッセージを送ると、それは
「ビュー」→「ビューに割り当てられたドキュメント」→「ドキュメントに割り当てられたドキュメントテンプレート」
という順番で処理されます。ドキュメントに送ると
「ドキュメント」→「ドキュメントに割り当てられたドキュメントテンプレート」
という順番です。つまり、アプリケーションに肝心のメッセージが送られないのです。これを避けるためには、メインフレームに送るしかないようです。

「ビュー」→「ドキュメント」→「アプリケーション」
という流れの「ドキュメント」→「アプリケーション」という流れだけを使おうと思っても出来ない訳がここにあります。しかし、前の方のトピックでも述べていますが、ドキュメントで受け取ったコマンドメッセージを、そのまま次に流す、という事は可能です。ON_COMMAND_EXCCmdUI::ContinueRoutingという面白そうなキーワードを見つけたので、そのうちこれらについての研究結果をお知らせしたいと思います。



戻る
トップページへ
masato@mb.kcom.ne.jp