FreeBASIC マニュアルのトップに戻る

FreeBASIC TutMessageIntro

目次→教本→いっしょに学ぼうIntroduction to Message-Based Programming←オリジナル・サイト

対話型プログラミングへの序論 左にメニュー・フレームが表示されていない場合は、ここをクリックして下さい
(メッセージ・ベース)

←リンク元に戻る プログラム開発関連に戻る


歴史的に、プログラミング言語は、手続きベースと、メッセージ・ベースとに、分類されました。
例えば、QuickBasic は、手続き型言語として、分類できます。そして、Visual Basic は、メッセージ・ベース(イベント・ドリブン)の言語として、分類できます。

手続き型言語では、プログラムは、通常、頭から始めて、コードの内容の処理をして、直線的な流れに従って、終了します。
メッセージ・ベースの言語では、一般に、システムを初期化すると、プログラムは、遊びループに置かれていて、何かが起こるのを待っています。 何かが起こると、あなたは、それを扱います。そして、また、プログラムは遊びループに戻ります。ユーザがプログラムを終えるとき、最終的に、ループを出ます。

手続き型言語では、あなたは、ユーザが見て、することに関して、完全にコントロールします。
メッセージ・ベースのシステムでは、あなたは、オペレーティング・システムと、ユーザと、提携して働きます。あなたは、関心があるメッセージだけを扱って、残りは、オペレーティング・システムに、扱わせます。
手続き型言語から、メッセージ・ベースの言語に替わるプログラマにとって、実際の障害(つまずきの石)は、メッセージだけに応じる、という概念です。
しかし、私たちは、白黒よりむしろ、グレーの色合いに関して、話しています。
手続き型を含めて、ほとんどの言語で、メッセージは重要な役割を果たします。

あなたが、今までにサブルーチンと関数呼び出しをサポートする言語を使ったことがあるなら、メッセージ・ベースのプログラミングを使用しました。
例えば、QuickBasic で、矢印キーの1つが押されるのを待ちながら、ループに留まるゲームを書いたことがあるかもしれません。
上矢印キーが押されると、画面でスプライト(表示画面の描画表示から独立して表示できる、描画パターン)の位置を更新するサブルーチンを呼びます。 A キーが押されると、A キーは関係しないので、あなたはそれを無視します。
これは、メッセージ・ベースのプログラミングです。
ここで、メッセージは、キー押しです。そして、スプライト更新サブルーチンは、メッセージ操作者です。

構造化プログラミング言語は、いずれも、メッセージ・ベースのプログラミング言語として分類できました。
メッセージ・ベースのプログラミングは、ユーザ入力を扱って、その入力に反応する方法の、概念です。
メッセージ・ベースとは、言語の種類というより、むしろ、方法論です。
メッセージ・ベースは、オペレーティング・システムがコマンド・ラインからグラフィカル・ユーザー・インターフェース(GUIs)に発展したとき、いくつかのプログラミング言語の支配的な特徴になりました。

Windows などの GUI ベースのオペレーティング・システムでは、OS は、すべての描画要素を、内部的に管理します。
プログラマは、ゼロからテキスト編集項目を作るわけではなく、描画シェルの中に作られた編集項目を、ただ利用するだけです。
それで、ユーザが、編集項目を更新しようとしていることを、プログラマに通知する方法が、なければなりません。
最も自然な方法は、編集項目が更新されたことを示すメッセージを、プログラムに送ることです。
Windowsの下では、GUI 要素のこの借用語と、メッセージの受信は、いわゆる Windows SDK (Windows Software Development Kit:ソフトウェア開発キット)として、正式なものにされました。

Windows SDK は、アプリケーション・プログラミング・インターフェース(API)の集まりで、オペレーティング・システムの大部分を形成する、動的リンク・ライブラリ(DLLs)のセットの中に入っています。
Windows で実行される、どんな GUI ベースのプログラムも、SDK を使用します。容易に明らかでない場合でも。
Delphi、Visual Basic、Real Basic などの、高速アプリケーション開発(RAD)言語では、言語は、プロパティとイベントを使って、SDKの細部を隠します。しかし、フードの下で、言語は、SDKを使っています。

RAD 言語は、プログラマが、すぐに GUI ベースのプログラムを組立てることを可能にします。しかし、また、それは、SDKの、より詳しい細部には、アクセスできないことを意味します。
例えば、VBを使って、サブクラスを制御することは、かなり難しいです。しかし、SDK を使うと、かなり簡単ですか?
しかし、SDK は巨大です。そして、APIの剪断サイズは、多くのプログラマを SDK プログラミングの考えに見切りをつけさせるのに、充分です。
一般的な考えは、それが使用できないくらい複雑で、難しいということです。しかし、その反対は真実です。
オペレーティング・システムが、プログラムのための、すべての描画要素を扱うので、プログラマは、プログラム・デザインの最も重要な局面、ユーザ相互作用に集中できます。
結局、GUI プログラムは、ユーザ相互作用に関するものです。

FreeBasic には、Windows プログラミングの RAD システムがありません。
FreeBasic で、Windows プログラムを作成するのに、あなたは SDK を使わなければなりません。これは、唯一のオプションです。
渡辺 注:FbEdit の RAD や、Window9 を使うと、もう少し楽になる?

SDK は、大規模であり、Windows プログラムの 99% に相当するため、SDK のすべてを分かろうとすると、生涯がかかるでしょう。しかし、我々が実際に使うのは、SDK の小さい部分集合だけです。
現実は、Windows SDKプログラミングは、他のタイプのいかなるプログラミングよりも少しも難しくはありません。そして、GUI ベースのプログラムを作るとき、あなた自身ですべての GUI 要素を作成しなければならない言語より、実際に簡単です。

さしあたり Windows API の、すべての砂だらけの細部は、横に置いておいて、SDK プログラムの中の、メッセージのメカニズムを理解することが、重要です。
旧知の、Hello World プログラムを確認してこれを達成すると、良いでしょう。
FreeBasic の、プログラム事例フォルダに、素敵な Hello World プログラムがあります。
FreeBASIC\examples\Windows\gui\hello.bas
下の例は、このサンプルを借用して、変更を加えたものです。

'' Option Explicit     渡辺 注:コメント・アウトします
'' Option Private      渡辺 注:コメント・アウトします

#include once "windows.bi"

Declare Function        WinMain     ( ByVal hInstance As HINSTANCE, _
                                      ByVal hPrevInstance As HINSTANCE, _
                                      szCmdLine As String, _
                                      ByVal iCmdShow As Integer ) As Integer

    ''
    '' 開始位置(エントリーポイント:呼び出し位置)
    ''
    End WinMain ( GetModuleHandle( null ), null, Command$, SW_NORMAL )

'' ::::::::
'' 名前: WndProc
'' 内容: 窓のメッセージ処理
''
'' ::::::::
Function WndProc ( ByVal hWnd As HWND, _
                   ByVal message As UINT, _
                   ByVal wParam As WPARAM, _
                   ByVal lParam As LPARAM ) As LRESULT

    Function = 0

    ''
    '' メッセージ処理
    ''
    Select Case( message )
        ''
        '' 窓は作成されました
        ''
        Case WM_CREATE           
            Exit Function

        '' ユーザはフォームをクリックしました
        Case WM_LBUTTONUP
            MessageBox NULL, "今日は 世界! from FreeBasic", "FB Win", MB_OK
        ''
        '' Windowsは塗り替えられています
        ''
        Case WM_PAINT
            Dim rct As RECT
            Dim pnt As PAINTSTRUCT
            Dim hDC As HDC

            hDC = BeginPaint ( hWnd, @pnt )
            GetClientRect ( hWnd, @rct )

            DrawText( hDC, _
                      "Hello Windows from FreeBasic!", _
                      -1, _
                      @rct, _
                      DT_SINGLELINE Or DT_CENTER Or DT_VCENTER )

            EndPaint ( hWnd, @pnt )

            Exit Function           

        ''
        '' キーが押されました
        ''
        Case WM_KEYDOWN
            'esc キーが押されたら、終了します
            If( LoByte( wParam ) = 27 ) Then
                PostMessage ( hWnd, WM_CLOSE, 0, 0 )
            End If

        ''
        '' 窓は閉じられました
        ''
        Case WM_DESTROY
            PostQuitMessage ( 0 )
            Exit Function
    End Select

    ''
    '' メッセージは私たちに無関係です。メッセージは、デフォルト・ハンドラーに送られます
    '' そして、結果を得ます
    ''
    Function = DefWindowProc ( hWnd, message, wParam, lParam )

End Function

'' ::::::::
'' 名前: WinMain
'' 内容: win2 gui プログラム・開始位置(エントリーポイント:呼び出し位置)
''
'' ::::::::
Function WinMain ( ByVal hInstance As HINSTANCE, _
                   ByVal hPrevInstance As HINSTANCE, _
                   szCmdLine As String, _
                   ByVal iCmdShow As Integer ) As Integer   

    Dim wMsg As MSG
    Dim wcls As WNDCLASS     
    Dim szAppName As String
    Dim hWnd As HWND

    Function = 0

    ''
    '' ウィンドウのクラスを設定
    ''
    szAppName = "HelloWin"

    With wcls
        .style         = CS_HREDRAW Or CS_VREDRAW
        .lpfnWndProc   = @WndProc
        .cbClsExtra    = 0
        .cbWndExtra    = 0
        .hInstance     = hInstance
        .hIcon         = LoadIcon ( NULL, IDI_APPLICATION )
        .hCursor       = LoadCursor ( NULL, IDC_ARROW )
        .hbrBackground = GetStockObject ( WHITE_BRUSH )
        .lpszMenuName  = NULL
        .lpszClassName = StrPtr ( szAppName )
    End With

    ''
    '' ウィンドウのクラスを登録します
    ''
    If( RegisterClass( @wcls ) = FALSE ) Then
       MessageBox ( null, "Failed to register wcls!", szAppName, MB_ICONERROR )
       Exit Function
    End If

    ''
    '' 窓を作成して、これを表示します
    ''
    hWnd = CreateWindowEx( 0, _
                           szAppName, _
                           "The Hello Program", _
                           WS_OVERLAPPEDWINDOW, _
                           CW_USEDEFAULT, _
                           CW_USEDEFAULT, _
                           CW_USEDEFAULT, _
                           CW_USEDEFAULT, _
                           NULL, _
                           NULL, _
                           hInstance, _
                           NULL )

    ShowWindow ( hWnd, iCmdShow )
    UpdateWindow ( hWnd )

    ''
    '' 窓のメッセージ処理
    ''
    While( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )
        TranslateMessage ( @wMsg )
        DispatchMessage ( @wMsg )
    Wend

    ''
    '' プログラムは終わりました
    ''
    Function = wMsg.wParam

End Function


上のプログラムをコンパイルして、動かすと、標準の窓が開いて、メッセージがフォームの上に表示されるでしょう。
フォームをマウスでクリックすると、メッセージ・ボックスが表示されます。そして、Escape キーを押すと、プログラムは終了します。

窓を調べるために、窓が開いた状態で止めて下さい。
フォームの右上には、システム・メニューの、最大、最小、終了ボタンがあります。マウスのドラッグで、窓のサイズをリサイズできることも、試してみて下さい。

今度は、上のコードを見てください。
上で調べた窓の属性を取り扱うためのコード記述は、どこにもありません。OS が、あなたのために、そのすべてを処理しているのです。
また、messagebox を表示するのに必要なのは、たった一行のコードだけです。(messagebox それ自体は、かなり複雑なオブジェクトです。)
結果と、コードの量の比率は、かなり顕著です。
この簡単なプログラムを、FreeBasic 標準の描画コマンドだけを使って、再作成すると、プログラム・コードの量は、100倍以上になるでしょう。
注:MessageBox()の構文
int MessageBox(HWND hWnd , LPCTSTR lpText , LPCTSTR lpCaption , UINT uType)
hWnd - オーナーウィンドウを指定。NULLの場合はオーナーウィンドウを持ちません
lpText - メッセージボックスに表示する文字列
lpCaption - タイトルバーに表示される文字列
uType - メッセージボックスの内容

戻り値 - メッセージボックスの押されたボタンを整数値で返します
   MessageBox の詳細は、下記を参照下さい。
   Win32API メッセージボックス

上に記載されたコードに関して、最初に気付いてほしいことは、形式です。
これは、全ての FreeBasic Windows プログラムの、基本形式です。
あらゆる Windows プログラムは、簡単か複雑かにかかわらず、この同じ基本形式に従います。
このプログラムの2つの主成分が、WinMainWinProc 手続きです。

WinMain 手続きは、プログラムが始まるとき、Windows を呼ぶ手続きです。
これは、Windows プログラムの入り口です。
WinMain に、主プログラム・ウインドウを造って、登録します。そして、メッセージを処理するために、メッセージ・ループを始めます。
プログラムがいったんメッセージ・ループに入ると、プログラムは、WinProc 手続きで、メッセージを処理し始めます。
この記事は、Windowsプログラムのメッセージ・モデルに関するものなので、私たちは、WinMain のメッセージ・ループと、WinProc 手続きについて述べるだけです。

ウインドウズ・オペレーティング・システムが稼働しているとき、常に生成されているメッセージがあります。
Windows プログラムが動いているとき、OS がプログラムに知らせるべきだと思うことが起こると、OSは、プログラムにメッセージを送ります。
これらのプログラムに特有のメッセージの一部は、直接 WinProc (または類似の) 手続きに送られます。そして、他 のもの (主にユーザー生成のメッセージ) は、メッセージ・キューに置かれます。
プログラムの大部分は、ユーザ相互作用に関係があるので、メッセージ・キューを理解することは、重要です。

待ち行列は、データが、待ち行列の「後部」に加えられて、「前部」から取り除かれる、データ構造です。
これは、先入れ先出し、または、FIFO スタックと呼ばれます。
今までに行列で映画の切符を買うのに耐えたことがあるのなら、あなたは待ち行列を経験しました。

プログラムのために、メッセージキューは、1つ以上のメッセージを保持します。映画の切符の行列の人々のように、並べられています。
Windows プログラムの待機ループは、座って、到着するメッセージを待っています。そして、メッセージを翻訳して、プログラムに送ります。
このメッセージ・ループは、WinMain からの、下のコード抜粋の中に、含まれています。

    ''
    '' 窓のメッセージ処理
    ''
    While( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )
        TranslateMessage ( @wMsg )
        DispatchMessage ( @wMsg )
    Wend


GetMessage 手続きは、wMsg パラメタを通して、待ち行列からメッセージを検索します。
wMsg パラメタは、MSG type-def です。これは、特定のメッセージに関連する必要事項を含んでいます。
ここに、MSG type-def の定義があります。

Type MSG
hwnd As HWND
    message As UINT
    wParam As WPARAM
    lParam As LPARAM
    Time As DWORD
    pt As Point
End Type


hwnd は、メッセージを処理する必要がある窓の、ハンドルです。
このメッセージは、その窓の WinProc 手続きで、処理されます。

Message は、メッセージ識別子です。
これは、例えば、WM_CREATE であるかもしれません。WM_CREATE は、窓が作成されたが、まだ表示されていない、と合図します。

wParamlParam は、ともに、メッセージ型に基づく、追加情報を指定します。
例えば、キーが押されるとき、wParam の lobyte を使って、キー・コードを検索できます。

time は、メッセージが送られた時刻を指定します。そして、pt は、メッセージが送られたときの、カーソルの位置を含む、構造です。

TranslateMessage は、仮想のキー・メッセージを文字メッセージに変換します。次に、そのキーが必要により処理されるように、待ち行列に置きます。
キーボードを使うどんなプログラムも、この手続きを必要とします。
DispatchMethod は、hWnd パラメタで特定される窓に関連している、windows WinProc (または同様の) 手続きに、メッセージを送ります。

動作をここでまとめるために、ユーザー生成のメッセージは、メッセージ・キューの「後ろ」に置かれます。
GetMessage は、最初の待ちメッセージを検索して、必要なら、メッセージを変換する TranslateMessage にそれを渡します。そして、それを待ち行列に戻します。
メッセージは、次に、DispatchMessage に渡されます。DispatchMessage は、どの窓がメッセージを受取るべきかを確認するために、メッセージを調べます。次に、メッセージを、窓の操作者手続きに、渡します。私たちの例では、それは、WinProc です。

WinProc 手続きについて議論する前に、しかし、私たちは、質問があります:
複数の窓がプログラムにあれば、何が起こりますか?
WinMain は、どの手続きを用いたらよいか、どうやってわかりますか?
答えは、WNDCLASS 構造の中に含まれています。
私たちの例では、wcls は、WinMain 手続きの中で、WNDCLASS と定義されています。

    With wcls
        .style         = CS_HREDRAW Or CS_VREDRAW
        .lpfnWndProc   = @WndProc
        .cbClsExtra    = 0
        .cbWndExtra    = 0
        .hInstance     = hInstance
        .hIcon         = LoadIcon ( NULL, IDI_APPLICATION )
        .hCursor       = LoadCursor ( NULL, IDC_ARROW )
        .hbrBackground = GetStockObject ( WHITE_BRUSH )
        .lpszMenuName  = NULL
        .lpszClassName = StrPtr ( szAppName )
    End With


お分かりのように、WNDCLASS 構造は、特定の窓に関係する、全ての情報をつかみます。
メッセージに関連して、重要な項目は、.lpfnWndProc 項目です。
この項目は、この窓のために、メッセージ操作者のアドレスを、つかみます。
FreeBasic の @ 演算子は、オブジェクトのアドレスを、この場合、WinProc 手続きのアドレスを、返します。
いったん、この窓が、RegisterClass メソッドを使って登録されると、Windows は、メッセージを処理するために、どんな手続きを用いたらよいかが、わかります。

お分かりのように、WinProc という名前に、どんな特別な意味もありません。
WinProc を、MyWinProc とか、WinHandler とか呼ぶことも、できます。
実際の SDK 名は、WindowProc です。WindowProc は、ただユーザ定義のメッセージ操作者名のための、代用語です。
情報の重要な部分は、それを何という名前で呼んでも、メッセージ操作者は、WinProc で定めたものと同じパラメータを持たなければならないということです。そして、その手続きのアドレスは、 .lpfnWndProc に保存されなければならない、ということです。

メッセージは、ユーザが発生したものか、システムが、定義されたメッセージ操作者を通して渡したものかにかかわらず、すべてのメッセージは、私たちの例のでは、WinProc です。
WinProc のパラメータ・リストを見て、GetMessage が、メッセージ構造の部品の大部分を検索することが、わかります。
hwnd は、ウィンドウ・ハンドルです。message は、メッセージ id です。そして、wParam と lParam は、追加メッセージ情報を保持します。
メッセージが、WinProc に渡されると、次に、私たちは、メッセージに興味を持っているかどうかを、決めなければなりません。

メッセージを扱いたいと思うなら、見るために、メッセージ・パラメータを調べる、選ばれたケースで、通常は、行います。
例えば、メッセージが WM_PAINT であれば、窓の全てか一部が再描画される毎に、私たちの窓をアップデートするように、WM_PAINT メッセージの下に、描画コードを置くでしょう。
私たちが、メッセージを考慮しないなら、メッセージを、デフォルト・メッセージ操作者に扱わせるために、単に、DefWindowProc に、メッセージを渡します。

ここでやることは、かなり簡単です。
WinMain か Windows は、メッセージを私たちに送ります。そして、私たちは興味があるメッセージに応じます。
メッセージが、メッセージ・キューに入るのに従って、メッセージは、処理されて、WinProc に送られます。WinProc で、メッセージは、さらに処理されるかもしれません。そして、次に、オペレーティング・システムで処理されるために、DefWindowProc に回されます。
このループは、プログラムが終了するまで続きます。WM_QUIT メッセージを受信して、その時点で窓が破壊されて、プログラムが終えられるまでです。

私たちのプログラムの例では、WM_LBUTTONUP、WM_PAINT、WM_KEYDOWN の 3つのメッセージだけを、考慮しています。
WM_CREATE と WM_DESTROY は、基本的な決まり文句で、どんなウィンドウ・プログラムでも見つけるられるでしょう。
私たちは、これらの3つのメッセージにしか興味を持っていないので、コードを書く必要があるのは、これらの3つのメッセージに対してだけです。
私たちが受け取るかもしれない残りのメッセージは、私たちに関係がないので、私たちはそれらを探しさえしません。

メッセージ・ベースの言語で、あなたは、起こったイベントを扱うためにコードを書いています。
私たちは、まさしくイベントを記載したメッセージを受け取ったので、イベントが行われたことを認知したことでしょう。
私たちは、関心があるイベントに対して、それに応じるコードを書きます。
私たちは、あるイベントのためにコードを書く必要があるだけです。そして、オペレーティング・システムに、他の何もかもを扱わせます。
手続き型言語でするような、プログラムのあらゆる局面を扱うために多量のコードを書く必要は、ありません。

もちろん、窓とコントロールを作成するために、コードを書かなければなりませんが、これは、ほとんど、コードの決まり文句の型です。
単純に API に従って、CreateWindow 関数に適切なパラメタを渡します。
いったん決まり文句を理解すると、それは、必要なプログラムに、そのコードを差し込む単純な作業です。
窓かコントロールと対話すると、実際の作用は、WinProc 関数の中で起こります。

メッセージ・ベースのプログラミングは、手続き型のプログラミングとは異なった考え方を、必要とします。
手続き型言語では、ユーザは、プログラムに、応じなければなりません。プログラマが仕切っています。
GUIプログラムでは、プログラムはユーザに反応しなければなりません、そして、ユーザが仕切っています。
有効な GUI プログラムを書くために、プログラマは、プログラムの制御を放棄しなければなりません。そして、オペレーティング・システムとユーザと、提携して仕事をしなければなりません。

GUI プログラムを設計するとき、あなたは、自分に尋ねなければなりません。プログラムが、どのようにユーザに反応して欲しいですか ?
例えば、アプリケーションが最小化されたとき、プログラムは、このイベントを無視しますか。あるいは、このアプリケーション自体をシステムトレーに置くように、何かをするべきですか?
これはメッセージ・ベースのプログラミングの、本質です。
どのイベントが重要かを定義して、次に、各イベントを扱う個々のルーチンを書きます。
メッセージ・ベースのプログラムは、単純に、特定のメッセージに対応して書かれた、特殊ルーチンの集まりです。

SDK の評判にもかかわらず、メッセージ・ベースのプログラミングの基本概念は、かなり簡単です。
あなたは、メッセージを扱うためのルーチンの集まりを、書いています。 これは、中心的な仕事です。
ウインドウを作成したり、塗り直したりするなどの、すべての他のものは、オペレーティング・システムによって行われます。 それは、ほとんどの人々が利用する SDK の範囲です。
そこに、いろいろな事があります。
しかし、決まり文句が言うように、 象を食べる最良の方法は、一口ずつ時間をかけることです。
SDK をマスタする最も良い方法は、単純にメッセージ・ベースのプログラミングの概念を理解して、決まり文句のコードを学ぶことです。
一旦それができれば、高度な Windows プログラムを作ることは、そんなに難しいことではありません。

いっしょに学ぼう に戻る
←リンク元に戻る プログラム開発関連に戻る

ページ歴史:2018-02-11 11:27:49
日本語翻訳:WATANABE Makoto、原文著作者:WikiRick(Rick Clark aka rdc)

ホームページのトップに戻る

表示-非営利-継承