WinAPI(memo)

program

C言語による簡易WindowsProgram作成手順

ウィンドウクラスの登録ウィンドウの作成ウィンドウの表示
メッセージループ  ウィンドウプロシージャ



C C++とかの初心者メモとか。有用な事は一切書きません。無能がほざくことを真剣に受け取らないでください。後々CやらC++を忘れたらがあったら見返そうとメモとっとくだけす。てか忘れるのか。

昔HSPとかやっていて、window(x1,y1,x2,y2)みたいな関数(当時、私は命令文と言っていた)を使うと簡単にウィンドウを表示させることができて、xにはx座標yにはy座標をいれればコンピュータ(OS)にウィンドウをだせ!と命令してくれる語があるものだと思っていた。まぁそうには違いないと思うわけだが、実際は()内にから得られた数字を元に ごちゃごちゃとやってることをCだのC++をやるうちに解ってきて「アホな子だったな俺は…」とか思ったりもした。今もアホな子であったりするが。

しかし私にとってCは実に難解だ。

#include <stdio.h>
int main()
{
	printf("Hello,world");
	return 0;
}

などという初歩中の初歩すら未だに把握していない。printf()という関数は"Hello,world"という文字列を受け取るわけだが、上に書いたようにどこかでvoid printf(char *){....}みたいな関数の元(?)が定義されているはずである。取敢えず書いてあるであろう<stdio.h>を覗いてみると

_CRTIMP int __cdecl printf(const char *, ...);

と定義されている(というかプロトタイプ宣言か?)。int型の戻り値を持つ…__cdecl…型?最後の「...」は確か可変個の引数を取るんだったか。 つーか_CRTIMPってなんじゃ?

/* Define _CRTIMP */

#ifndef _CRTIMP
#ifdef  _DLL
#define _CRTIMP __declspec(dllimport)
#else   /* ndef _DLL */
#define _CRTIMP
#endif  /* _DLL */
#endif  /* _CRTIMP */

こんなものを見つけた。
なになに、もし_CRTIMPがマクロ定義されていなく、さらに_DLLが定義されていれば__declspec(dllimport)を_CRTIMPと定義する(置き換える)。_DLLが定義されていない場合は_CRTIMPは空定義(?)すると。

つまり_DLLが定義されているかどうかでCRTIMPをマクロにするかどうかを決めるわけですな。しかし今度は_DLLと __declspec()関数について調べねばならんのか…。()内のdllimportって変数もか。いや、配列のポインタかもしれん。いやいや、構造体の…。めんどい、投げる。

ちゅーか標準入出力ライブラリを初心者が読み解こうっつーのがそもそも厳しいのかもしれない。素直に使っておけということですな。

7/27

DOS窓でタラタラと動作する見栄えもなにもあったもんじゃないプログラム作っていてもツマラン。そろそろ窓でプログラムを走らせたい。てなわけでなんとなくWindowsプログラムに取り組んでみる。

まさに酒池肉林。謎過ぎる変数名による自己の自己による自己の完結。哲学っぽい。わけもないわけだがマジで 意味不明すぎなのでちょっとずつ理解していこうと思う。ちなみに全部猫でもわかるプログラミングさんのSDK編を参考にしている。まあなんとなくアレンジはしているものの必要っぽい事を抜き出しているだけであるわけだがそいつは黙殺していただきたい。

typedef struct _WNDCLASS {    
    UINT    style;          //サイズ変更で再描画
    WNDPROC lpfnWndProc; 	//プロシージャ名登録
    int     cbClsExtra; 	//メモリ追加設定
    int     cbWndExtra; 	// 〃普段は0で良い
    HANDLE  hInstance; 		//インスタンス登録
    HICON   hIcon; 		//アイコン登録
    HCURSOR hCursor; 		//カーソル登録
    HBRUSH  hbrBackground; 	//背景登録
    LPCTSTR lpszMenuName; 	//メニュー名登録
    LPCTSTR lpszClassName; 	//クラス名登録
} WNDCLASS;

大事な構造体、_WNDCLASS君。最後に構造体変数が付いているのはポイント高い(?)。まあ…うん。ちょっとずついこう。UNIT型のstyleにはCS_HREDRAW(水平方向のサイズ変更があったら全体を描き直す)、CS_VREDRAW(垂直方向のサイズ変更で全体を描き直す)などの定数が入る。まあ他の定数は必要なときに調べればいい。複数指定するには「 | 」を使う。多分どこかでstyleに入ってる定数で描画方式(?)を決定する条件式か関数か何かがあるんだろうな。
WNDPROC型のlpfnWndProcは ウィンドウプロシージャの名前を登録をする。Procってのはプロシージャの事だったのか…。fnはファイルネームと解釈してもいいかな?(ダメだ)。int型のcbClsExtraとか、cbWndExtraはメモリ領域の追加設定の時使うが、普段は0でよろしいそうだ。なんだかなぁ。
HICON型のhIcon、HCURCOR型のhCursorはウィンドウクラスにアイコン、カーソルを登録する。たしか先頭にHがつくのはハンドルって意味だったはず。ハンドルって?車屋に聞け。実は俺もよくわからん。どうせそのうちわかるだろ(伸びないタイプの典型)。
HBRUSH型のhbrBackgroundは背景を登録する。LPCTSTR型のlpszMenuNameはメニュー名登録、lpszClassNameはクラス名を登録する。何故か登録って言葉がしっくりこないおバカさんが1名。そのうち慣れるだろ(伸びry)。

意味不明なデータ型はあるものの変数名とかは意外とわかり易いではないか。が、これらの_WNDCLASS構造体のメンバ変数がいかに使われているかを理解していかなければならないかを考えると正直頭が痛い。まぁちょっとずつ、ちょっとずつ。

7/30

↑の_WNDCLASS構造体のメンバ変数に代入する定数なんかについてちょちょいとメモ。

■ hIcon=LordIcon( , )

()の第一引数には基本的にはNULLを。NULLにするとWindowsで準備されているアイコンを使える。アイコンとは ウィンドウのタイトルバーの左端に表示される画像の事。
第二引数には
IDI_APPLICATION:既定のアプリケーションアイコン
IDI_ERROR:ストップマークのアイコン
IDI_INFORMATION:情報アイコン
IDI_QUESTION:疑問符のアイコン
IDI_WARNING:感嘆符のアイコン
といったものをブチこむ。まあ普通はIDI_APPLICATIONを入れとけば問題ないだろう。

■ LoadCursor( , )

()の第一引数にはまたもやNULLを。カーソルなんぞWindowsに任せればいいだろう。ちなみに自分で用意したカーソルをどう使うかはまだわからん。そのうち調べるだろ(ry。
第二引数には
IDC_ARROW:標準の矢印カーソル
IDC_APPSTARTING:標準の矢印カーソルと小さい砂時計カーソル
IDC_CROSS:十字カーソル
IDC_HAND:ハンドカーソル
IDC_HELP:矢印と疑問符
IDC_IBEAM:アイビーム (縦線) カーソル
IDC_WAIT:砂時計カーソル
IDC_NO:禁止カーソル(円に左上から右下への斜線)
この辺はいろいろ使えそうですな。

■ hbrBackground = (HBRUSH)GetStockObject( )

GetStockObject関数をHBRUSH型に型キャストするのは必須っぽい。GetStockObjectの戻り値がHBURASH型じゃないらしくHBURASH型からなんたら型にはできんみたいな事言われます(VisualStdio2003)。
GetStockObjectの引数には
BLACK_BRUSH:黒のブラシ
DKGRAY_BRUSH:濃い灰色のブラシ
GRAY_BRUSH:灰色のブラシ
LTGRAY_BRUSH:明るい灰色のブラシ
WHITE_BRUSH:白のブラシ
NULL_BRUSH
が用意されている。
const COLORREF 変数 = RGB(赤,青,緑)
と予め宣言しておき、
hbrBackground = CreateSolidBrush(変数)
とすることで任意の色を背景にすることができる。赤青緑の数値は0〜255。
hbrBackground = CreateHatchBrush(ハッチパターン, 変数)
とすることでハッチパターンのブラシを作る事ができる。ハッチパターンには
HS_BDIAGONAL:45度の右下がりのハッチ
HS_CROSS:水平と垂直のクロスハッチ
HS_DIAGCROSS:45度のクロスハッチ
HS_FDIAGONAL:45度の右上がりのハッチ
HS_HORIZONTAL:水平ハッチ
HS_VERTICAL:垂直ハッチ
などを使う。ハッチハッチ言っているが蓋とか入り口とかそういう意味ではない。ただの網目模様。

7/31

★ ウィンドウの作成

HWND CreateWindow(
	???                   //拡張ウィンドウスタイル
    LPCTSTR lpClassName,     //クラス名(文字列)
    LPCTSTR lpWindowName, //ウィンドウの名前(タイトル)
    DWORD dwStyle,            //ウィンドウスタイル    
    int x,                //X座標    
    int y,                //Y座標    
    int nWidth,            //ウィンドウの横幅    
    int nHeight,            //ウィンドウの高さ    
    HWND hWndParent,     //親HWND(親を作るときはNULL)
    HMENU hMenu,            //クラスメニューの時はNULL
    HANDLE hInstance,        //インスタンスハンドル    
    LPVOID lpParam         //ウィンドウ作成データ    
   );

HWND型を返すのでHWND型の変数に代入する形で使う。HWND hwnd = CreateWindow(...)といった感じだ。ウィンドウの作成に失敗するとNULLを返す。
以下メモ。

■ 拡張ウィンドウスタイル

WS_EX_OVERLAPPEDWINDOW:エッジ付きウィンドウ
WS_EX_OVERLAPPEDWINDOW | WS_EX_TOPMOST:↑を常に前にするように追加

■ ウィンドウスタイル

WS_OVERLAPPED:オーバーラップウィンドウ
WS_CAPTION:タイトルバー
WS_SYSMENU:システムメニュー
WS_THICKFRAME:サイズ変更枠
WS_BORDER:サイズ変更不可能枠
WS_MINIMIZEBOX:最小化ボタン
WS_MAXIMIZEBOX:最大化ボタン

これらを好きなように「 | 」で組み合わせる。全ての機能を持たすにはWS_OVERLAPPEDWINDOWを使うと楽。また、

WS_POPUP:ポップアップウィンドウ

をいれることによりタイトルバーや枠を消すことが可能。しかしポップアップウィンドウはウィンドウの位置と高さにCW_USEDEFAULTを使えない。自分で数値を打ち込むべし。

★ ウィンドウの表示

ウィンドウを表示状態にするためにShowWinow関数とUpdateWindow関数を使う。

■ ShowWindow( , )

第一引数にはウィンドウハンドルを、第二引数に状態を設定するが、とりあえずはWinMain関数の第四引数を入れる。一応用意されているものとして、

SW_HIDE
ウィンドウを非表示にし、他のウィンドウをアクティブにする。
SW_RESTORE
ウィンドウをアクティブにして表示する。最小化または最大化されていたウィンドウは、元の位置とサイズに戻る。
SW_SHOW
ウィンドウをアクティブにして、現在の位置とサイズで表示する。
SW_SHOWMAXIMIZED
ウィンドウをアクティブにして、最大化する。
SW_SHOWMINNOACTIVE
ウィンドウを最小化する。ウィンドウはアクティブ化されない。
SW_SHOWNOACTIVATE
ウィンドウを直前の位置とサイズで表示する。ウィンドウはアクティブ化されない。
SW_SHOWNORMAL
ウィンドウをアクティブにして表示する。ウィンドウが最小化または最大化されていた場合は、その位置とサイズを元に戻す。

が、ある。

■ UpdateWindow( )

第一引数にはウィンドウハンドルをぶち込む。これは理解できない関数です。後々調べてみる。

8/4

★ メッセージループ

事実上ここがプログラムの活動状態。WinMain関数の最後の処理。

MSG msg;

while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

return msg.wParam;

■ GetMessage関数はWM_QUITを受け取るとFalse(0)、それ以外のメッセージを受け取るとTrue(1)をMSG型変数に格納する。WM_QUITメッセージを受け取るまで延々とループを繰り返すというわけだ。第一引数以外を処理することはほとんどないので他の引数にはNULLだの0だのぶち込んでおいて問題ない。

■ DispatchMessage関数はGetMessage関数で得られた情報(メッセージ)を使う。この場合msgが引数となる。情報を使うってあんた一体何に…という感じなのでまた事後調べるとしよう。

■ TranslateMessage関数はキーボードによって押された文字の文字コードを渡してくれるWM_CHARを生成する。ほかにもWM_SYSCHARという重要なメッセージも生成する。

WinMain関数についての主な作業はこんな感じだ。メッセージループはウィンドウプロシージャの起動なので、プログラムが走り始めてからの処理はウィンドウプロシージャに記述する。


★ ウィンドウプロシージャ

なんつーかここからが本番みたいな感じである。ここでウィンドウの振る舞いを決める。ウィンドウプロシージャは発生するイベントに対応する処理を記述する。またウィンドウプロシージャは複数を作ることができ、名前も自由に決めることができる。

ウィンドウクラスの登録で_WNDCLASS構造体にWNDPROC型のlpfnWndProcというメンバ変数があったわけだがここには作成するウィンドウプロシージャの名前を入れるわけである。

ウィンドウプロシージャは、イベントが発生するとウィンドウズから呼び出されると考える。で、プロシージャ内で好き勝手にメッセージに対して処理していいかというと、まぁ別に構わないのだが、Windowsの統一された操作性が失われることになる。タイトルバーをドラッグしたらウィンドウが動く、なんてのはWindowsのユーザーなら常識かと思われる。それがドラッグしたら強制終了などというプログラムをつくっても誰も使わない。

static LRESULT CALLBACK WindowProc
 (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	swich(uMsg){
	case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        break;
	}
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

上のコードを見ると、ウィンドウプロシージャに渡されたすべての情報をDefWindowProc関数に渡して実行し、DefWindowProc関数の返してくる値を、そのままウィンドウプロシージャの戻り値として、つまりメッセージに対する返事として返している。この DefWindowProc関数はメッセージ(イベント)に対して標準の処理をするように、あらかじめ用意されたウィンドウプロシージャで、デフォルトウィンドウプロシージャと呼ばれる。こいつのお陰で処理したいメッセージのみをswitch文で処理してやればいいわけだ。偉大だ、デフォ。

switch文の中を見てみる。uMsg(メッセージ番号)を見て、WM_DESTROYが入っていた場合、つまりウィンドウが破棄されようとしている状態であるとき、PostQuitMessage関数を呼び出す。PostQuitMessageはWM_QUITメッセージを送る。