Tutorial 3: A Simple Window

このチュートリアルでは、デスクトップに基本となるウィンドウを表示させるプログラムを作成する。
   ソース      実行結果  

Theory:

Windowsプログラムにおいて、GUIの部分というのはAPI関数にほぼ依存している。 このことは、プログラマや、そのプログラムをユーザも勝手がわかるので便利である。 ユーザは、違うプログラムでもGUIの操作性が一貫したものなので、迷うことはそれほど無い。 プログラマにとっては使用するGUI部分のコードは既にテストされているので、 使用するに当たって余分に発生する作業が無い。 ところがプログラマは、ウィンドウやメニューアイコンを作成するといった、 GUI部分のプログラムをする場合Windowsプログラムの複雑なルールを覚えないといけない。 しかし、モジュール化プログラム、オブジェクト指向といったテクニックによって打開できる。

デスクトップにウィンドウを表示するための手順の概略を以下に示そう。

  1. インスタンスハンドルを取得
  2. コマンドライン引数を取得(必要な場合)
  3. ウィンドウクラスを登録(MessageBoxやダイアログボックスといった定義済みのものでなければ必要)
  4. ウィンドウを作成
  5. デスクトップにウィンドウを表示(すぐに表示したくなければ別にしなくてもよい)
  6. ウィンドウのクライアント領域をリフレッシュ
  7. 無限ループ(メッセージループ)中でWindowsから投げられるメッセージを監視する
  8. メッセージが来たら、自分で処理したいメッセージに対して適切な処理を行う
  9. ユーザが「閉じる」ボタン(×ボタン)を押したら終了するようにしておく

見ればわかるとおり、WindowsプログラムというのはDOSプログラムに比べると少々複雑だが、 Windowsプログラムの世界はDOSとまったく異なっており、WindowsがマルチタスクなOSなので、 あるWindowsプログラムが他のWindowsプログラムと共存できなければならず、 そのため、より制限のきついルールに遵守しなければならない。

Content:

下のプログラムは単純なウィンドウを表示するコードだ。泥臭いWin32 ASM プログラムの説明に入る前に、 知っているとWin32のプログラムの理解を促進してくれるちょっとしたコツをいくつか紹介しよう。

.386 
.model flat,stdcall 
option casemap:none 
include    \masm32\include\windows.inc 
include    \masm32\include\user32.inc 
includelib \masm32\lib\user32.lib                    ; calls to functions in user32.lib and kernel32.lib 
include    \masm32\include\kernel32.inc 
includelib \masm32\lib\kernel32.lib 

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD 

.DATA                                                ; initialized data 
ClassName db "SimpleWinClass",0                      ; the name of our window class 
AppName db "Our First Window",0                      ; the name of our window 

.DATA?                                               ; Uninitialized data 
hInstance HINSTANCE ?                                ; Instance handle of our program 
CommandLine LPSTR ? 

.CODE                                                ; Here begins our code 
start: 
invoke GetModuleHandle, NULL                         ; get the instance handle of our program. 
                                                     ; Under Win32, hmodule==hinstance mov hInstance,eax 
mov hInstance,eax 
invoke GetCommandLine                                ; get the command line. You don't have to call this function IF 
                                                     ; your program doesn't process the command line. 
mov CommandLine,eax 
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT        ; call the main function 
invoke ExitProcess, eax                              ; quit our program. The exit code is returned in eax from WinMain. 

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX                              ; create local variables on stack 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 

    mov   wc.cbSize,SIZEOF WNDCLASSEX                ; fill values in members of wc 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInstance 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_WINDOW+1 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc                  ; register our window class 
    invoke CreateWindowEx,NULL,\ 
                ADDR ClassName,\ 
                ADDR AppName,\ 
                WS_OVERLAPPEDWINDOW,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                CW_USEDEFAULT,\ 
                NULL,\ 
                NULL,\ 
                hInst,\ 
                NULL 
    mov   hwnd,eax 
    invoke ShowWindow, hwnd,CmdShow                   ; display our window on desktop 
    invoke UpdateWindow, hwnd                         ; refresh the client area 

    .WHILE TRUE                                       ; Enter message loop 
                invoke GetMessage, ADDR msg,NULL,0,0 
                .BREAK .IF (!eax) 
                invoke TranslateMessage, ADDR msg 
                invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax,msg.wParam                            ; return exit code in eax 
    ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    .IF uMsg==WM_DESTROY                              ; if the user closes our window 
        invoke PostQuitMessage,NULL                   ; quit our application 
    .ELSE 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam  ; Default message processing 
        ret 
    .ENDIF 
    xor eax,eax 
    ret 
WndProc endp 

end start

Analysis:

たぶん、こんなウィンドウを出すだけでどうしてこんなにもコード量が必要なのか、 驚いたことだろう。でも、これらのコードは単なる「雛形」なので、違うソースファイルにコピーすればいいだけの話だ。 もしくは、これらのコードのうちプロローグ部分、エピローグ部分として使用する箇所をアセンブルして、 ライブラリファイルに収めることもやろうと思えば可能である。 そうすれば、WinMain関数のところだけを書けばよいことになる。 実際、(VCなどの)Cコンパイラはそうやっている。 そのため、それらのコンパイラを使用すれば、そういう煩わしいところは何も考えずにWinMain関数を書くことができる。 ただし、Cコンパイラを使用するに当たって、唯一の問題はWinMainという名前の関数を使わなければならないことだ。 さもなければ、Cコンパイラはあなたが作ったプログラムとCコンパイラが用意してあるプロローグ部分とエピローグ部分のプログラムを くっつけることができなくなってしまう。 アセンブラでは、そのような制限は無い。WinMainの代わりにどんな関数名でも使用できるし、関数が無くてもかまわない。

さー準備はいいかい?ここからは長くなるぞ。ではこのプログラムをとことんまで解析しよう

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

最初の3行は「必要不可欠」である。
386 というのは、このプログラムでは 80386CPU の命令セットをしようすることを、 .model flat,stdcall というのは、フラットメモリモデルを使用することと、 stdcall方式の関数の呼び出し規約をデフォルトとして使用する、ということを表している。

次は、WinMain関数のプロトタイプ宣言である。後でWinMain関数をCALLしているので、 CALLする行より前に関数プロトタイプを記述しておかないといけない。

ソースコードの一番最初で、windows.inc をインクルードしないといけない。 windows.inc には、このプログラムで使用する重要な構造体や定数が定義されている。 windows.inc は単なるテキストファイルなので、あなたにも編集可能であるのだが、 Windowsで使用する全ての構造体や定数が記述されているわけではない(まだできていない)。 hutch と私は今尚続きを行っているところである。 このファイルに無いものはあなたにも追加することができるようになっている。

このプログラムは、user32.dll に存在するAPI関数(CreateWindowEx や RegisterWindowClassEx など)や、 kernel32.dll に存在するAPI関数(ExitProcess)をCALLしている。 そのため、これら2つのインポートライブラリをリンクしなければならない。

ところで、どのインポートライブラリをリンクすれば良いのかどうやって知ればいいのか?
というのを疑問に思うかもしれない。 これを知るためには、あなたのプログラムで呼び出しているAPI関数がどのライブラリに存在するかを知らなければならない。 例えば、gdi32.dll にあるAPI関数を呼ぶとしたら、gdi32.lib とリンクしないといけない。

これがMASMのアプローチだ。TASMの方法はもっと簡単で、import32.lib をリンクするだけだ。

.DATA
   ClassName db "SimpleWinClass",0
   AppName db "Our First Window",0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?

次は、"DATA"セクションだ。

.DATA セクションでは2つの文字列を宣言している。1つはウィンドウクラス名ともう1つはアプリケーション名だ。 2つの変数は初期化されていることに注意しなければならない。

.DATA? セクションでは2つの変数を宣言している。 hInstance(プログラムのインスタンスハンドル)とコマンドライン引数だ。 これらの HINSTANCEとLPSTRという見慣れないデータ型の本当の名前は DWORD で、 windows.inc に記述されている。 .DATA? セクションにある変数は初期化されないことに注意しないといけない。 つまり、起動時に何も値を保持しなくてもよいのである。 ただ、そのうち使用するため、メモリ領域だけは確保したいのである。

.CODE
 start:
    invoke GetModuleHandle, NULL
    mov   hInstance,eax
    invoke GetCommandLine
    mov   CommandLine,eax
    invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    invoke ExitProcess,eax
    .....
end start

.CODE セクションは全てあなたの指令である。あなたの書くコードは <starting label>: と end <starting label> の間に入っていなければならないのだが、ラベル名は好きな名前をつけることができる。 その際、唯一な名前で、かつMASMの命名規則に違反してはならない。

このプログラムでは、自分のプログラムのインスタンスハンドルを取得する GetModuleHandle 関数をまずはじめにCALLしている。 Win32 では、インスタンスハンドルとモジュールハンドルは同じものである。 インスタンスハンドルはあなたのプログラムのIDみたいなものだと考えることができ、 いくつかのAPI関数でこのインスタンスハンドルを引数に取るものがあり、 プログラムの最初でこのハンドルを取得するのはにいい考えである。

※   実際のところ、インスタンスハンドルというのは、あなたのプログラムのアドレスである。

Win32 関数から戻ってくると、関数の戻り値があるわけだが、これは eax レジスタの値となっている。 他の全ての値は関数呼び出し時の引数にセットした変数を通して返される。 あなたが呼び出す Win32 関数はほぼ常に、セグメントレジスタである、ebx、edi、esi、そしてebpレジスタを一時退避している。 反対に、ecx や ebx レジスタの値は汚れたレジスタとして考えなければならず、 Win32 関数から戻ってきた後には呼び出す前の状態を保っていることはほとんどない。

※   API関数を呼び出した後も、eax、ecx、edx レジスタの値がそのままであることを期待してはいけない

一番下の行(invoke ExitProcess,eax)は、API関数をCALLするときに、 eaxレジスタに戻り値があることを期待している。 もし、あなたの作ったプログラムが Windows から呼び出されるものであれば、そのルールを踏襲しないといけない。 セグメントレジスタの ebx、edi、esi、ebp レジスタの値を一時退避したり、復元しないといけない。 さもないと、あなたのプログラムは即座にクラッシュするだろう。 これはウィンドウプロシージャや、コールバック関数にもいえることだ。

GetComandLine 関数は必ずしも必要と言うわけではなく、もしコマンド引数を必要としなければ、 無くてもかまわない。この例では、この関数の使い方を紹介するため、あえて使用している。

次は、WinMain関数の呼び出しだ。4つの引数がある。それらは

である。
Win32では、以前のインスタンスは存在しないので、hPrevInst は常に 0 である。 これはWin16時代からの互換性のため残っている仕様で、 Win16 では全てのプログラムは同じアドレス空間で起動しているため、 プログラムが起動する際、自分が一番最初に起動したのかどうか知る必要があり、 もし NULL ならこのプログラムが最初に起動したものということになる。

※  WinMainという関数名で宣言する必要は無く、全くの自由な名前を使用することができる。 さらに言えば、WinMainと同等の関数を使用することすら必要ではなく、 例えば、上の例で、WinMain関数を呼び出している行を消して、 その消したところに、WinMain関数の内容を貼り付ければ、完全に同じ動作をする。

WinMain関数から返ってきたとき、eaxレジスタは戻り値となっている。 ExitProcess 関数で終了すると同時に、この値をそのままプログラムの返り値としている。

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

上の行はWinMain関数の宣言である。ここで注意すべきは、PROC 命令に続く型と引数のペアである。 それらの引数はWinMain 関数の呼び出し側から受け取ることになっているのだが、 これらの引数はスタックを操作して参照できるようになっている。 しかも、MASMは WinMain 関数のプロローグ部分とエピローグ部分を作ってくれるので、 プログラムを書くときに、関数の入り口と出口におけるスタックフレームについて、 心配する必要は無いのである。

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

LOCAL 命令は関数で使用するローカル変数のメモリ領域をスタックに確保するものである。 PROC 命令のすぐ後に、LOCAL 命令をひとかたまりにして、記述しなければならない。 LOCAL 命令は、 <the name of local variable>:<variable type> という書式で使用することになっており、 例えば、wc:WNDCLASSEX というのは、スタックに、構造体「WNDCLASSEX」のサイズを確保し、 その変数名を「wc」としているのである。 こうすることによって、以降、めんどくさいスタック操作を行わずにこの変数を参照することができる。 これは本当にラクチンである。 ただ、注意することがあり、このローカル変数は関数を抜けた後(呼び出しもとの関数に戻った時)は自動的に消去されるため参照できなくなる。 また、ローカル変数は自動的に初期化されないことも気に留めておかなければならない。 そのため、LOCAL 命令の後に、これらのローカル変数に値を手動でセットしなければならない。

mov    wc.cbSize,SIZEOF WNDCLASSEX
mov    wc.style, CS_HREDRAW or CS_VREDRAW
mov    wc.lpfnWndProc, OFFSET WndProc
mov    wc.cbClsExtra,NULL
mov    wc.cbWndExtra,NULL
push   hInstance
pop    wc.hInstance
mov    wc.hbrBackground,COLOR_WINDOW+1
mov    wc.lpszMenuName,NULL
mov    wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov    wc.hIcon,eax
mov    wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov    wc.hCursor,eax
invoke RegisterClassEx, addr wc

上の例は、一見難しそうだが、やってることは至って単純で、ウィンドウクラスを登録しているだけである。 ウィンドウクラスの登録というのは、単なるウィンドウの設定を行っているに過ぎず、 どのアイコンを表示するか、どのカーソルを表示するか、どの関数に処理を行わせるか、 などの設定を行っている。 そして、そのような設定を行ったウィンドウクラスを用いて、ウィンドウを画面上に表示するのである。 このような方法は、オブジェクト指向プログラミングの利点の一つであり、 もし、同じ設定のウィンドウクラスから2つ以上のウィンドウを表示したければ、 それらの複数表示されたウィンドウの設定は唯一のウィンドウクラスの設定を必要なときに参照すればいいのだ。 この方法は、(ウィンドウの設定に関する)情報を二重に持つということを回避できるためメモリを節約することができる。 思い出してみよう、Windows が設計されていたころは、メモリは非常に高価で、 ほとんどのコンピュータは1MBほどしかメモリがなかった。 そのため、Windows は乏しいメモリ量でもきちんと動作しなければならなかったのである。 昔話はいいとして、もしウィンドウを表示したければ、先に、 WNDCLASS もしくは、 WNDCLASSEX 構造体でウィンドウの設定を行い、 RegisterClass もしくは RegisterClassEx 関数でウィンドウ クラスを登録しないといけない。 そして、その登録は、一度行えばよい。

Windows にはあらかじめいくつかのウィンドウクラスが登録されている。 例えばボタンや、エディットボックスなどだ。 これらのウィンドウ(コントロールと言う場合もある)は自分で登録する必要は無く、 使用したければ、直接これらのクラス名で CreateWindowsEx 関数をCALLすればよい。

WNDCLASSEX構造体のメンバ変数のうち、最も重要なものは lpfnWndProc である。 lpfn というのは、関数へのロングポインタという意味である。 Win32 では、メモリモデルを Win16 から刷新してフラットメモリモデルとしたため、 near ポインタや far ポインタというのは無くなった。 しかし、これもまた、Win16 からの名残である。 ウィンドウはユーザとの間で何らかの処理を行うのだが、 ウィンドウクラスは、その処理を行う関数(ウィンドウプロシジャ)へのポインタを持っていなければならない。 ウィンドウプロシジャは、ウィンドウに送られてくる全てのメッセージに対して処理を行う責務がある。 ユーザがキーボードを押したとか、マウスをクリックした、 という作成したウィンドウが処理を行うべき重要なイベントを報告するため、 Windowsはメッセージをウィンドウプロシジャに送っているのである。 Windowsから送られてくるメッセージに対して、 的確に反応するかどうかは、このウィンドウプロシジャ次第である。 たぶん、Windowsプログラムでは、 このウィンドウプロシジャのイベントハンドラを書く時間が大半となるだろう。

WNDCLASSEX STRUCT DWORD
 cbSize           DWORD     ?
 style            DWORD     ?
 lpfnWndProc      DWORD     ?
 cbClsExtra       DWORD     ?
 cbWndExtra       DWORD     ?
 hInstance        DWORD     ?
 hIcon            DWORD     ?
 hCursor          DWORD     ?
 hbrBackground    DWORD     ?
 lpszMenuName     DWORD     ?
 lpszClassName    DWORD     ?
 hIconSm          DWORD     ?
WNDCLASSEX ENDS

invoke CreateWindowEx,       \
        NULL,                \
        ADDR ClassName,      \
        ADDR AppName,        \
        WS_OVERLAPPEDWINDOW, \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        NULL,                \
        NULL,                \
        hInst,               \
        NULL

ウィンドウクラスが登録されれば、 CreateWindowEx 関数でそのウィンドウクラスの設定そのままのウィンドウを作成できる。 ところが、この関数には、12個も引数がある。

CreateWindowExA proto \
  dwExStyle:DWORD,    \
  lpClassName:DWORD,  \
  lpWindowName:DWORD, \
  dwStyle:DWORD,      \
  X:DWORD,            \
  Y:DWORD,            \
  nWidth:DWORD,       \
  nHeight:DWORD,      \
  hWndParent:DWORD,   \
  hMenu:DWORD,        \
  hInstance:DWORD,    \
  lpParam:DWORD

では、それぞれの引数について見ていこう。

mov    hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd

CreateWindowEx 関数が成功すれば、ウィンドウハンドルは eax レジスタに格納されている。 この値はそのうち使用するので、保存しておかなければならない。 そして、この CreateWindowEx 関数で作成したウィンドウは、 実は自動的に表示されるわけではなく、 今取得したウィンドウハンドルと、画面上にどのように表示するかを指定したものを引数として ShowWindow 関数をCALLしなければならない。 そして、次に UpdateWindow 関数で自分の作成したウィンドウのクライアントエリアを再描画す る。 この UpdateWindow 関数は、 自分の作成したウィンドウのクライアントエリアをアップデートする時にとても便利だ。 でも、別にこの関数は呼ばなければならないというわけではない。

.WHILE TRUE
             invoke GetMessage, ADDR msg,NULL,0,0
               .BREAK .IF (!eax)
             invoke TranslateMessage, ADDR msg
             invoke DispatchMessage, ADDR msg
.ENDW

これでようやく、画面上にウィンドウが表示される。 でもこれではまだ、何もできないので、 Windowsからやってくるメッセージを受け付けるようにしてあげないといけない。 これは、メッセージループを作成することにより、可能となる。 それぞれのモジュールは、1つのメッセージループを持っている。 このメッセージループ内では、GetMessage 関数をCALLすることにより、 Windowsから連続的にやってくるメッセージをチェックしている。 GetMessage 関数は Windows にMSG構造体のポインタを渡すようになっている。 このMSG構造体には、Windowsがこの作成したウィンドウに対して送りたいメッセージ(情報)が入っている。 GetMessage関数は、ウィンドウに対する何らかのメッセージ(情報)が作成されるまで、返ってこない。 その間、Windows は他のプログラムに制御を移すことができる。 これが、Win16 プラットフォームにおける協調マルチタスク環境だ。 GetMessage 関数はWM_QUITメッセージを受け取るとFALSEを返し、 このメッセージループは終了し、プログラムも終了するだろう。

TranslateMessage 関数は生のキーボードの入力情報を取得すると、 メッセージキューに、新たに WM_CHARメッセージを作成するという処理を行う。 WM_CHARメッセージは、押されたキーのアスキー値を含んでおり、生のキーボードコードを扱うより簡単である。 もし、キーストロークを扱うようなプログラムでなければ、この TranslateMessage 関数を使用する必要は無い。

メッセージデータをそれに対応したウィンドウプロシージャに配信する。

   mov    eax,msg.wParam
   ret
WinMain endp

メッセージループが終了すれば、終了コードがMSG構造体のwParamに格納される。 その値を eax レジスタに格納して Windows に制御を返す。 この時、Windows はこの戻り値を使用しないのだが、 一応ルールに従った方が安全なので、そうしよう。

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

これがこのプログラムのウィンドウプロシージャだ。 ちなみに、この関数名を WndProc とする必要は無い。 まず1番目のパラメータは、hWnd で、これはメッセージの送り先のウィンドウハンドルだ。 uMsg はメッセージパラメータである。 uMsg はMSG構造体ではないことに注意しなければならない。 実際には、これは単なる数値で、この数値によってメッセージを識別しているのである。 Windowsは何百ものメッセージ(数値)を定義しているが、 そのメッセージがいくつかということは、あなたにはどうでもいいことで、 とにかくWindowsはウィンドウに発生した何らかの現象をそのウィンドウに 教えてあげるために、そのメッセージをそのウィンドウに送ってあげるのである。 ウィンドウプロシージャはそのメッセージを受け取り、そのメッセージに対して適した処理を行う。 wParam と lParam はいくつかのメッセージに対して、付加的な情報を伝えるためのものである。 あるメッセージはメッセージ自身にデータを添付して送ったりする。 それらのデータは lParam や wParam にのってやってくる。

これは、非常に重要な部分だ。ここの部分がこのプログラムの中で一番インテリジェンスだ。 Windowsから送られてくるそれぞれのメッセージの対して処理を行う箇所というのはウィンドウプロシジャだ。 そのウィンドウプロシジャでは、まず、自分が処理を行いたいメッセージかどうかをチェックする。 もし、自分で処理を行いたいメッセージの場合は、行いたい処理のコードを記述した後、 eax レジスタに 0 をストアして関数を抜けないといけない。 もし、自分で処理を行いたいメッセージでなければ、 DefWindowProc関数をCALLし、この関数でもらった引数(hWnd,uMsg,wParam,lParam)を、そっくりそのまま、 その関数に引数として渡さなければならない。 これで、そのメッセージのデフォルトの処理(最大化ボタンなど) をWindowsが行ってくれるようになる。

唯一の、デフォルト処理でなく自分の処理を記述しなければならないメッセージは、WM_DESTROYだ。 このメッセージはウィンドウが閉じられたときに必ず送られるメッセージだ。 プロシージャに、このメッセージが送信されるまでには、 そのプロシージャに対するウィンドウは、画面上から消えてしまっている。 このWM_DESTROYは、あなたのウィンドウが閉じられたことを告知するもので、 Windowsに処理を戻すための準備を行わなければならないのだが、 Windowsに処理を戻すより前に、自分のウィンドウに対する後処理を行うことができる。 ただその後は、この状態になってしまったら、終了するより他ないのだが。 ところで、もし、ユーザにウィンドウが閉じれなくなるようにしたいと思ったら、 WM_CLOSE メッセージに対する処理をカスタマイズすればよい。 WM_DESTROY に話を戻して、自分のウィンドウの後処理を終えたら、 モジュールにWM_QUITメッセージを送るPostQuitMessage関数をCALLしなければならない。 WM_QUIT メッセージは、GetMessage 関数の戻り値が 0 で返ってくるようにして、 順に、メッセージループが終了し、Windowsに処理を戻す。 自分のウィンドウプロシジャにWM_DESTROYメッセージを送りたければ、 DestroyWindow関数を呼べばよい。


[戻る]