CTestWindow

http://toshi.my.land.to/table/ctestwindow/
* トップ ページ - プログラムのテーブル - CTestWindow :: 概要 / コード / 解説

概要

RAD ツールを利用せずにウィンドウを作成するクラスです。
RAD ツールの恩恵を授かることは出来ませんが、ウィンドウ プロシージャが
完全に見える為、メッセージ関係の実験に使えると思います。

コード

 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:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
Class CTestWindow

Public

    hMainWnd As HWND

    Sub CTestWindow()
        Assist.TestWindowPtr=VarPtr(This)
        Dim WCE As WNDCLASSEX
        With WCE
            .cbSize=Len(WCE)
            .style=CS_DBLCLKS Or CS_HREDRAW Or CS_OWNDC Or CS_VREDRAW
            .lpfnWndProc=AddressOf(MainWindowProc)
            .cbClsExtra=0
            .cbWndExtra=0
            .hInstance=GetModuleHandle(NULL)
            .hIcon=LoadIcon(NULL,IDI_APPLICATION As BytePtr)
            .hCursor=LoadCursor(NULL,IDC_ARROW As BytePtr)
            .hbrBackground=((COLOR_3DFACE)+1) As HBRUSH
            .lpszMenuName=NULL
            .lpszClassName="TestWindowsApplication Window Class"
            .hIconSm=LoadIcon(NULL,IDI_APPLICATION As BytePtr)
        End With
        RegisterClassEx(WCE)
        hMainWnd=CreateWindowEx(NULL,"TestWindowsApplication Window Class","TestWindowsApplication",
        WS_BORDER Or WS_VISIBLE Or WS_CLIPCHILDREN Or WS_MAXIMIZEBOX Or WS_MINIMIZEBOX Or _
        WS_OVERLAPPED Or WS_SYSMENU Or WS_THICKFRAME,
        0,0,640,480,NULL,NULL,GetModuleHandle(NULL),NULL)
    End Sub

    Function MainWindowProc(hWnd As HWND,dwMsg As DWord,wParam As WPARAM,lParam As LPARAM) As LRESULT
        MainWindowProc=Assist.TestWindowPtr->MainWindowProcSecond(hWnd,dwMsg,wParam,lParam)
    End Function

    Function MainWindowProcSecond(hWnd As HWND,dwMsg As DWord,wParam As WPARAM,lParam As LPARAM) As LRESULT
        Select Case dwMsg
            Case WM_CLOSE
                DestroyWindow(hWnd)
            Case WM_DESTROY
                PostQuitMessage(0)
            Case Else
                MainWindowProcSecond=DefWindowProc(hWnd,dwMsg,wParam,lParam)
        End Select
    End Function

End Class

Class CAssist

Public

    TestWindowPtr As *CTestWindow

End Class

Dim Assist As CAssist
Dim TestWindow As CTestWindow

Dim Message As MSG
Do
    If GetMessage(Message,NULL,NULL,NULL)<>1 Then Exit Do
    TranslateMessage(Message)
    DispatchMessage(Message)
Loop
ExitProcess(0)

解説

まずCTestWindow クラスと CAssist クラスを定義します。
何故 CAssist クラスも定義するかは後述します。
そして Assist オブジェクトと TestWindow オブジェクトを生成します。
TestWindow オブジェクトのコンストラクタでは、まず Assist クラスの CTestWindowPtr メンバに
自身の This ポインタを代入しています。何故なのかは、やっぱり後述します。
後は普通にウィンドウ クラスを登録し、ウィンドウを作成します。
そうしたらメッセージ ループに入るだけ。
WM_CLOSE と WM_DESTROY メッセージのみは自前できちんと処理を行っています。

肝心のウィンドウ プロシージャの部分。なんだか怪しいですね。
13 行目で、ウィンドウ プロシージャは MainWindowProc と指定しています。
ということで、TestWindow クラスの MainWindowProc メソッドを見てみましょう。

31:
32:
33:
    Function MainWindowProc(hWnd As HWND,dwMsg As DWord,wParam As WPARAM,lParam As LPARAM) As LRESULT
        MainWindowProc=Assist.TestWindowPtr->MainWindowProcSecond(hWnd,dwMsg,wParam,lParam)
    End Function

まず、Assist オブジェクトの TestWindowPtr メンバ は、
CTestWindow クラスへのポインタ型です。

そのポインタを経由して MainWindowProcSecond メソッドを呼び出し、
その戻り値をそのまま返しています。
構造体へのポインタからその構造体の各メンバへアクセスすることが出来るように、
クラスへのポインタからそのクラスの各メンバへのアクセスは勿論、
各メソッドの呼び出しも可能なのです。

そして MainWindowProcSecond メソッドで、
本来のウィンドウ プロシージャとしての処理を行っています。
そんなことしなくても直接 MainWindowProc メソッドで処理したっていいと思いますよね。
確かに、この時点ではそれでも不具合は起きません。
しかし、ある条件が満たされると、不具合が起きてしまうのです。

では試してみましょう。
今 13 行目の部分はこうなっていますが、

13:
            .lpfnWndProc=AddressOf(MainWindowProc)

以下のように書き換えてみます。

13:
            .lpfnWndProc=AddressOf(MainWindowProcSecond)

こうすることで、ポインタ経由ではなく直接 MainWindowProcSecond メソッドが
呼び出されるようになります。
ちなみに、この時点ではまだその条件は満たされていないので、
実行してみても何も不具合は起きません。
そして次に 38 行目です。
今はこうなっていますが、

38:
                DestroyWindow(hWnd)

以下のように書き換えてみます。

38:
                DestroyWindow(hMainWnd)

DestroyWindow API に渡すウィンドウ ハンドルを、
メソッドの仮引数ではなく TestWindow クラスの hMainWnd メンバにしました。
当然どちらも同じ値なので、同じ動作をすると思いますよね。
では実行して、ウィンドウを閉じてみてください。
アクセス違反になるはずです。
ではとても話がややこしくなるのですが、ゆっくり説明を入れます。

まず、最初の「ポインタを経由した WindowProcSecond メソッドの呼び出し」について。
これは、明示的に「TestWindow クラスの WindowProcSecond メソッド」を呼んでいるのが
分かるでしょうか。ポインタを利用して、メソッドを特定してはっきりと呼び出しているわけです。
これなら、呼び出されたメソッドはそのポインタを手掛かりとして、
自らが属するクラスのメンバにアクセスすることが出来ます。
ややこしいですがそういうものなのです。(爆)

次に、「直接的な WindowProcSecond メソッドの呼び出し」について。
ウィンドウ プロシージャというのは OS から呼び出されるのが普通であり、
ポインタを経由しているわけではありませんから、自らが属するクラスのことなど
全く分かりません。仮にそのクラスのメンバにアクセスしようとしてもアクセス違反に
なってしまいます。
だから、ややこしいですがそういうものなのです。((爆))

ということで、クラスに属しているウィンドウ プロシージャ(の役割を持ったメソッド)は、
OS から呼び出された段階では自らが属するクラスの素性など知り得ません。
なので、クラスのメンバにアクセスしたいなどというときは、
自らが属するクラスの This ポインタをどこかから引っ張ってきて、
そのポインタを利用する必要があります。
ポインタを利用せずにアクセスしようとすれば、当然アクセス違反になるわけです。

当然、利用する This ポインタはクラスの外に格納されていないといけませんね。
クラスの中に格納されていても、そもそも利用することが出来ませんから。
このコードでは別なクラスに This ポインタを格納していますが、
これ以外にも方法はあります。

ウィンドウ プロシージャの中でクラスのメンバにアクセスしたりしない場合は、
直接的に呼び出すようにしても構いません。

まとめると、
クラスに属しているウィンドウ プロシージャ内では
クラスのメンバにアクセスすることが出来ません。

ではクラスのメソッドを呼び出すことが出来るかというと、
可能です。但し、同じようにポインタ経由で呼び出しているわけでは
ありませんから、クラスのメンバにアクセスすることは出来ません。

このようなややこしい理由から、ウィンドウ プロシージャがああなっているのです。

この記事は、BackSearchAB.chm の「2451-メソッド内の変数のアクセス違反」
(No.5432)を参考にしました。

ページの先頭へ戻る
プログラムのテーブルへ戻る
トップ ページへ戻る

* トップ ページ - プログラムのテーブル - CTestWindow :: 概要 / コード / 解説
http://toshi.my.land.to/table/ctestwindow/
(C) 2005 - 2010 Toshi, All Rights Reserved.