リンクフリーを近日中にとりやめる予定です

すでにリンクを貼っていただいている方、ご一報頂きたくお願い申し上げます。


ごく少数ですが、リンクをお断りする場合があります



ブログ内 風景光景カテゴリー

続編記事などをご希望の方は こちらへどうぞ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[未掲載分] 車輪の再発明 (5)

元来、好ましい意味で用いられない「車輪の再発明」。



筆者は、そのような行為を肯定的に捉えている。
なぜならば、新しい技術や文化が芽生えるきっかけは、一見無駄と思われることへの挑戦である・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています


この題目は2012年暮れ頃に掲載しようと下書きし、諸事情により掲載却下とした分です。
前話、車輪の再発明 (4)にひきつづき、利用環境を OS はWindows , CPU は Intel 製、いわゆるウィンテル機を前提に綴ります。

前話までで、アプリの骨格を作る話題に触れた。今回は肉付け、アプリを作る過程では初歩的なことを綴ってゆこう。
Intel 製 CPUを搭載したPC、OS は Windows 7 、Visual Studio を用いる 、C/C++ 言語、など諸条件は以前と同じ。

この題目のきっかけとなったブロガーさんは、学業の合間をみてプログラミングを身につけたい様子。
全てのコードをここに載せれば解決しそうな話だが・・・理解いただけるとも思えない。

きっかけとなったブロガーさんより
「○○の書いたコードを見てみたい」との連絡がありました。
筆者は、
「他人様にコードを書いてもらうと高くつきますよ~」とお茶を濁しておいた。
その返答では、「○○はアプリが組めない」等の疑惑をもたれるてもいたしかたない。

コードを書いてほしいとリクエストされた内容、筆者にとっては数時間で書けそう。アプリをひとつ作るのに比べればごく一部。家を一軒建てることに例えれば、玄関だけ、キッチンだけ作るようなもの。って、実際何かを作るとなると、それで終わりではない。全体が仕上がってこそ意味がある。

返答の真意としては、「自分で課題をこなさないと力が付かない」の旨であったが、想いが通じたが不明。
たしかに、時代を問わず代行作業を引き受ける者が介在する。実生活の場では代行依頼もアリだ。しかし、学び・向上を目的とするならば如何なものか・・・

実生活の場で「○○はアプリが組めない」などと疑われようが、挑発的な言動には無視・放置。
このブログにおいて、2013年2月21日分の記事をはじめ、2012年8月24日2012年8月22日2012年6月19日2012年6月29日などの記事中、筆者が作ったアプリの画像が載っているハズ。
鋭いヒトが見れば、「筆者は入門書の練習課題などに四苦八苦しているレベルでない」ことはお察しいただけるだろう・・・

前話、車輪の再発明 (4)まででウィンドウを作成、表示、実行することを触れた。若干手を加えて、速度の違いを目で見て確認できるように変えてゆこう。

このお題の根底にあるのは
「~~することにより速度が向上する???」

速い遅いを把握するため、「何かの合図をしたら速度の測定を開始する」ように加工する。
ウィンドウ、フォーム上にボタンを配置、そのボタンをクリックすることで開始の合図とする仕組みが手っ取り早い。

既に前回までの流れでウィンドウ、メインフォームが表示されるようになっている。
今後、おおまかな流れとしては
(1) ボタンを作成
(2) 下記(3) を呼び出す準備
(3) 速度を測る部分を作成

の3点。

(0) その前に、SSE2 命令を使えるように修正する
SSE2 を使った場合とそうでない場合を比較する流れで話を進めてきた。

Visual Studio を使っている場合はソリューションエクスプローラ



ツリービューの中、ヘッダーファイル「stdafx.h」を開く。


※ stdafx.cpp と間違わないように注意

ヘッダーファイル「stdafx.h」の終盤、
// TODO: プログラムに必要な追加ヘッダーを ~~
の後に
#include <mmsystem.h>

#pragma comment(lib, "winmm.lib")
#include <emmintrin.h>

を追加して保存。

修正したファイルを保存するには、キーボードの[Ctrl]を押しながら[S]を押す。
複数のファイルを修正した場合はキーボードの[Ctrl]と[Shift]を押しながら[S]を押せば一括保存となる。
不慣れな段階のヒトは最終行が改行で終わっていることを確認してから保存する習慣を・・・


キーボードを使わなくとも、



開いているタブを右クリックし、表示されたメニューより「~~ の保存(S)」をクリックして保存することも可能。

追加した部分を簡単に説明すると、
mmsystem.h と winmm.lib は時間を取得するのに必要。本来は音声や動画データを扱うためのヘッダーとライブラリファイル。今回は時刻・時間を取得する目的でインクルードする。

emmintrin.h は SSE2 命令を使うのに必要なヘッダーファイル。SSE3 , SSE4 などを使いたいならば tmmintrin.h 、pmmintrin.h 、smmintrin.h などをインクルードすることになる。
Visual Studio ならば あれこれインクルードするのではなく、intrin.h ひとつをインクルードすれば済む。
ただし、Visual Studio 2005 に関しては intrin.h に重大な問題 (error C2733)が2行含まれているので注意が必要。
emmintrin.h が使えるのは Visual Studio .NET 2003 くらいから。
ちなみに、Visual Studio 6.0 でも Processor Pack を導入することでSSE2 命令の利用が可能になる。VC6 なら、Visual C++ ToolKit 2003 も使える。

(1-1) 開始の合図を決める

作業としては「Resource.h」という名前のヘッダーファイルに2つ修正を加える。
ひとつめの修正、実行開始のメッセージ、数値を決めます。



修正箇所が少ない段階ならば、メモ帳を使って「Resource.h」に修正を加えるのも良いだろう・・・

Visual Studio を使っている場合は左ペイン、ソリューションエクスプローラのツリービューの中、
ヘッダーファイル「Resource.h」を開く。

Resource.h ファイルを開くと #define ID~~ が並んでいる。



これらは「#define ディレクティブ」や「プリプロセッサディレクティブ」と呼ばれる。
マクロを定義したり、特定の文字列の置き換えとして用いたり、コンパイル条件を指定する。
このファイルにある #define ID~~は、アプリの中でやりとりされるメッセージの数値が定義されている。

例えば、アプリが終了するときには「IDM_EXIT」と空白、それに対応する数値として「105」が割り当てられています。
「IDM_EXIT」の部分は「識別子」と呼ばれ、英数字やアンダースコア、空白を挟んで右の数値は、その識別子と対応する数値やマクロ。プログラムコード中に「IDM_EXIT」となっている箇所は、翻訳時に「105」に置き換えられる。

もちろん、識別子・定数に頼らず、プログラム中に直接数値を埋め込むことも可能です。が、後日修正の際などに手間が増えるだけです。何らか修正する際、直接埋め込んだ場合は全てを書き換えることになります。規模が大きくなるほど修正漏れも生じ易くなります。
それに比べ、定数を用いるならば「#define ~~ 」で定義した内容を1箇所変更すれば全体に反映されます。全ての箇所を修正するよりも楽なのは明白。

「#define」の後に続く識別子の文字長は32文字までが有効とされています。それより長い文字列はエラーになるか、コンパイラによっては無視されます。

「IDM_」「IDC_」や「IDR_」の意味や規則性に関しては MSDN ライブラリに詳細が載っているのでここでは割愛します。
知りたいヒトのために、以下に検索用リンクを貼っておきます。
テクニカルノート 20
テクニカルノート 20 (の原文)

今回は「IDM_」に実行の「RUN」を付け、実行開始の合図を「IDM_RUN」と決め、数値は直前までと衝突しない「106」とし、
「#define IDM_EXIT ~」の次の行に「#define IDM_RUN 106」を加えました。
この例では偶々「106」が空いていたのですが、新しく加えるID番号が既存と重複しないことが重要。

ふたつめの修正は「Resource.h」の終わりの数行に含まれている
「#define _APS_NEXT_~~_VALUE 数値」の部分をチェック。
この数値は、次回割り当てる番号。現状で割り当てられている数値の中で最も大きい数プラス1になっているのが望ましい。( すでに割り当ててある番号との重複を防ぐため )

「#define _APS_NEXT_COMMAND_VALUE ~」 や「#define _APS_NEXT_SYMED_VALUE ~」で割り当てられる数が適切か手動でチェックしたほうが良いでしょう。
今回は自ら追加したID「IDM_RUN」に「106」を割り当てた。よってプラス1の「107」より小さい数が割り当てられているならば要修正。

この辺もMSDN ライブラリに説明があるハズですので検索用リンクを貼っておきます。
テクニカル ノート 35

今回は簡単な例なので、リソーススクリプトやヘッダーファイルを直接書き換えました。ちなみに、筆者は普段ID などの定義は別の方法・・・

(1-2) アプリのフォームに開始ボタンを着ける

まず、ボタンを作成した際に返ってくるハンドル値を保存できるようにする。

ハンドルのことを大雑把に言ってしまえば、ウィンドウやボタン等のコントロールは異なる番号が割り当てられており、この番号のことを指す。言い換えれば、スポーツ選手の背番号みたいなもので、各々が独自の番号を持つことで識別が容易になります。
もし、この番号が無いとすれば
「複数のボタンのうち何番目がクリックされたかを知る」や
「複数のウィンドウが同時に開いていて、2番目のウィンドウを前に出す」などが難しくなります。

ハンドルを受け取る変数として
static HWND hBtn = NULL;

追加する。追加する場所は



LRESULT CALLBACK WndProc(HWND ~~
HDC hdc;
の後ろあたり。

HWND の前の「static」を付け忘れるとハンドルの値は正しく保持されない。
「= NULL」の意味としては初期化。頭に「static」を付けるのは、この変数が初めて使われる時だけ初期化される。
「static」を付けずに「= NULL」が付いている場合、関数が呼び出される毎に変数が初期化されてしまうのだ。つまり、作成したボタンのハンドルは保持されない。
今回ボタンはひとつで足りる。複数のボタンを配置したいときは配列変数を用いると良いだろう・・・

この段階で、アプリケーションウィザードによって作成されたコードは



となっているハズ。「case WM_COMMAND:」の前に
case WM_CREATE:
    hBtn = CreateWindow(_TEXT("BUTTON") , _TEXT("開始\0") ,
        WS_CHILD | WS_VISIBLE ,
         10 , 10 ,
        160 , 32 ,
        hWnd , (HMENU) IDM_RUN ,
        hInst , NULL);

        return 0;

を挿入する。
試しにビルド、実行。アプリを実行するには、キーボードの[F5]キーを押す。



アプリのフォーム上に開始ボタンが着いたハズ・・・

定数「WM_CREATE」はウィンドウが作成される際、つまり、アプリのフォームが表示される前に送信される。
「アプリが初めて表示される時に行いたい処理」を書いておく。
CreateWindow や CreateWindowEx 関数 が実行されるのと同時。

どこで実行されるのか知りたいヒトもいることだろう。今回の例では InitInstance 関数 の中で実行される。



アプリケーションウィザードで自動的に作成されるコード、InitInstance 関数の中に
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
という部分がある。この CreateWindow 関数を実行するとき、「WM_CREATE」以下が実行される。

ボタンの作成を失敗した時にエラーとみなしたいならば「return 0;」の前に
if (!hBtn) return -1;
を追加挿入すると良いだろう。
「return 0;」以外で終わる時は、CreateWindow 関数が正常に終了しなかったことを意味する。

ところで、「case WM_CREATE:」と対になるのは「case WM_DESTROY:」。
Create とDestroy 、つまり「作る」と「壊す」の関係。
ここでの Destroy は「壊す」というより、ウィンドウが閉じる際の「お片付け」「清掃」と考えると良い。

CreateWindow や CreateWindowEx といった関数はウィンドウ作成する。
ほかにも、ボタンやドロップダウンリストといったコントロールを作成する際にも用いられる。

_TEXT("BUTTON") などの _TEXT("") 、 _T("") といった部分は、文字列を Unicode で扱うか否かを楽にするオマジナイ。
初歩的な教本などでは Unicode を意識していないサンプルが載っている一方、現状は Unicode を意識したアプリ作成が主流。
例えば、文字列の型を宣言する際も char や wchar_t と直接書くのではなく、TCHAR 型で宣言する。
ビルドする時の状況に応じて Unicode 用のコードかそうでないコードを生成するか、コンパイラに任せてしまうのが楽。
なお、Windows での利用を想定したアプリでは、文字列の末端、ダブルクォーテーションの直前、半角英数で「¥0」を忘れないように。

(HMENU) IDM_RUN の部分で、「このボタンをクリックするとIDM_RUN というメッセージを送る」を意味する。
そのメッセージを受信するのは「case WM_COMMAND:」となる。後で述べる。

CreateWindow 関数と対になる DestroyWindow 関数もある。
一般的にウィンドウを破棄する際、( ユーザから見てウィンドウが閉じられる際 ) 子ウィンドウやコントロール類も自動的に破棄されることになっている。つまり、OS ( Windows ) が後片付けを行うことになっている。

律儀に「case WM_CLOSE:」「case WM_DESTROY:」あたりで DestroyWindow 関数 を付ける例も見かける。
DestroyWindow 関数を用いるならばウィンドウハンドルが無効でないことを確認するのが良い。
表面上、DestroyWindow 関数 や DestroyMenu 関数で無効なハンドルを解放してもエラーが出ない。のだが、深く追跡すると何らかの問題が生じているものだ。

ハンドル値を保持する変数を hw とするならば
if (::IsWindow( hw ) ) ::DestroyWindow( hw );
のように、IsWindow 関数を呼び出してから破棄するのが良策。
さらに安全を考えるならば
if ( hw ){if (::IsWindow( hw ) ) ::DestroyWindow( hw );}

くらい慎重な方が頼もしい。

プロシージャ内で「WM_ ~~」となっているのは、アプリに届くメッセージ。
例えば、アプリがサイズ変更された際には「WM_SIZE」、右クリックされた際には「WM_RBUTTONDOWN」などが通知される。
「WM_ ~~」として定義されているメッセージは多数あり、全てのメッセージに対応したコードを書く必要はない。
不慣れな段階であれば、対応するコードを省きOS に任せてしまおう。

※ 通常、「case ~:」と対になる「break;」を設置しますが、ここでは省いています。「return 0;」で脱出するため、「break;」を置いても到達できない・・・

※ 「WM_ ~~」 メッセージの定義は Winuser.h ファイルの中、もしくは 、MSDN ライブラリに詳しく載っています。

(1-3) 開始の合図 - ボタンを使わない場合
たしかに「いちいちボタンを作るのが面倒」との声も聞えてきそうだ。
ボタンを作成しなくとも、メニューバー、つまりアプリ上方に表示されるメニューに「開始」があればよい。
これは、リソース編集で簡単に修正できる。



しかし、Visual Studio の中でも Express エディションではリソースの編集が出来ないとされている。
その場合、リソーススクリプトをメモ帳で開き
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "アプリケーションの終了(&X)", IDM_EXIT
    END
を探そう。BEGIN と END の間に
MENUITEM "開始 (&G)", IDM_RUN
と加え、リソーススクリプトを上書き保存する。

ビルド、実行してみると、メニューバーに「開始 (G)」が加わったハズ。



筆者の私見を述べるが、今後のアプリ作成はメニューバーに依存しない流れになるだろう。旧来からのメニューバーはマウス操作には適しているのだが、今後は指先を使うタッチ操作が主流となる。

(2)開始の合図「IDM_RUN」が届いたら
速度を測るための関数を呼び出す。

(2-1) 新設する関数の名前を決めておく。
しばしば、パフォーマンスを比較・評価するのに「ベンチマーク」と言い、何かを開始するのは「Execute」。そこで、関数名は「execBench」とする。

(2-2) 関数を増設する
関数内、実行途中でエラーが生じた場合は、そのエラーを返すようにする。
その際、変数の型に注意。
一般的な、というか古典的な教本では値を返すような関数は 「int」 で扱われている。
Windows に限って言えば、「int」ではなく「LONG」や「HRESULT」。失敗か成功だけを問うならば「BOOL」でも良いだろう。

64ビットのプログラミング環境が意識された頃から「LONG_PTR」など「~~_PTR」といった変数の型が用いられるようになってきた。これはプラットフォームが32ビットなら32ビット幅の「LONG」つまり「LONG32」、64ビットなら「LONG64」。

とりあえず、戻り値の型を「LONG」として
LONG execBench(HWND hOwner)
{
    return NO_ERROR;
}

を追加。場所は LRESULT CALLBACK WndProc( ~~ より前が良い。もちろん、それより後方に設置することも可能だが関数プロトタイプを置き、事前に宣言せねばならない。
今回に限っては、int APIENTRY _tWinMain(HINSTANCE ~~ の直前に追加するのが判り易いかも。

この段階でexecBench の中身は空。呼び出すことはできるが、何も作業せず、NO_ERROR つまり OK と返事をするだけ。

(2-3) 増設した関数を呼ぶ準備
execBench 関数 からの返却された値を受け取るための変数を準備する。
LONG l;
追加する。場所としては先ほど
static HWND hBtn = NULL;
を追加した直後あたり。
execBench を実行した結果 (成功もしくは失敗のお知らせ)が 整数型の変数 l に入る。

そして、「IDM_RUN」が通知されたとき、execBench 関数を呼ぶためのコード
case IDM_RUN:
    ::EnableWindow(hWnd , FALSE);
    l = execBench(hWnd);
    ::EnableWindow(hWnd , TRUE );
    break;
を追加する。場所は「case WM_COMMAND:」の下



「case IDM_EXIT:」より後ろ「break;」と「default:」の間。

EnableWindow 関数でウィンドウの有効 / 無効 を切り替える。execBench を呼び出す前に アプリ本体を無効にすることでキーボードやマウスの処理を受け付けなくなる。より作業に集中させるためのオマジナイ的なモノ。
execBench から戻った後、再びEnableWindow 関数でアプリのフォームを有効に戻す。有効に戻さないと、マウスやキー操作を受け付けず、アプリの終了が不能になる。通常終了できないので、タスクマネージャ等で終了させることになる。

本来、execBench から戻ってきた値に応じてエラーのメッセージを画面に出すのだが・・・ここでは省いてあります。後で追加します・・・

初歩の段階、不慣れなうちは、戻り値・返却された値の確認を疎かにしがちである。入門書などでも省かれている例が多いので、確認の重要性が実感できないことだろう。しかし、それが習慣となると、本格的にアプリを組む段階において苦労する。
筆者としては、初歩の段階のヒトほど戻り値をチェックしてほしいと願うばかり・・・

※ 段階(3) 速度を測る部分を作成する は次回以降。

長くなりましたので続きはまた後日・・・

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

検索サイトからお越しの方へ
検索サイトからお越しの方は、ブラウザのアドレス欄vitalaboloveおよび、fc2.comが含まれているかご確認ください。
含まれていない場合、偽サイトを閲覧なされている可能性があります。

偽サイトは、当ブログの文字部分や画像部分が有害サイトへのバナーと置き換わっているようです。
プロフィール

Author:Vitalabolove
ご訪問ありがとうございます。
店長を任されておりますVitalaboloveです。

コメントはお気軽に。
今のところリンクフリーですが、あと数日でとりやめます。

画像データ、文言の引用は事前連絡くださるようお願い申し上げます。事前連絡の際は、左下、メールフォームを経由をご利用ください。

最新記事
カレンダー
09 | 2017/10 | 11
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 - - - -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

フリーエリア
内緒話などはおきてがみをご利用ください。
月別アーカイブ
メールフォーム
掲載された記事について、ご不明な点はここからお問い合わせください

名前:
メール:
件名:
本文:

最新コメント
最新トラックバック
スパムと思われるトラックバックは削除しました
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。