Part 103 C++ ネイティブコード アプリケーションの開発 |
この Part の目次 | ||||
章 | 目 次 | 公開日 | 改定日 | 記 事 |
1 | Visual Studio 特有の注意 | 2013/07/08 | ||
2 | ベース プロジェクトの作成 | 2013/06/28 | ||
2.1 | 32/64ビット アプリケーションの作り分け | 2013/07/08 | ||
3 | VC++ で開発したアプリケーションの配布 | 2013/06/28 | ||
3.1 | VC++ ネイティブ コードの作成手順 | 2013/06/28 | ||
3.2 | 配布方法 | 2013/06/28 | ||
3.3 | 配布とランセンスの問題 | 2013/07/10 | 2013/07/19 | Microsoft 本社ライセンス担当部門からライセンス上の問題が無いことが示されました。 |
4 | _tWinMain 関数 | 2013/06/28 | ||
4.1 | 無限ループ | 2013/06/28 | ||
4.2 | GetMessage 函数がエラーコード -1 を返す場合の処理について | 2013/07/19 | 日本 Microsoft 技術サポート チームの説明を受け、納得できた結論を記しました。 | |
5 | Button の追加 | 2013/06/28 | ||
6 | メニューの追加 | 2013/06/28 | ||
7 | アクセラレータ キーの追加 | 2013/06/28 | ||
7.1 | 他の main 函数の例 | 2013/07/09 | ||
8 | UNICODE 文字列の表記 | 2013/06/28 | ||
9 | C++ アプリケーション開発の注意事項など | 2013/06/28 | ||
9.1 | 技術資料の入手方法 | 2013/06/28 | ||
9.2 | ボタン作成に関係する Microsoft 技術資料リスト | 2013/06/28 | ||
9.3 | CreateWindow 関数 | 2013/06/28 | ||
9.4 | メニューのリソース ファイル | 2013/06/28 | ||
10 |
この Part は全体として矛盾がない構成にすることを目的に、全面的に見直しました。まだまだ誤りが多く残っているかもしれませんので、今後も内容の見直しを続けます。 2013年6月28日 志摩 敞 |
|
||
およそ20年前、プログラマー廃業まで使用していた開発言語は、アセンブラ と Kerninghan & Ritchie の C言語です。C++
言語の経験はありません。Windows PE 3.x から起動できるアプリケーション プログラムを作る目的で、手始めにボタン作成から始めることにしました。
Microsoft に技術支援を求めたところ、C++ ネイティブコードで開発を行うことになりました。 技術支援をしてくれたエンジニアのアドバイスで、次の書籍で学習を続けています。 粂井 康孝著 「猫でもわかる Windows プログラミング」 第3版 SoftBank Creative このPartの解説内容は、上記「猫」に似かよっていますが、私なりの解説を行います。ネズミ年生まれの私は、娘たちから「チュウ」と呼び捨てにされます。「猫」と「チュウ」の解説はだれの目にも「猫」>>「チュウ」ですね。是非「猫」での学習もお勧めします。なお C++ の文法学習は、同じ著者の「猫でもわかる C++ プログラミング」が有力な選択肢だと思います。 こPartでは、Visual Studio 2013(Visual Studio Professional 2013)を使用します。私自身 MSDN で契約したままで使っていない多くの Visual Studio のバージョンを持っていますが、2013 と特定するのは、開発環境のライフ サイクルを考えた選択です。 このPartでは、まず「ベース プロジェクト」を作って見ます。「ベース プロジェクト」と云う言葉は少し大げさですが、これが今後全てのアプリケーション開発の基本となるので、この名前をつけました。 実際のアプリケーション プログラムを開発する作業は、「ベース プロジェクト」の中の WndProc 関数の修正/追加がほぼ全てです。これで、「ベース プロジェクト」という言葉の意味を理解いただけたことと思います。今後の作業は保存しておいてた「ベース プロジェクト」 を修正するのではありません。「ベース プロジェクト」自体は、1~2分で作れるので、今後の開発作業は常に「ベース プロジェクト」 作りから始めるという意味です。 |
|
||
1章 Visual Studio 特有の注意 | ||
20年ぶりに現世に復帰したものの最新の Visual Studio 2013 を眼にし驚きました。誤って玉手箱を開いてしまった老人が七転八倒の末学んだことが、参考になれば幸いです。 この章で取り上げるのは、項目名が主なので、詳細は関連する本文を参照してください。 |
||
① Visual Studio 2013 は、x64にも対応した不思議なx86アプリケーションです。 | ||
このこと自体大きな問題はありませんが、x86にしか対応しなかった初期の Visual Studio に無理やり屋上屋を積み重ね、x64対応にしています。 当然フォルダ構成とフォルダ名は x86/x64 でシンメトリー性が欠如することになります(下図参照)。 |
||
左 Pane の赤枠で囲った部分が Cドライブ直下に構成した私の Visual Studio 用開発環境のフォルダー構成です。 右 Pane の赤枠で囲った部分が初期の Visual Studio のフォルダー構成(アプリケーションだけに注目した概念図)です。この部分は、32ビット用のフォルダーです。 緑枠で囲った x64 フォルダーが、積み重ねた屋上屋です。この中には、32ビットと同様に「Debug」と「Release」フォルダーが存在し得ます。 このように、x64 と言う言葉は存在しても x86 と言う言葉は何処にも存在しません。 気付き悪いのですが、32/64ビット両方のアプリケーションを開発する場合、どちらを先に作るかにより、微妙に設定方法が変化します。Visual Studio 2013 は、あくまでも32ビット用統合開発環境が基本理念で、64ビット用の環境はおまけでついているような印象です。シンメトリ構成の32/64ビット統合開発環境を望むのは、私だけなのでしょうか?Microsoft の技術者もほとんど気にしていない印象を受けます。 |
||
② Bool 宣言した函数に多くの返り値を設定しています。 | ||
Bool 宣言した GetMessage 函数(4章参照)に 特別な値と称して True と False 以外に ”-1” を設定し、無限ループを抜け出せなくしています。加えてその対応をユーザーに義務付けています。この行為は、 ANSI や JIS などの国際規約に違反しています。 |
||
③ 事象をまとめるグループ化の概念が目茶苦茶、また事象を示す言葉が意味不明です。 | ||
これは、多くの場面で出現します。ユーザー目線で全体を見渡せる開発リーダーの不足が原因と考えますが、初期の Visual Studio の不備を継承しているのかもしれません。 このことが原因でユーザーの自然な発想での機能選択を非常に難しくしています。以下に私が苦労した実例を記します。 (1) Visual Studio で開発するアプリのビット幅を選択する場所が判りますか? ① の x86/x64 にも関連しています。 (2) (1)にも関連しますが、x64 に対局する言葉が Visual Studio では、”win32” なのを理解できますが?Microsoft の多くの技術資料では、当然 x86 です。 (3) マルチ スレッドの言葉が判りますか?Microsoft の技術サポートでも説明できないのだから、判るほうが不思議ですね。本来のマルチ スレッドの意味と全く違う事象にこの言葉を使うのは不適切です。Microsoft がこのような対応を取り続ける限りユーザーのごく自然な発想で Visual Studio を使いこなすことが一層難しくなります。PC の世界は、昔から知っている人がだけが偉いという発想がありました。困った風習です。 |
||
④ 正常に動作しても、注意が必要です。 | ||
最も重要な注意です。充分注意したつもりでも、意外な落とし穴があります。 それは、Visual Studio がデフォルトで設定している内容を見落とし、アプリケーションの動作状態を正確に把握していなかったために、私も陥った誤解です。 デフォルト設定は、多くの人が「そのように設定したい」と考えていることが実現されているので現実に問題になることは少ないと思います。しかし、ランタイム ライブラリが DLL(MD) に設定されていることは、忘れないで頂きたいと願う老婆心ゆえのご注意です。充分ご留意ください。 |
||
|
||
2章 ベース プロジェクトの作成 |
まずこの Part の主目的である、「C++ ネイティブ コード アプリケーションの開発」を行うためのプロジェクト設定を行います。 Visual Studio 2013 を起動します。 |
上の図の赤枠で囲った「新しいプロジェクト...」を選びます。 |
Visual Studio 2013 起動直後の画面は、インストール時に行った設定によりさまざまです。このサイトでは 「C++ ネイティブ
コード アプリケーションの開発」を行うための設定を行っているので Visual Studio 2013 起動直後は、次の画面が表示されることを前提にしています。 |
上記の画面が表示されない場合は、上図の赤枠で囲った部分に注目し、「テンプレート」と「Visual C++]を展開し、”Win32”を選んでください。名前と場所は自由です。私は次のように設定しています。 名前 : BaseProject としました。場所は C: ドライブ直下に VisualStudio2013 フォルダーを作りこのフォルダーを常に指定しています(一度指定しておけば変更しない限り、次回も同じフォルダー名が表示されます)。なお、Windows PE のコマンド プロンプト画面と連携することを考えると Visual Studio 2013 のようにフォルダー名やファイル名にブランク文字を含まないほうが良いと思います。 上図のように「Visual C++]を展開した時、「CLR」を選ぶと、.Net Framework に対応したアプリケーション開発環境になります。私たちは、この設定で ”Win32” を選んだので、C++ ネイティブ コード アプリケーションを開発する環境になっていることを理解してください。 上図のようにチェック ボックスにチェックをつけないで、「OK」を選びます。 |
続いて表示される、上の画面で「完了」を選びます。これで「ベース プロジェクト」が完成します。『名前入力』→『OK』→『完了』の3ステップ、とても簡単です。20~30秒で次の画面が表示されます。 |
これは、Visual Studio 2013 が自動作成したプログラム リストの先頭部分です。赤枠で囲った部分は、古典的な Kerninghan & Ritchie の C言語の Main 函数に相当する _tWinMain 函数です。この函数については、後で詳しく説明します。 このプログラム リストを最後までスクロールします。 |
最後は、About という函数です、この関数の最後の括弧 (}) にカーソルを合わせると 182行目であることが判ります。2013年7月現在の「ベース プロジェクト」プログラム行数です。Visual Studio 2013 の起動も含め、あなたの環境でも2分程度で「ベース プロジェクト」が完成することでしょう。 |
上の図の赤枠で表示した「ローカル Windows デバッガー」ボタンが表示されていることを確認してください。異なる表示の場合は、すぐ右の緑色枠で示した小さな下向き矢印で選択しなおします。このボタンを押すとこのウィンドウに記入されたコードがコンパイルされた後エラーが無ければ実行されます。 |
上の画面で「はい」を選びます。数秒待つと次の画面が表示されます。 |
この画面がベース プロジェクト アプリケーションを実行した画面です。 一番上の青色部分は「タイトルバー」で、このプロジェクトの名前が表示されています。左端には、アイコンも表示されています。 右側にある最大化、最小化ボタンが動作し、ウィンドウの大きさを自由に拡大、縮小、移動もできます。右端の ”X” 印でこのプログラムを閉じることができます。 次の灰色行は、「メニューバー」です。「ファイル」と「ヘルプ」2つのメニューが表示されています。 今後は、この「ベース プロジェクト」に肉付けして、Windows で動くアプリケーション プログラムを作れるわけです。C++ は、Microsoft で唯一ネイティブコードを作り出せるコンパイラーで、これはCPU性能を100%要求される分野では必須の技術です。 一度コンパイルした「ベース プロジェクト」はハードディスクに保存され、F5 キーで起動できます。 この「ベース プロジェクト」はさらに 「ショートカット キー」 機能と 「アクセラレータ キー」 機能をデフォルトで備えています。例えばメモ帳で、「ファイル」メニューを選んだ時に表示される下記のような画面をよく見ますね。 |
上の図で、 新規(N) の ()の中の文字 N が、ショートカット キー Ctrl+N の 文字 N は、アクセラレータ キー なのです。 「ファイル」メニューを選び、サブメニューを表示した状態で、文字 O(オー) をキーインすると、「開く」のサブメニューを選んだのと同じ働きをします。 「ファイル」メニューを選ばない状態で、 Ctrl+O をキーインすると、上と同じ働きをします。 わざわざ サブメニューを表示しないで、 Ctrl+S で上書き保存、 Ctrl+P で印刷などの機能が使えるアクセラレータ キー機能はとても便利ですね。 「ベース プロジェクト」では、ショートカット キーのありますが、アクセラレータ キーは、ありません。これは、アクセラレータ キーの登録がないからで、登録すれば「メモ帳」などと同じように使うことができます。登録の方法は、後の章で説明します。 ところで、「ベース プロジェクト」には、秘密(説明がありませんが、後の章で使うリソース ファイルを見ると分かります)のアクセラレータ キーが存在し、 Alt+/ あるいは、Alt+? で、「ヘルプ」キーと同じ働きをします。 |
以上で1章の解説は終わりです。皆様のご感想は如何ですか? 私は『20年前のアセンブラ と Kerninghan & Ritchie の C言語の時代なら、「ベース プロジェクト」の実現には、1年以上かかったのでは』と思いました。 わずか2分で、「ベース プロジェクト」が完成する現実に感激しています。 |
|
||
2.1 章 32/64ビット アプリケーションの作り分け | ||
2章で作ったベース プロジェクトは、32ビットのアプリケーションです。では、64ビットのベース プロジェクトはどのようにして作るのでしょうか? |
||
上の図で、赤枠で囲った「Release」にカーソルを合わせるとその下に赤枠で囲ったように、「ソリューション構成」と表示されます。ここに含まれているものは、「ソリューション構成」と言う名前で区分されますという、Microsoft
苦しい言い訳ですね。 「Release」の右に表示されている下向きの小さな矢印をクリックすると次の図に示す選択肢が表示されます。 |
||
上図赤枠で囲った「構成マネージャー」を選択します。 |
||
上図赤枠で囲ったプラットフォームの下に「Win32」と表示されているセル右にある矢印(どちらでも良い)をクリックします。 |
||
上図で、「新規作成」を選びます。 なお上の図や下の図に表示されている ”ARM” の言葉は、CPU の種類を表す言葉です。 |
||
上の図が表示されるので、赤枠部分を確認し「OK」を選びます。 |
||
上図のようにプラットフォームに x64 が表示されました。 |
||
主画面も「Release」の右側に x64 が表示されています。上図赤枠の「ローカル Windows デバッガー」をクリックして完了です。 皆さん、Release/Debug を選ぶ選択肢で32/64ビット アプリケーションの作り分けが選べると素直に理解できますか? 加えて、Visual Studio 2013 で x64 に対局する言葉は、 Win32 なのです。Microsoft のほとんどの技術資料では、x64 に対局する言葉は勿論、x86 です。 Microsoft の全製品で共通の言葉を使って欲しいと考えます。 |
||
|
3章 VC++ で開発したアプリケーションの配布 | ||
|
||
3.1 章 VC++ ネイティブ コードの作成手順 | ||
通常 Visual Studio 2013 で作られるコードは、.NET Frameworkの共通言語基盤 CLI(Common Language Infrastructure)で動作します。このサイトでは、Windows PE 環境で動作させるため、ネイティブ コード アプリケーションを作る手続きを次の手順で行います。 |
||
上図の赤枠のプロジェクト名を右クリック → 「構成プロパティ」 → 「C/C++」 → 「コード生成」 → 「ランタイム ライブラリ」 →
「マルチスレッド(/MT)」 の順に選択し「OK」を選びます。下に一連の画面ショットを連続して示します。 なおここで改めて説明する項目ではありませんが、配布用アプリケーションを開発するのであれば、上図右上方の赤枠部分に示すように「Release」を選択しておきます。 |
||
次の「プロパティ」が表示されます。 |
||
上の図で、赤枠で囲った部分を展開または選択します。 最後の「ランタイム ライブラリ」はどれを選んでもネイティブ コードが生成されますが、ここでは「マルチスレッド(/MT)」 を選びます(デフォルトは、青色に反転している「マルチスレッド DLL(/MD)」が選ばれています)。2つ前の画面で「Releas」を選んだので、この画面に「(/MTd)」や「(MDd)」の選択肢(選択肢の大文字 D はDLL 小文字 d は、デバッグを表しています)が表示されるべきではありません。Visual Studio のバグですね。 この画面で本来選択肢として表示されるべき2つの選択肢の役割は、次のとおりです。 /MT : 外部コンポーネントを一切必要としない完結した構成のアプリケーションを作ります。 /MD : 実行時に必要な外部コンポーネントを呼び出す構成です。Windows Update の結果が反映されるかも知れません。 Windows PE 用アプリケーションで、/MD を選択しても正常に動きません。 理由は、Windows PE には、必要な DLL が準備されていないからです。従ってここでは、 /MT を選択します。 なぜ選択肢の重要なキーワードが「マルチスレッド」なのか、判然としません。長年の習慣なのでしょうが、「知っている人だけが使える」システムは、困ったものです。私自身この手続きを見つけるのに、1ヶ月以上かかりました。 使用者に「ネイティブ コード」 ≡ 「マルチスレッド」の発想は絶対に浮かびませんね。適切な名前付けと Microsoft による正確な解説書が望まれます。 この件は、Microsoft の技術サポートに問い合わせ中です。 |
||
|
||
3.2 章 配布方法 |
この Part は、開発したアプリケーションを他の人や、他の PC に配布する方法の説明です。配布先に Visual Studio 2013 がインストールされていれば、配布には何も問題がありません。したがって Visual Studio 2013 のライセンスがあれば、まず Visual Studio 2013 をインストールすることでも解決します。 また、Mac 環境では、標準添付されている Boot Camp で NTFS パーティションを作り Windows OS をインストール後 Visual Studio 2013 をインストールすれば、同じ環境になります。 以上を踏まえ一般的な配布方法を説明した技術資料の出典は、次の MSDN 技術資料です。 |
http://msdn.microsoft.com/ja-jp/library/dd293574.aspx 上記資料によれば、次の3つの方法があります。 ① Central Deployment 次の Visual C++ 再頒布可能パッケージを使います。これは、Visual Studio 2013 をインストールしていない環境に配布する標準の方法です。 なお、このパッケージは無料でダウン ロードできます。 VCRedist_x86.exe (約4.8MB) : 32ビット用 VCRedist_x64.exe (約7MB) : 64ビット用 特徴 : Windows Update で自動更新されます。 ② Local Deployment 次の図に示す2つの DLL を配布メディア(USB など)に配布したいアプリケーションと一緒にコピーしておきます。 msvcp110.dll msvcr110.dll 32ビットのコピー元 : C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\redist\x86\Microsoft.VC110.CRT\ 64ビットのコピー元 : C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\redist\x64\Microsoft.VC110.CRT\ なお、この DLL ファイルをターゲットに配布する必要はありません。 Windows Update による自動更新はありません。 |
なお、上の図で赤枠で囲った DLL は、Windows 8 のストア アプリ用ライブラリー ファイルなので、今回の目的には関係ありません。 この状態で、USB に収めたアプリケーションをダブル クリックすることでで実行できます。私の説明不足のため、この動作に「何故?」の疑問が残りますね。実は配布したアプリケーションが、Visual Studio VC++ デフォルトの /MD で作られていたからなのです。ダブル クリックされたアプリケーションに不足している全ての DLL が カレント フォルダーである USB の中にあったので、ダブル クリックにより未解決の外部参照が解決し USB の中で /MT で作られた状態に変身してしまったのです。配布先が Windows マシンの場合この Exe をコピーすれば、/MT で作られたアプリケーションとして動作できます。 相手の利用者が Windows PE の場合は、シャット ダウンで実メモリ上のイメージは消えるので、USB を渡す必要があります。 ③ Statically Link 2.1章 で説明したネイティブ コードのアプリケーションを作る方法です。 Windows Update による自動更新はありません。 |
|
||
3.3 章 配布とライセンスの問題 | ||
配布で問題になるのは、3.2章 ② Local Deployment で説明した、2つの DLL を利用する場合だと考えました。 ライセンス問題の Microsoft 所管部門が判らず、取り合えず「日本 Microsoft IT化支援センター」に問い合わせました。2013年2月のことです。回答結果要旨は以下のとおりです。 『あなたは MSDN のライセンスを持っているので、開発レベルでの使用は認める。しかし、開発終了後の DLL 配布は、ライセンス違反なので、再頒布可能パッケージをダウンロードして利用して欲しい』 Windows PE 3.1 では再頒布可能パッケージが使えないことを説明しても、「IT化支援センターは技術のことは答えられない、有償の技術支援を利用して欲しい」との回答です。 2013年5月、インシデントを添付して技術支援依頼を行いました。結論は、「日本には、ライセンス上の結論を出せる部門は無い」との回答です。2013年7月2日、日本 Microsoft の技術支援担当部門に、米国の法務担当部門に問い合わを依頼しました。 結論が得られれば、この Part で報告します。 |
||
米国のライセンス担当部門からの回答 | ||
2013年7月16日、日本 Microsoft の技術支援担当部門経由『2つの DLL の配布は、ライセンス上問題が無い』との連絡を受けました。下記の資料は、その際根拠資料として提示されたものです。 |
||
Part 10 参考文献 No.60 「License Extensions for Visual Studio 2012 and Visual Studio 2012 SDK」 上記資料に記載されている2つの DLL に係わる部分を下の図に抜粋します。 |
||
これでライセンス問題は、全面解決です。 なお、Visual Studio 2013 Preview 以降は次の参考文献を参照ください。 Part 10 参考文献 No.59 「MicrosoftProductUseRights(WW)(English)(July2013) 」 |
||
|
||
4章 _tWinMain 関数 |
この章では「ベース プロジェクト」の _tWinMain 関数について分かる範囲で説明を行います。正確な説明書がないので、外見から推測しました。 _tWinMain 関数のフロー図を次の図に示します。機能にあまり関係のない部分は省略してあります。フロー図左列の数字は、コードの行番号です。全コードを行番号付きで印刷し、説明と合わせて参照してください。 |
上のフロー図で _tWinMain 関数の大雑把な流れが理解できると思います。コードの43行目までは、この環境の「初期設定」部分でプログラムを起動した時1回だけ実行されます。 46行以降 は、このアプリケーション プログラムへの作業指示メッセージの待ち受けとメッセージ処理を行う無限ループです。ユーザーが終わりを指示しない限り、無限に回り続けます。終わりを指示する代表的な仕掛けは、ウィンドウ右上端の、終了ボタン(通称 ”X" マーク)です。終了ボタンをクリックすると QUIT メッセージが発行され、この図46行目の判定文(この図は概念的なものです。46行目の実体は While 文で、判定文は While 文を終了する条件です。)で分岐してプログラムが終了します。 メッセージの代表格は、マウスやキーボードなどの人の操作で送信されます。 無限ループはイベント ドリブン型ソフトウェアー特有の構造で、Windows OS もプログラムの一番最後が必ず無限ループになっているはずです。 外見が違う多くのアプリケーション プログラムの骨組み部分は、ほとんど上図と同じです。 「ベース プロジェクト」の126行目に WndProc 関数が登録されていますね。この関数は、ウィンドウ プロシージャと呼ばれるイベント ハンドラで、私達プログラム開発者が作ります。「ベース プロジェクト」では、72行目の構造体メンバーにプログラム名を登録しておく仕様になっています。この WndProc 関数が、アプリケーション プログラムの性格を決めているのです。 大雑把にいえば、アプリケーション プログラムの開発は、 WndProc 関数に修正/追加する作業と言い換えられます。 上のフロー図をもう一度見てください。_tWinMain 関数から直接呼び出される関数はわずか6つです。次の表でもう少し詳しく説明します。 |
|
行 | コード | 説 明 |
15 | ATOM MyRegisterClass(HINSTANCE hInstance); | _tWinMain() 関数で使用する関数のプロトタイプ宣言です。 コンパイラは、未知の関数呼び出しコードをエラーにする仕様になっています。そのため予め呼び出す関数の引数、返り値などの仕様をコンパイラに教え、呼び出しがあれば処理を取りあえず終え、当該関数を読み込んだところでペンディングが解消されます。 関数が呼び出される前に関数を記述しておけば、この4行は不要です。このプロジェクトの場合、”About()”、”WndProc()”、”MyRegisterClass()”、”InitInstance()”、”_tWinMain()” の順番にソースコードを記述すればこの4行を省略でます。About() 関数を先頭にするのは、WinProc() 関数で関連付けが最初に解釈されるためです。 なお「プロトタイプ宣言」には関係ありませんが、 ”CALLBACK” キーワードは、関数を「イベント ハンドラ」にするためのキーワードで、”WINAPI” と記述してもかまいません。 ”About()”、”WndProc()” の2つの関数は、次のようなイベント ハンドラとしての処理を要求された時に起動されます。 ①About() ; アプリケーション ウィンドウで バージョン情報を要求した時。 ②WndProc() : ウィンドウの作成、ウィンドウにボタンなどのコントロールの作成、ボタンのクリックなど Window に係る要求が生じたとき。 イベント ハンドラ名は自由ですが、関連付け情報と整合性が必要なので統合開発環境(Visual Studio)が決めた上記の名前を使うのが無難です。 |
16 | BOOL InitInstance(HINSTANCE, int); | |
17 | LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); | |
18 | INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); | |
20~23 | int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow) | Kerninghan & Ritchie C言語の main 関数に相当する関数です。2番目の引数 hPrevInstance は下位互換のために残っていますが、現在は使われていません。 |
25 | UNREFERENCED_PARAMETER() | 1度も使用していない変数が存在する警告を回避する処理です。警告を気にしなければ無くてもかまいません。 |
26 | UNREFERENCED_PARAMETER() | |
33 | LoadString(hInstance,IDS_APP_TITLE, szTitle, MAX_LOADSTRING); | _tWinMain() 関数の第1引数を使い、このプロジェクトに必要な名前文字列を指定された文字配列に保存すします。szTitle[] には、 ウィンドウのタイトルバーに表示するタイトル名を保存します。 |
34 | LoadString(hInstance,IDC_SAMPLE, szWindowClass, MAX_LOADSTRING); | 33行目と同様に、プロジェクト名をszWindowClass[ ] 配列に取り込みます。文字列は大文字に変換され、プログラム内の定数の一部分として複数の場所で使用される重要な役割を持っています。 |
38 | if (!InitInstance (hInstance, nCmdShow)) | CreateWindow() 関数を呼び出し、メインフレーム ウインドウを作成します。 続いて ShowWindow() 関数でウィンドウを表示します。 |
43 | hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SANPLE)); | ショートカットキーとアクセラレータ キー文字テーブル情報を xxxx.rc リソース ファイル(xxxx は、プロジェクト名)を読み込み、メモリー上に高速処理に適したテーブルを作っているものと考えられます。このテーブルのハンドルは、48行目の TranslateAccelerator() 関数で使用されます。 |
46 | while (GetMessage(&msg, NULL, 0, 0)) | オペレーティング システムが送信するメッセージを待機する無限ループです。メッセージ取得関数の返り値が TRUE の間処理を繰り返す普通の処理ですが、MSDN の技術資料に次のような不穏な記載があります。 GetMessage 関数返り値は BOOL と定義されているが、次の3つの情報を返す。 ① 終了の場合 : FALSE ② 終了しない場合 : -1 以外の TRUE ③ エラーの場合 : -1 の TRUE 従って while (GetMessage(&msg, NULL, 0, 0)) のコーディングでは、ループを抜け出せない可能性があるので、このようなコーディングはしないこと。 この while 文の記述は、Visual Studio 2013 が自動作成したものと全く同一で、不可解極まりない注意喚起と言えます。 ネットワーク上にも、複数の注意喚起情報が投稿されていますが、このようなトラブルがどのような状況下で発生し得るのかの情報はありません。 経験則から、この種情報の真偽をインシデント付きで Microsoft に解明依頼しても、日本マイクロソフト社は受け入れてくれないと思われ、この警告を無視するしかない状態です。 Microsoft の注意書きは、返り値を BOOL に指定した関数は、文法上 TRUE か FALSE の2値を返すと決めた ANSI や JIS 規約に違反しています。 Microsoft には、大至急次の対応を求めます。 ① 関数の返り値を int に変更する。 ② Visual Studio 2013 が自動生成するプロジェクトのコードにエラー処理を含める。 あまりにも腹立たしいのでさらに付言すると、Visual Studio の小売値はかなり高価であり、購入者は、Microsoft を信じて使用しています。その高価な商品が自動生成するコードの危険性を技術情報で注意喚起しても免責にはなりません。技術文書は、官報による公告とはわけが違います。 万一このトラブルて訴訟提起する方があれば、私は全面的に協力します。なお私自身は、無視と決めているので、訴訟に持ち込む意思はありません。 なお、粂井さんの「猫」では、返り値が -1 なら break文で無限ループを抜け出す処理が施されています。真面目な猫さんですね。 繰り返し主張します。 -1 は、全てのビット値が 1 の絶対的な True 値です。False 値以外のいかなる値と論理輪をとっても必ず True になる、最強の True 値です。このような値を While 文の終了条件にして終了することはプログラム作法として絶対に許されません。 Microsoft に反論があれば、公開の場での議論を提案します。 注意 : 「4.2章 GetMessage 函数がエラーコード -1 を返す場合の処理について」 を参照してください。 |
48 | if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) | 正直言ってこの関数の詳細な処理内容は、想定不能です。例えば「猫でもわかる Windows プログラミング」6.3章でアクセラレータ キー処理を行う例では
Ctr + Shif + 1 3文字分の入力が終わらなければ、43行目で作成した表を引く処理が完了しません。即ちこの関数も3回呼ばれることになります。3回分の辞書引き成功後、実際の処理イメージを誰かが(この誰かは、多分50行目の
TranslateMessage 関数だと考えますが断定はできません)作ることになるのでしょう。 『実際の処理イメージを作る』とは、この例の場合 WndProc 関数の switch (wmId) ブロックで処理される caseラベル 『IDM_OPT1』を46行目で作った表から取り出し、あたかもこのラベルを直接選択するメッセージが利用者から送られたようにメッセージ構造体を作り直すことです。その処理を行ったうえで、51行目の DispatchMessage 関数を介して、WndProc 関数に送っていると推察します。何れにしても想像の域を出ません。 |
50 | TranslateMessage(&msg); | この2つの関数も処理内容の詳細な想定は不能です。50行目の TranslateMessage 関数が無いと ①アクセラレーター キーの処理が全くできない、 ②『メッセージを変換する』と言う意味のこの関数名、 の2つに注目した推察を行い、48行目の『説明』欄に記載した結論に至りました。 なお、 DispatchMessage 関数は、イベント ハンドラー モドキ WndProc 関数を呼び出すために存在しています。 |
51 | DispatchMessage(&msg); |
GetMessage 関数 : http://msdn.microsoft.com/ja-jp/library/cc364699.aspx 上のリンクで説明が追加されていました。しかし、この函数の返り値が Bool である限り、言い訳にしか過ぎません。なぜここまで、Microsoft はへ理屈を言い続けるのか理解に苦しみます。この函数の返り値を Int に変更できない正当な理由が有るのでしょうか? 注意 : 「4.2章 GetMessage 函数がエラーコード -1 を返す場合の処理について」 を参照してください。 |
最後に、48行目の TranslateAccelerator 関数について一言付け加えます。 第1引数には、このウィンドウのハンドルを指定する仕様ですが、このハンドルをどうやって手に入れるのか、「猫」の著者 粂井さんも悩まれたようです。粂井さんは InitInstance 関数でウィンドウを作った直後、このハンドルを グローバル変数 hParent に保存しています。 私が参照した「猫」は第3版で新しい版では変更されているかもしれません。 しかし、Visual Studio 2013 が作り出したコードではさりげなく msg.hwnd と設定してあります。この関数は、必ず46行目の GetMessage 関数とペアで使うので、GetMessage(&msg, ・・・)関数を実行したところで、msg 構造体にこのウィンドウのハンドルが収められているからです。全く憎らしいほどスマートで素晴らしいコーディングですね。下にこの構造体の構成内容を MSDN から転載します。 MSG 構造体 |
typedef struct tagMSG { // msg HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG; パラメータ
|
|
||
4.1 章 無限ループ |
多くの方には無用かもしれませんが、_tWinMain 関数の説明を行うには「無限ループ」の説明が必要です。 私は CP/M さえも無い PC 黎明期に PC に興味を持ちました。最初のアセンブラ プログラムは MC6800、 始めてお金儲けのために、リアルタイム モニターと称して OS モドキを作ったのは Intel の8085 用でした。話が逸れましたが、OS モドキの一番最後は、その最後の命令(1つ前の命令、あるいは自分自身、CPU によりアセンブラーの仕様が異なるためです)にジャンプする無限ループです。 一筆書きのプログラムが終了してしまうと、それ以上何も処理ができないので、無限ループが必要なのです。何もせず、グルグル回っているときに CPU の割り込みを使って必要な処理をします。複数のプログラムを周期的にディスパッチする(マルチ タスク)には、タイマー ハードウェアー割り込みを利用しますが、多くは CPU に1つしか無い「ソフトウェアー割込み」を利用します。多くの割り込み原因を区別するのは、今で言うポート番号です。 無限ループの回り具合を調べれば、CPUの負荷状態がわかります。Windows のタスク マネージャーの仕掛ですね。常に回り続けていれば、CPU 負荷率は 0%、無限ループにコントロールが帰ってこなければ、100%負荷状態です。 そのようなわけで、1つの C++ アプリケーションプログラムで、ユーザーの気まぐれな要求に対応しようとすれば、アプリケーション プログラム中に無限ループが必ず必要になります。 CPU の使用権を OS に返してしまうと、二度とユーザーの要求に応じられなくなるからです。 _tWinMain 関数では、この無限ループの構成が少し複雑になっています。通常割り込み処理は、OS の持つイベント ハンドラーで処理しますが、_tWinMain 関数を使ったプロジェクトでは WndProc 関数でユーザーが処理しなければなりません。言い換えるとメッセージ処理を全てソフトウェアーで処理しなければならないのです。誰かが出すメッセージ イベント フラグを常時 TranslateMessage 函数で監視をしなければならない宿命になっているのです。 この事情が無限ループ部分の構成を複雑にし、美しくない構成設計になっているのだと考えます。 ① 46行目 : GetMessage 関数。 ユーザの処理要求の監視を行っています。 ② 48行目 : TranslateAccelerator 関数。 この関数は、アクセラレータ キーの解析が終わるまでは、50行目 と 51行目の関数を呼び出さないようにするためのフラグを返すのが主な目的と推定します。 ③ 50行目 : TranslateMessage 関数 アクセラレータ キー処理が終わった時だけ処理を行い、それ以外は何もしません。この関数が処理を行うときに受け取っているメッセージは、複数キーが同時に押されていることを示すメッセージです。「猫でもわかる Windows プログラミング」6.3章の例題では、シフト キー、コントロール キー、数字1のキーの3つのキーが同時に押されているメーッセージです。このメッセージをそのまま送り出しても、WndProc イベント ハンドラー は、正しく処理できません。そこで、43行目の LoadAccelerators 関数で作ったテーブルから必要な caseラベル IDM_OPT1 を取り出し、MSG 構造体に入れなおします。言い換えると偽メッセージに交換しているのです。 ④ DispatchMessage 関数。 イベント ハンドラー WndProc 関数を呼び出しています。 上記処理の大前提として、43行目の LoadAcceletators 関数を呼び出しています。この関数呼び出しは、1回だけなのでこのプロジェクト処理に大きな影響負荷はありません。目的は、冗長なアクセラレーター キー処方箋から、高速処理ができる検索/変換表をメモリー上に作ることです。 このように複雑な処理を行っているので、無限ループも多少の負荷要因になっています。 |
|
||
4.2 章 GetMessage函数がエラーコード -1 を返す場合の処理について | ||
Part 10 参考文献「No.57 GetMessage 函数」に記載されている注意喚起について、日本マイクロソフト(株) Visual Studio サポート チームのマネージャーから長時間の説明を受け、得られた結論を以下に報告します。 結論 : 『Visual Studio 2013 の VC++ が自動生成するプロジェクト(このサイトでは、ベース プロジェクトと呼んでいます)をそのまま利用する限り、上記技術資料の注意喚起を適用する必要はありません。』 以下にその理由を記載します。 GetMessge 函数は、関数に与えられた引数に問題がある場合、 -1 のエラー コードを返します。具体的には、(A)第1引数の lpMsg に指定した MSG 構造体へのポインターが不正な場合、(B)第2引数 hWnd ウィンドウのハンドルが不正な場合です。自動生成コードはこの2つの条件に該当しません。また GetMessage 函数がローカル変数のアドレスを意図的に改変することもあり得ないので、Visual Studio が自動生成したコーディングでは、-1 のエラー コードが返されることは無いと断言できます。 私が長年働いた軍需産業では(原子爆弾の爆発による)強力な放射能や宇宙線の影響でメモリー内容が変化し、コンピューターが誤作動する現象の対応を検討したことがあります。このような原因で、コーディングにミスが無くてもコンピューターの誤作動はあり得ますが、民生品では100年に1度の天災と割り切るべきでしょう。 上に記載した -1 のエラー コードが設定される原因を知り、皆さんはどう考えますか?私は、重大なコーディング ミスによるもので、どのように深刻な事態が発生しようとも、全ての責任はユーザーにあると考えます。 これは、エラー コードを判定して 抜け出す処理を怠ったために招いた深刻な事態ではなく、メチャクチャなコーディングが招いた事態であり、ユーザーに同情する余地は全くありません。 では、なぜ上記の参考文献で注意喚起しているのでしょう。確かなことは判りませんが、GetMessage 函数の事故が続発したのが原因と推定します。しかし、Visual Studio 2013 が自動生成したコーディングは、Micorosoft 側の充分な検証に加え、世界中のユーザーによる長時間のテストに耐えた実績で、上記注意喚起の必要が無いと断言できるのです。その結果、Visual Studio 2013 が自動生成したコーディングには、上記技術資料の適用が不要なのが明白なので、適用してないのだと考えます。 なお、メッセージのフィルタリングについても付記しておきます。GetMessage 函数の仕様上、第3引数 wMsgFilterMin と第4引数 wMsgFilterMax にどのような値を指定しても WM_QUIT メッセージだけは必ず取得され、フィルタリングの対象になりません。この意味は、ユーザーがアプリケーションを終わりたいと判断した場合、最優先で終了させるための配慮です。 従って、システムが正常に作動しているときは、-1 のエラーコードが設定されていても、WM_QUIT メッセージを発行すれば、必ず停止できる設計上の配慮が行われているわけです。 しかし悲しいことに、Visual Studio で開発したアプリケーションは、この配慮を100%の確率では実現できない事情があるのです。終了処理は、CPU に1つしかないソフトウェアー割込みで処理しているはずだと何度か指摘しました。OS 内部での処理なら、割込み処理ハンドラーに終了処理を記載しておけば、必ず終了できます。しかし、ユーザーが書いたプログラムに割込み処理を公開できな理由が、OS 側には厳然と存在するのです。 この制約のため、 GetMessage 函数は、MSG 構造体に WM_QUIT メッセージの受信事実を記入して伝えるのですが、構造体のポインターが間違っていると書き込みは不可能で、終了処理の意思を呼び出し側に伝えることができません。他方、GetMessage 函数の返り値を False で返すと、ユーザーは何故無限ループが終わってしまったのか理解できません。 GetMessage 函数は、非常に古い函数だそうです。C言語の普及が始まった黎明期には、ポインターの誤使用によるトラブルが大きな話題になりました。Visual Studio による GUI 方式のプログラム開発黎明期にも同様のトラブルが多発したことは充分想像できます。そのような訳で GetMessage 函数公開後にエラー コード -1 を設定せざるを得なかったのだと推定します。 この Part の冒頭に記載した参考文献では、Visual Studio が自動生成したコーディングについて一言の言及も無く、結果的に危機感をあおり過ぎています。この Part の冒頭に記載した粂井さんの著書「猫」でも、説明なしで上記技術資料の注意喚起に応じています。私を含め多くの日本人が、「猫」で VC++ の学習をしています。この結果ほとんどの日本人は、この注意喚起を誤解しているものと思います。いいえ、世界中の人たちも誤解しているのでしょう。Microsoft に紹介された、次のブログ記事を参照してください。 Part 10 参考文献「No.102 When will GetMessage return -1?」 GetMessage 函数の技術資料に、Visual Studio 2013 が自動生成したコーディングへの対応についてコメントを追加するよう、日本マイクロソフトに求めました。危機感のあおり過ぎについて Microsoft の理解は得られましたが、技術資料への対応は不明です。 |
||
|
||
5章 Button の追加 | ||
|
||
ピンク色で囲った部分に、この関数で処理する「 caseラベル」の説明がコメント行で書かれています。このプロジェクトで追加する「WM_CREATE」識別子の説明を、ここに追加しておきます。 ④ 上図の WndProc 関数で赤枠で囲った部分は、宣言部分です。ここでボタンとラジオボタンのハンドルを宣言します。 ⑤ WinProc 関数には、緑色枠で囲った switch (message) ブロックがあり、さらにその内部に茶色枠で囲った switch (wmId) ブロックが入れ子になっています。 ボタンを追加するコードは switch (message) に、ボタンが押された時の処理コードは、 switch (wmId) に挿入します。 追加場所は、既に存在している先輩を尊重して、夫々 default ラベルの直前にしました。ただし、2つの switch() 文への挿入は順番があるので注意してください。 ボタン追加コードを先に挿入します。ボタンが押された場合の処理コードを先に挿入するとエラーになるので注意してください。 コピー&ペーストの注意 : 1つの文中に改行コードがある場合、複数行のコピー&ペーストを行うとエラーになります。必ずセミコロンで終わる完成した1行のコードにしてコピー&ペーストを行ってください。ペースト完了後は、適当な位置に改行コードの挿入ができます。 define 文や、case ラベル文などの完成した文は、この制限を受けません。 複数行を一括処理する場合は、追加する全コードを一旦、メモ帳に張り付け、改行コードをすべて削除し、各行がセミコロンで終わる文に加工したうえで、Visual Studio 2013のコードエディター画面に張り付けます。 改行コードを削除しないで、コピー&ペストする場合は、必ず1行ずつコピー作業を行ってください。 その他の注意 : ① CreateWindow 関数文の手動入力時に表示される、赤い波線の警告は、文最後のセミコロン入力で解消します。 ② インデントは、適当に処理してください。 ③ 手入力の場合、大文字、小文字、コロン、セミコロン、カンマなど間違わないように注意が必要です。 挿入するコードを次に示します。この順番で上で説明した位置に挿入してください。 ④ 上図の WndProc 関数で赤枠で囲った部分は、宣言部分です。ここでボタンとラジオボタンのハンドルを宣言します。 ⑤ WinProc 関数には、緑色枠で囲った switch (message) ブロックがあり、さらにその内部に茶色枠で囲った switch (wmId) ブロックが入れ子になっています。 ボタンを追加するコードは switch (message) に、ボタンが押された時の処理コードは、 switch (wmId) に挿入します。 追加場所は、既に存在している先輩を尊重して、夫々 default ラベルの直前にしました。ただし、2つの switch() 文への挿入は順番があるので注意してください。 ボタン追加コードを先に挿入します。ボタンが押された場合の処理コードを先に挿入するとエラーになるので注意してください。 挿入するコードを次に示します。この順番で上で説明した位置に挿入してください。 define 文のコード #define ID_BUTTON1 151 #define ID_RADIO_BUTTONA 161 #define ID_RADIO_BUTTONB 162 #define ID_RADIO_BUTTONX 163 #define ID_RADIO_BUTTONY 164 #define ID_RADIO_BUTTONZ 165 コメント部分のコード // WM_CREATE - CreateWindow() 関数処理により送られるメッセージ 宣言部分のコード HWND hwndButton; // ButtonTest HWND hwndRadio; // ButtonTest ボタン追加コード case WM_CREATE: hwndButton = CreateWindow(L"BUTTON", L"ボタン 1", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, 50, 50, 100, 50, hWnd, (HMENU)ID_BUTTON1, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); hwndRadio = CreateWindow(L"BUTTON", L"A", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON | WS_GROUP, 50, 150, 50, 50, hWnd, (HMENU)ID_RADIO_BUTTONA, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); hwndRadio = CreateWindow(L"BUTTON", L"B", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON, 150, 150, 50, 50, hWnd, (HMENU)ID_RADIO_BUTTONB, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); hwndRadio = CreateWindow(L"BUTTON", L"X", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON | WS_GROUP, 300, 150, 50, 50, hWnd, (HMENU)ID_RADIO_BUTTONX, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); hwndRadio = CreateWindow(L"BUTTON", L"Y", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON, 400, 150, 50, 50, hWnd, (HMENU)ID_RADIO_BUTTONY, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); hwndRadio = CreateWindow(L"BUTTON", L"Z", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON, 500, 150, 50, 50, hWnd, (HMENU)ID_RADIO_BUTTONZ, (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), NULL); break; ボタン処理コード case ID_BUTTON1: MessageBox(hWnd, L"ボタン 1 が押されました。", L"ボタン", MB_OK); break; case ID_RADIO_BUTTONA: MessageBox(hWnd, L"A が選ばれました。", L"ラジオ ボタン", MB_OK); break; case ID_RADIO_BUTTONB: MessageBox(hWnd, L"B が選ばれました。", L"ラジオ ボタン", MB_OK); break; // ButtonTest case ID_RADIO_BUTTONX: MessageBox(hWnd, L"X が選ばれました。", L"ラジオ ボタン", MB_OK); break; // ButtonTest case ID_RADIO_BUTTONY: MessageBox(hWnd, L"Y が選ばれました。", L"ラジオ ボタン", MB_OK); break; // ButtonTest case ID_RADIO_BUTTONZ: MessageBox(hWnd, L"Z が選ばれました。", L"ラジオ ボタン", MB_OK); break; |
下の図に define 文が追加された状態を示します。 |
||||
下の図は、コメントを挿入した状態です。 |
||||
下の図は、これまでの作業で完成した、ButtonTestプロジェクトの実行結果です。 グループ化されたラジオボタンを確認できます。 |
||||
「ボタン」 と 「ラジオボタン」 の作り分けは、CreateWindow 関数の第3引数に指定するキーワードで行います。「ボタン」は 「BS_DEFPUSHBUTTON」、ラジオボタンは、「BS_AUTORADIOBUTTON」
を指定します。 ラジオボタンのグループ化は CreateWindow 関数の第3引数に仕掛があります。 キーワード 「WS_GROUP」 を指定したラジオボタンから、グループ化が始まります。再度 「WS_GROUP」 が指定されると、その直前まででグループ化は一旦終わり、新しいグループ化が始まります。上図の例では、「ラジオボタンA」 と 「ラジオボタンX」 を作成するコードにだけ、このキーワードが指定されています。 CreateWindow 関数の第3引数は、第3章最後の「技術資料の入手方法」を参照してください。 ところで、上図のネイティブ コード アプリケーション「ButtonTest」のファイル サイズを次の図に示します。 |
||||
このアプリケーションのファイル サイズは、52KB、7.1章でも話題にします。 | ||||
|
6章 メニューの追加 | ||
この章では、 「ベース プロジェクト」にメニューを追加します。 「ベース プロジェクト」が素晴らしいのは、この作業が僅かな時間で終わってしまうことです。 この章の説明の基本、実は「猫」の丸コピーなので、是非「猫」を熟読ください。 まずプロジェクト名を MenuTest として 「ベース プロジェクト」を作ります。2.1章で説明したように、Visual Studio 2013 を起動し、『プロジェクト名入力』→『OK』→『完了』 の3ステップ、1~2分で完成でしたね。 ソリューション エクスプローラーを見ると、リソース ファイルの中に、「MenuTest.rc」と言う名前のファイルができています。(下図参照) |
||
この「ファイルを右クリック」→「コードの表示」で表示されるコードの一部を下図に示します。 ここに表示した11行のコードが 「ベース プロジェクト」のメニューバー作りの処方箋なのです。 |
||
/////////////////////////////////////////////////////////////////////////////
// // メニュー // IDC_MENUTEST MENU BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "アプリケーションの終了(&X)", IDM_EXIT END POPUP "ヘルプ(&H)" BEGIN MENUITEM "バージョン情報(&A)...", IDM_ABOUT END END |
||
1行目 : IDC_MENUTEST MENU 「 IDC_MENUTEST」は、メニュー名で、ウィンドウ クラス構造体の lpszMenuName メンバーに登録されています。 「MENU」は、このフィルのキーワードで、「 IDC_MENUTEST」がメニューであることを示しています。 2行目 : 「BEGIN」 メニュー定義の始りを示すキーワードで、対応する「END」キーワード(11行目)までのブロックを構成しています。 3行目 : 「POPUP ”ファイル(F)”」 「POPUP」は、キーワードで、続く文字列名のサブ メニューを作ります。「&」文字は、次に続く文の最初の1文字をショートカットキーにし、サブ メニューが表示されていないときは、Alt + ショートカットキー で、サブ メニューが選択状態で表示され、Enter で実行されます。サブ メニューが表示されているときは、ショートカットキーだけの入力でサブ メニューが実行されます。このコードでメニューバーに 「ファイル(F)」 のメニューが表示されます。 4行目 : 「BEGIN」 2行目同様処理の始まりを示すキーワード。6行目の「END」までのブロックを構成しています。 5行目 : 「MENUITEM "アプリケーションの終了(&X)" IDM_EXIT」 ポップアップ メニューに「アプリケーションの終了(X)」作成する。「IDM_EXIT」は、サブ メニューの識別子で wndProc 関数の case (wmId) 中で識別子に該当する機能が実行されます。「IDM_EXIT」は、プログラムを終了処理をする識別子でしたね。 6行目 : 「END」 4行目の「BBEGIN」で始まる手続きの終了を示します。 7行目 : 「POPUP "ヘルプ(&H)"」 3行目と同様。 8行目 : 「BEGIN」 4行目と同様。10行目の「END」までのブロックを形成。 9行目 : 「MENUITEM "バージョン情報(&A)" IDM_ABOUT」 5行目と同様。ポップアップ メニュー「バージョン情報(A」を作成する。「IDM_ABOUT」は、バージョン情報の表示を行う識別子。 10行目 : 「END」 8行目の「BEGIN」からのブロックの終了を示す。 11行目 : 「END」 2行目からのブロックの終了を示す。 |
||
この 「ベース プロジェクト」に メニューを追加するために、上のオリジナル ファイルを次図のように修正したいのです。C++ は、行の概念がなく、1行の使い方は自由です。「猫」の書式とは少し異なっています。 青色枠で囲った部分を 「ベース プロジェクト」が作成したオリジナル ファイルに挿入してください。 このファイルは、既存メニュー「ファイル」 と 「ヘルプ」の間に「都道府県」 と 「アジア」の2つを追加します。メニュー構成は、下の通りで、「愛知県」 と 「東京都」には、サブメニューがあり、「名古屋市」には、さらにサブメニューがあります。 「都道府県」 「愛知県」 「名古屋市」 「中区」 「東区」 「西区」 「岡崎市」 「豊田市」 「豊橋市」 「東京都」 「中央区」 「新宿区」 「品川区」 「大阪府」 「北海道」 「アジア」 「日本」 「韓国」 |
||
上の図で、メニュー、サブメニューの作り方が理解できると思います。上の図の数字の 99 のところには、メニューそれぞれの識別子を記入しますが、この例では処理コードを書かないので、コンパイルエラーを避けるため、適当な値 99 が書いてあります。上の図で、意味があるのは緑色枠で囲った2つの識別子だけです。識別子は、「Resource.h」ヘッダーフィルに define 文で記述します。 メニュー追加の確認をするには、次のコマンドを正しい位置に挿入してください。ペーストした後上の図を参考に、コードの見栄えが良くなるようにインデント調整をしますが、この作業は必須ではありません。 |
||
|
||
実行した結果の図を次に示します。他のメニューも試してみてください。 下の図は、「都道府県」→「愛知県」→「名古屋市」→「東区」と進んでいます。東区なら、さしずめナゴヤドームのPRでもするのでしょうか?そのアプリケーションは、WndProc 関数の中に記述してください。 その時は、上の図の MENUITEM "東区" 99 は、下のように書き直されるはずです。 MENUITEM "東区" PC_NAGOYA_EAST 加えて、Resource.h ファイルに、次の define 文の追加も必要ですね。 #define PC_NAGOYA_EAST 12345 さらに、WndProc 関数の switch (wmId) の中に、 case 文が次のように追加し、その下には、処理コードの記載が必要です。 case PC_NAGOYA_EAST: |
||
|
||
「ベース プロジェクト」が素晴らしいのは、メニュー追加のために編集が必要な 「MenuTest.rc」ファイル と 「Resource.h」 ファイル がすでに出来上がっているので、追加作業が簡単なことです。 「猫」用プロジェクトでは、「MenuTest.rc」ファイルを自分で作らなければなりません。この作業は、少々注意が必要です。 「ソリューション エクスプローラー」を右クリック→「追加」→「新しい項目」を選んで、「MenuTest.rc」ファイルを作った時点で、「Resource.h」ファイルも自動的に作られます。もし、「Resource.h」ファイルを先に自分で作ると、「MenuTest.rc」ファイルを作った時でシステムが別のインクルード ファイルを作ってしまいます。大きな不具合はないと思いますが、管理ファイルが増え、全体構成の美しさが欠けることになります。 |
||
|
||
|
7章 アクセラレータ キーの追加 | ||
アクセラレータ キーの追加は、横着をして「猫」の例題を丸々お借りすることにします。説明文も是非、「猫」を熟読してください。 まず、猫用プロジェクトとベース プロジェクト利用の差異を説明しておきます。 「猫」の場合は、学んだ技術をもとに、1つの完成プロジェクトとして、「ファイル」、「オプション」、「ヘルプ」メニューを作ると同時に、アクセラレータ キー機能を実現しています。 「ベース プロジェクト」利用の場合は、「ファイル」メニューと「ヘルプ」メニューがすでに完成しているので、2つのメニューの間に、「オプション」メニューを挿入し、アクセラレータ キー機能も実現しています。結論として、2つの手法に違いはありません。 しかし、「猫」は main 関数に WinMain 関数を使い、「ベース プロジェクト」は _tWinMain 関数を使っています。main 関数が違うので、「猫」で学んでいる方は心配かもしれませんが、関数の看板が違うだけで、利用する API は完全に同じです。2つの main 関数の違いを、今の私には正確に説明できませんので、しばらくペンディングにさせてください。多分、コンパイル時のオプション適用の可否程度の差だと思います。 コードを追加するのは、次に示す3つのファイルです。 Ⅰ Resource.h ヘッダー ファイル Ⅱ xxx.rc リソース ファイル(xxx は、「ベース プロジェクト」作成時に指定したプロジェクト名) Ⅲ WndProc 関数の switch (wmId) ブロックの内部 例によって、AccelKeyTest などのプロジェクト名で、「ベース プロジェクト」を作ります。 ① Resource.h ファイルの最後尾に、次の4行のコードを挿入します。コードの内容は、選ばれたメニューの識別子とその値を設定する define 文です。 |
||
// アクセラレータ追加始り+++++++++++++++++++++++++++++++++++++++++++ #define IDM_OPT1 1103 #define IDM_OPT2 1104 // アクセラレータ追加終り------------------------------------------- |
||
② AccelKeyTest.rc ファイルを開き次図上の赤色矢印で指す位置に、オプションメニューを作る、6行のコードを挿入します。 ③ 同じ図の下の赤色矢印位置に、アクセラレーター文字を定義する、4行のコードをコードを追加します。 挿入するコードは、下に示します。 |
||
オプションメニューを作るコード // アクセラレータ追加始り+++++++++++++++++++++++++++++++++++++++++++ POPUP "オプション(&O)", BEGIN MENUITEM "オプション(&1)\tShift +Ctrl + 1", IDM_OPT1 MENUITEM "オプション(&2)\tShift +Ctrl + 2", IDM_OPT2 END // アクセラレータ追加終り------------------------------------------- アクセラレータ文字を定義するコード // アクセラレータ追加始り+++++++++++++++++++++++++++++++++++++++++++ "1", IDM_OPT1, VIRTKEY, SHIFT, CONTROL, NOINVERT "2", IDM_OPT2, VIRTKEY, SHIFT, CONTROL, NOINVERT // アクセラレータ追加終り------------------------------------------ |
||
④ WndProc 関数の次の図に示す位置に、メニューが選択されたときに、メッセージを表示するコード 8行を挿入します。 |
||
メニューが選択されたときに、メッセージを表示するコード // アクセラレータ追加始り----------------------------------------- case IDM_OPT1: MessageBox(hWnd, TEXT("オプション1 です"), TEXT("Option1"), MB_OK); break; case IDM_OPT2: MessageBox(hWnd, TEXT("オプション2 です"), TEXT("Option2"), MB_OK); break; // アクセラレータ追加終り----------------------------------------- |
||
実行結果を次の図に示します。 |
||
|
||
7.1章 他の main 函数の例 | ||
7章の冒頭で main 函数を話題にしましたので、7章本来の趣旨とは違いますが、 Hello World! を表示する伝統的な main 函数の例を記載してみます。 Visual Studio 2013 を起動し、「新しいプロジェクト」を選ぶと下の図が表示されます。 |
||
上の図の赤枠で示すように次のように選択します。 左 Pane の「テンプレート」 → 「全般」 を選択します。 右 Pane の「空のプロジェクト Visual C++」を選択 → 「OK」 を選びます。 |
||
上の図に示すように、左 Pane の「ソース ファイル」を右クリックし、表示されるプル ダウン メニューから「追加」を選び続いて「新しい項目」を選びます。 次の図が表示されます。 |
||
「C++ ファイル Visual C++」を選び、「追加」を選びます。この結果次の図に示すように、ソース コードを入力する画面が表示されます。 |
||
上の図に示す、8行のソース コードを入力します。このコードは多くの C++ 解説書に記載されています。6行目の getchar(); は、コマンド
プロンプト画面がすぐに消えない仕掛けです。 詳細な画面ショットは省きますが、上図に示すように、「Release」を選び、ランタイム ライブラリは、「マルチ スレッド(/MT)」を選んでおきます。 |
||
上図に示すように、完成したネイティブ コード のアプリケーションのサイズは、152KBです。ところで、5章で説明した ”ButtonTest”
のファイルサイズは、52KBでした。100KB も違うこの差は、ここで紹介した Hello World! を表示するアプリケーションが Cmd.exe を利用しているからです。Cmd.exe は 334MB の非常に大きなプログラムです。 たった10文字あまりを表示する Hello World! に比べ、”ButtonTest” は、拡張性を期待させる高度なアプリケーション プログラムだと思います。Visual Studio に搭載された VC++ が大きな未来を開いてくれそうです。 |
||
実行結果は、上図のとおりです。 |
||
|
||
8章 UNICODE 文字列の表記 | ||
私が使用した UNICODE 表記は様々です。UNICODE 表記が必要な場合、次のような表記が使用できますので、びっくりしないでください。 L"ボタン" TEXT("ボタン") _TEXT("ボタン") __TEXT("ボタン") _T("ボタン") __T("ボタン") UNICODE 環境専用や、UNICODE マクロ定数を判読して UNICODE 環境と MBCS(Multiple Byte Character Set) 環境の両方で使えるものがあります。 |
||
|
||
9章 C++ アプリケーション開発の注意事項など | ||||||||||||||||||||
20年前C言語で、初歩的な GUI プログラムを開発するのは、大変な作業でした。現代のC++ は、いとも簡単にネイティブのアプリケーションを作り上げてくれそうです。技術の進歩に愕然とし、浦島太郎は、竜宮城に住み続ければよかったとも思います。誤解をしないでください。現代社会のほうが竜宮城よりはるかにキラビヤカで、将来への希望も格段に大きいのです。 最後に、C++ 環境下でのアプリケーション開発を行うための、注意事項などをまとめておきます。 |
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
9.1 章 技術資料の入手方法 | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
9.2章 ボタン作成に関係する Microsoft 技術資料リスト | ||||||||||||||||||||
MSDN 技術資料の基本的入手方法は、前章で説明しました。ここでは、ここまでに説明した内容に関係があると思われる技術資料の URL を記載します。 ① Win32 ベースのアプリケーションの作成 (C++) < http://msdn.microsoft.com/ja-jp/library/bb384843.aspx > Kerninghan & Ritchie 時代から定番の 「Hello! World!」 の文字列を表示するプログラムが作れます。英文ですが、親切な説明が添付されているので、C++ 入門には、最適の資料です。私もこの資料で、C++ の扉を開きました。一番最後に、『コピー』と書かれた大きな四角形の枠の中に、コードのフルセットが記載されていますので、『コピー』部分をクリックするだけで、簡単にコピーできるのでペーストしてしてみてください。是非この資料で、C++ の扉を開いてください。 なお、 main 関数は、WinMain を使っています。 ② Video How to: Creating Win32 Applications (C++) - Watch the video < http://go.microsoft.com/fwlink/?LinkId=102470 > ① のビデオ説明です。年寄りには、英語は疲れます。 ③ CreateWindow 関数 < http://msdn.microsoft.com/ja-jp/library/cc410713.aspx > 「ベース プロジェクト」で多用した関数の資料です。前の章に一部転記した lpClassName の説明表は、この資料から転記しました。 ④ How to Create a Button < http://msdn.microsoft.com/ja-jp/library/windows/desktop/hh298354.aspx > この資料でボタンは、 CreateWindow 関数で作ることがわかります。次章で説明しているこの 関数の第3引数(dwStyle)の正解がさりげなく書かれています。 座標の単位は、ビクセルです。 ⑤ ボタン スタイル < http://msdn.microsoft.com/ja-jp/library/tf9hd91s(v=vs.100).aspx > BS_AUTORADIOBUTTON など、"BS_" で始まる既定値のドキュメントです。 ⑥ ウィンドウ スタイル < http://msdn.microsoft.com/ja-jp/library/vstudio/czada357(v=vs.100).aspx > WS_CHILD など、"WS_" で始まる既定値のドキュメントです。 ⑦ GetWindowLong < http://msdn.microsoft.com/ja-jp/library/cc364760.aspx > GWL_HINSTANCE など、”GWL_” で始まる既定値についての解説が記載されています。 ⑧ GetNativeSystemInfo function < http://msdn.microsoft.com/ja-jp/library/windows/desktop/ms724340.aspx > ⑨ SYSTEM_INFO structure < http://msdn.microsoft.com/en-us/library/windows/desktop/ms724958.aspx > ⑩ printf、wprintf (MSDN) < http://msdn.microsoft.com/ja-jp/library/ms396940.aspx > |
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
9.3章 CreateWindow 関数 | ||||||||||||||||||||
入手した MSDN 技術資料を活用するには、多くの困難があります。私の場合、センスがアメリカ人と違うために理解できないのが一番大きい理由ですが、完備した技術資料がないのも現実です。また、技術資料に書かれている内容の真意が理解できないことも多くあります。その結果、力任せに色々試して解決策を見つけるドロ臭い手法に頼ることも多くなります。この章で最も多く活用した CreateWindow 関数を例に、私のドロ臭い手法を紹介します。 この関数の第3引数(dwStyle) の決め方が重要なポイントになります。ボタンの例では、次のように、4つのシステム定数の論理和です。 WS_、 BS_ で始まる多くの定数がシステムで定義されています。この英単語を見ると、なんとなく「この定数も必要なんだ」と思えるでしょうが、最初から自分で決めるとなると大変です。 |
||||||||||||||||||||
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, |
||||||||||||||||||||
まず、この第3引数の前後に NULL を付けて次のように書き直します。NULL は、ゼロなので追加しても論理和の結果には影響がありません。 |
||||||||||||||||||||
NULL | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | NULL, |
||||||||||||||||||||
前にも書きましたが、C++ には行の概念がありません。これを利用し、キーワードごとに改行コードを挿入して、次のように書き直します。 |
||||||||||||||||||||
NULL | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON |NULL, |
||||||||||||||||||||
修正したコードを下図に示します。 |
||||||||||||||||||||
実際の定数候補は、もっと多いでしょう。そのうえ、前述の Microsoft 技術資料リスト ⑤~⑦ など多くの資料を調べねばなりません。 これらの定数候補行を順次コメントアウト/追加して色々な組み合わせで動作を確認します。上図の先頭と最後に追加した NULL 文は組み合わせが決まるまで残しておきます。 コメントアウトと解除は、下図の赤枠で示すボタンを使います。このボタンが表示されない場合は、右にある小さな下向き矢印を押して追加します。 組み合わせの確認は、下の緑枠ボタンの出番です。 |
||||||||||||||||||||
|
||||||||||||||||||||
|
9.4章 メニューのリソース ファイル |
BEGIN と END のネスト(入れ子)は矛盾がないように、注意してください。 |
上の図で、太い赤線で結んだのは、Visual Studio 2013が作った部分、細線で結んだのは、私が作った部分の BEGIN と END のブロック関係を示しています。 POPUP キーワードのシンタックスは、 「POPUP メニュー名文字列」 と「猫」で説明されています。しかし、その次に 「BEGIN」 キーワードが必ず必要なので、私は勝手に次のように決めました。 POPUP メニュー名文字列, BEGIN MENUITEM キーワードのシンタックスは、次の通りです。 MENUITEM メニュー名文字, 識別子 ただ識別子として上図のように、直接数値を書くのは、誉められません。前にも説明しましたが、これはコンパイラーがエラーを検出して、次に進めなくなるのを避けるための便宜措置です。当然のことですがメニューは、選択された時の処理を WndProc 関数中の switch (wmId) ブロックの中に記載しなければなりません。そのためには、上図の IDM_EXIT のように動作が想像できる識別子の名前と値を決め、インクルード ファイルに登録し、コードでは 識別子名を使うのが、誤りの少ないプロがラムを作るための工夫です。 説明が長くなりました。結論は、2つのキーワードのシンタックスを次のように決めます。根拠は、C++ には、行の概念がないからです。 POPUP メニュー文字列, BEGIN MENUITEM メニュー文字列, 識別子 退屈な話をもう少し聞いてください。キーワードや文字列、識別子の間の区切り文字はどうするのでしょう。一般的な区切り文字は、カンマですね。現状は、かなりいい加減ですが、同じファイル中に記述されている「アクセラレータ」部分の処理は、同じファイル中なのにかなり厳格です。したがって、次のように統一使用するべきでしょう。 POPUP と MENUITEM の後 : ブランク その他 : カンマ 改行前 : カンマは有っても無くてもかまいません コードは、上の図を参考に作成します。 |
|
Copyright(c) 2014 Takashi Shima Email : TakashiShima0629@yahoo.co.jp |