空のプロジェクトから作成する

VC++ で空のプロジェクトを作成して、ソースコードを全て自前で作成してみましょう。
テンプレートを使ってプロジェクトを作成すると多くのファイルが作成されますが、本当に必要なファイルは少しだけです。
またソースコードも「1/3」以下になり、非常にすっきりしたプログラムを作成することが出来ます。
このプログラムを通じて Windows Program に対する理解を深めて下さい。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プロジェクトの作成

  1. 新規プロジェクトで [空のプロジェクト](Empty)を作成します。
    1. [スタート][プログラム] から VC++ を起動して下さい。
      VC++ のアイコンをダブルクリックして起動することもできます。

    2. [ファイル][新規作成][プロジェクト]を選択すると新しいプロジェクトのウインドウが表示されます。
    3. [プロジェクトの種類]から[Visual C++ プロジェクト]を、[テンプレート]から[Win32 プロジェクト]を選択します。
    4. [プロジェクト名]に[Empty]とタイプします。

    5. [OK]をクリックしてアプリケーションウイザードが表示されます。
      [Windows アプリケーション]を確認して[空のプロジェクト]にチェックを入れ[完了]をクリックします。

  2. Empty.cpp をフォルダーに格納して[プロジェクト][既存項目の追加]からプロジェクトに追加します。
    文字列を表示する Empty.cpp のソースコードです。
    /***********************************************************/
    /*★ 空のプロジェクトからプログラムを作成する    前田 稔 ★*/
    /***********************************************************/
    #define     NAME    "Empty Program"
    #define     TITLE   "Windows Sample"
    #include    <windows.h>
    
    // Function Prototype
    int     APIENTRY    WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
    LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
    ATOM    InitApp(HINSTANCE hInst);
    BOOL    InitInstance(HINSTANCE hInst, int nCmdShow);
    
    //★ Windows Main
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                         LPTSTR lpCmdLine, int nCmdShow)
    {   MSG     msg;
    
        if (!InitApp(hInstance))                return FALSE;
        if (!InitInstance(hInstance, nCmdShow)) return FALSE;
    
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
    }
    
    //★ Windows Initialize
    ATOM InitApp(HINSTANCE hInst)
    {
        WNDCLASSEX wc;
        wc.cbSize       = sizeof(WNDCLASSEX);
        wc.style        = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc  = WndProc;      //プロシージャ名
        wc.cbClsExtra   = 0;
        wc.cbWndExtra   = 0;
        wc.hInstance    = hInst;        //インスタンス
        wc.hIcon        = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor      = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    //    wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszMenuName = NULL;
        wc.lpszClassName= NAME;
        wc.hIconSm      = LoadIcon(NULL, IDI_APPLICATION);
        return (RegisterClassEx(&wc));
    }
    
    //★ Windows の生成
    BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
    {   HWND    hWnd;
    
        hWnd = CreateWindow(NAME,TITLE,
               WS_OVERLAPPEDWINDOW, //ウィンドウの種類
               CW_USEDEFAULT,       //X座標
               CW_USEDEFAULT,       //Y座標
               CW_USEDEFAULT,       //幅
               CW_USEDEFAULT,       //高さ
               NULL,        //親ウィンドウのハンドル、親を作るときはNULL
               NULL,        //メニューハンドル、クラスメニューを使うときはNULL
               hInst,       //インスタンスハンドル
               NULL);
        if (!hWnd)      return FALSE;
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        return TRUE;
    }
    
    //★ CALLBACK 関数
    LRESULT  CALLBACK  WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
    {   PAINTSTRUCT ps;
        HDC         hdc;
    
        //渡された message から、イベントの種類を解析する
        switch(msg)
        {   case WM_PAINT:
                 hdc= BeginPaint(hWnd, &ps);
                 TextOut(hdc, 40, 70, "空のプロジェクトから作成しました", 32);
                 EndPaint(hWnd, &ps);
                 break;
            case WM_DESTROY:
                 PostQuitMessage(0);
                 return 0L;
        }
        //デフォルトの処理
        return  DefWindowProc(hWnd,msg,wParam,lParam);
    }
    
  3. ビルドに続いて実行を行います。
    "空のプロジェクトから作成しました" と表示されたら完成です。 <(`^´)>
  4. Windows 10 で空のプロジェクト Empty Hello を作成するには一工夫必要です。

【課題1】

CALLBACK 関数の先頭で、ポストされてくる全てのメッセージを捕まえて OutputDebugString() で表示して下さい。
OutputDebugString() の説明は Debug Message のプログラム を参照して下さい。
これがウインドウプログラムの制御の根幹です。
5秒間に何件ぐらいポストされてくるでしょう。
マウスをコチョコチョと動かすだけで、件数が一挙に増えるので試してみて下さい。


【課題2】

WNDCLASS や WNDCLASSEX は構造体で、初期値と共に定義することが出来ます。
また WinMain と InitApp と InitInstance と一個の関数にまとめれば、さらに半分ぐらいの行数になります。
WNDCLASS に初期値として値を設定して、三個の関数を一個にまとめて下さい。

【NOTE】

Windows Program の制御の流れは、次のようになります。

  1. ウインドウの生成に必要なパラメーターを設定して登録します。
  2. 設定されたパラメータに従って、ウインドウを生成します。
  3. ウインドウの初期化と表示を行います。
  4. メッセージがポストされるのを待つ Message Loop に入ります。
  5. CALLBACK 関数にメッセージ届くと、アプリケーションに応じた処理を行います。
  6. アプリケーションで処理しないメッセージは Windows に任せます。
  7. プログラムの終了が要求されると WM_DESTROY がポストされます。
  8. WM_DESTROY で PostQuitMessage をポストすることによりスレッド(プログラム)を終了します。

詳しい流れの説明

  1. プログラムが起動されたときに制御が渡される WinMain() 関数です。
        HWND        g_hWnd;
    
        int APIENTRY  WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
        {   MSG         msg;
        
    1. hInst
      インスタンスハンドルです。
      Windows で実行中のアプリケーションを一意に識別します。
      アプリケーションは一般的に複数のウインドウを持ちますが、ウインドウがどのアプリケーションに属するかを表すためにも使われます。
      HINSTANCE は GetWindowLong() で HWND から取得することが出来ます。
              HINSTANCE   hInst;
                  hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
              
    2. hPrevInstance
      同じプログラムのアクティブなインスタンスの中で、最も起動時刻が新しいインスタンスです。
      起動中のインスタンスが無いときは、NULL になっています。
    3. lpCmdLine
      コマンドライン引数を含む ASCII 文字列への far ポインタです。
    4. nCmdShow
      ウインドウの初期表示方法を指示する数値です。
      (1=通常ウィンドウ,7=アイコン表示)
    5. HWND g_hWnd;
      ウインドウごとに割り当てられるハンドルです。
      ハンドルとは Windows が管理する制御構造体に対するアクセス手段を提供するものです。
  2. Window を生成するソースコードです。
        // Set up and register window class
        WNDCLASS wc = { CS_CLASSDC,WndProc,0L,0L,hInstance,NULL,LoadCursor(NULL,IDC_ARROW),
                        (HBRUSH)GetStockObject(WHITE_BRUSH),NULL,NAME };
        if (RegisterClass(&wc)==0)    return FALSE;
        // Create a window
        hWnd= CreateWindow(NAME,TITLE,WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,CW_USEDEFAULT,400,200,
                NULL,NULL,hInstance,NULL);
        if (!hWnd)  return FALSE;
        
    1. WNDCLASS に Window を生成するためのパラメータを設定します。
    2. RegisterClass() で新しい Window Class を登録します。
      Window Class は NAME によって識別されます。
    3. CreateWindow() で Window Class に基づいてウインドウを作成します。
    4. CW_USEDEFAULT,CW_USEDEFAULT, は Window を表示する左上座標です。
    5. 400,200, は Window の幅と高さです。
  3. Window を表示するコードです。
        ShowWindow(hWnd,nCmdShow);      //Window を表示
        UpdateWindow(hWnd);             //表示を初期化
        SetFocus(hWnd);                 //フォーカスを設定
        
    1. ShowWindow() で Window を表示します。
    2. UpdateWindow() で Window を初期化します。
    3. SetFocus() で Window にフォーカスを設定します。
  4. メッセージループです。
    Message がポストされるまで待ち続けます。
    GetMessage() でメッセージを受け取ります。
    WM_QUIT を受け取ると FALSE を返します。
    TranslateMessage() で仮想キーを変換してメッセージキューにポストします。
    DispatchMessage() でウインドウプロシージャにメッセージを送ります。
        while (GetMessage(&msg,NULL,0,0))
        {   TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return msg.wParam;
        
  5. Message を受け取る CALLBACK 関数です。
    WM_PAINT: は Window を描く必要が生じたときにポストされます。
    WM_DESTROY: は Window が破棄された(閉じられた)ときにポストされます。
    PostQuitMessage(); は起動されているスレッドを直ちに終了させるコードです。
    DefWindowProc(hWnd,msg,wParam,lParam); はポストされた Message の処理を Window に引き継ぎます。
    自分で処理して Window に引き継ぐ必要の無いときは、リターンでゼロを返して下さい。
    //★ イベントで起動するウインドウプロシージャ
    LRESULT  CALLBACK  WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
    {   PAINTSTRUCT ps;
        HDC         hdc;
        //渡された message から、イベントの種類を解析する
        switch(msg)
        {   case WM_PAINT:
                 hdc= BeginPaint (hWnd, &ps);
                 TextOut(hdc, 40, 70, "空のプロジェクトから作成しました", 32);
                 EndPaint(hWnd, &ps);
                 break;
            case WM_DESTROY:
                 PostQuitMessage(0);
                 return 0L;
        }
        //デフォルトの処理
        return  DefWindowProc(hWnd,msg,wParam,lParam);
    }
        
  6. ポストされる Message の種類は大変多く、例えば次のようなものがあります。
  7. HDC は普通 BeginPaint() で取得するのですが GetDC(hWnd) で取得することもできます。
    GetDC() で取得した HDC は ReleaseDC() で開放して下さい。
    PAINTSTRUCT ps;
    HDC hdc;

    hdc = BeginPaint(hWnd, &ps);
    hdc = GetDC(hWnd);
           :
    ReleaseDC(hWnd,hdc);
  8. Message がポストされたこと(イベントの発生)をきっかけに処理を行う方式をイベント・ドリブンと言います。
    Windows のプログラムはメッセージループでイベントが起こるのを待ち続け Message を解析して処理を進めて行きます。

ポストされるメッセージ

CALLBACK 関数でポストされる全てのメッセージを捕まえてみました。
開始から5秒で100件以上のメッセージがポストされてきました。
マウスをコチョコチョと動かすだけで、400を超えてしまいました。
//★ CALLBACK 関数
LRESULT  CALLBACK  WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{   PAINTSTRUCT ps;
    HDC         hdc;

    char        str[80];
    static      int cnt= 0;
    cnt++;
    wsprintf(str,"%d Message Code= %X\n",cnt,msg);
    OutputDebugString(str);

    //渡された message から、イベントの種類を解析する
1 Message Code= 24
2 Message Code= 81
'C:\WINDOWS\system32\imjp81.ime' をロードしました、合致するシンボル情報は見つかりませんでした。
'C:\WINDOWS\system32\imjp81k.dll' をロードしました、合致するシンボル情報は見つかりませんでした。
'C:\WINDOWS\system32\comctl32.dll' をロードしました、合致するシンボル情報は見つかりませんでした。
'C:\WINDOWS\system32\shell32.dll' をロードしました、合致するシンボル情報は見つかりませんでした。
'C:\WINDOWS\system32\shlwapi.dll' をロードしました、合致するシンボル情報は見つかりませんでした。
'C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.0.0_x-ww_1382d70a\comctl32.dll' をロードしました、合致するシンボル情報は見つかりませんでした。
3 Message Code= 83
4 Message Code= 1
5 Message Code= 18
6 Message Code= 46
7 Message Code= 46
8 Message Code= 1C
9 Message Code= 86
10 Message Code= 7F
        :
145 Message Code= 2
146 Message Code= 82
スレッド 0xB38 終了、終了コード 0 (0x0)。
プログラム 'C:\Data\WinC\01基礎\13Empty\Debug\Main.exe' はコード 0 (0x0) で終了しました。

超初心者のプログラム入門(Win32API C++)