LoginSignup
20
24

More than 5 years have passed since last update.

Visual Studio 2017 による Win32 API を使ったデスクトップアプリの開発

Last updated at Posted at 2018-01-20

はじめに

最近では Win32 API を使って Windows アプリケーションを新しく作成することはほとんどないと思いますが、古いプログラムをカスタマイズしたいとかは、たまにあるかもしれません。

しかし、最新の Visual Studio (ここでは VS2017) では、昔の古い Win32 アプリケーションをそのまま動かすのは簡単ではありません。

そのようなケースでは、新しくプロジェクトを作り、古いプログラムを関数単位で移植するのがいいかもしれません。

しかし、最近では Win32 の経験者は少ないし、参考書やネット情報も少ない状況です。という訳で、以下のような文書を作ってみました。(簡素な内容なのであまり役に立たないかもしれませんが)

プロジェクトの作成

Win32 API を使ったデスクトップアプリのプロジェクトは、「新しいプロジェクト」ダイアログで 「Windows デスクトップアプリケーション」 プロジェクトを選んで作成します。

VCProj_Win32_Upd3.png

もし、Visual Studio 2017 の Update 3 より前のバージョンを使っていたなら、次のようになります。(マイナーアップデートで画面まで変更するとは・・・)

VCProj_Win32Desktop.png

アプリケーション設定画面が表示されたら Windows アプリケーションを選んで「完了」ボタンをクリックします。(これは Update 3 より前の場合のみ)

VCProj_Win32Desktop2.png

次のようなプロジェクトがソリューションに追加されます。

VCProj_Win32Desktop_Source.png

そのまま実行すると、下のようなウィンドウが表示されます。

Win32Desktop_Screenshot.png

カスタマイズが必要なファイル

次のソースは、プロジェクト名と同じファイル名のソースファイル (ここでは、 Win32Desktop1.cpp) です。主にこのソースをカスタマイズしてアプリケーションを構築していきます。

このソースのカスタマイズですが、次のような知識が前提になります。これは結構、ハードルが高いです。

  • Windows の内部動作の知識 (ウィンドウメッセージにより動作することを知っている)
  • Win32 API 関数の知識 (どんな構成になっているか)
  • ウィンドウメッセージの知識 (どんなイベントでどんなメッセージが飛ぶか)
  • コントロールやメニュー等の知識 (ウィンドウリソースについて知っている)
  • デバイスコンテキスト等の知識 (ウィンドウ描画について知っている)

さて、このプログラムの動作ですが、起動すると wWinMain 関数がまずコールされます。ここでは、アプリケーションの初期化を行った後、Windows から送られてくるメッセージを処理するためのメッセージループに入ります。

wWinMain は簡単なアプリケーションなら、カスタマイズは必要ありません。もし、特別な初期動作が必要なら TODO: コメントの後にコードを追加します。

ウィンドウメッセージ待ちに入ると、メッセージが送られてくるごとに WndProc がコールされます。ここでは、Windows から送られてきたウィンドウメッセージを見て、特に反応する必要がなければ無視し、反応が必要なら特別な処理を行います。カスタマイズを行うとすれば、この WndProc が中心となります。

// Win32Desktop1.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "Win32Desktop1.h"

#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。

    // グローバル文字列を初期化しています。
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32DESKTOP1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーションの初期化を実行します:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32DESKTOP1));

    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}


//
//  関数: MyRegisterClass()
//
//  目的: ウィンドウ クラスを登録します。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32DESKTOP1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WIN32DESKTOP1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します。
//
//   コメント:
//
//        この関数で、グローバル変数でインスタンス ハンドルを保存し、
//        メイン プログラム ウィンドウを作成および表示します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // グローバル変数にインスタンス処理を格納します。

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウの描画
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// バージョン情報ボックスのメッセージ ハンドラーです。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

カスタマイズの例

ウィンドウに描画してみる。

ウィンドウへの描画は、ウィンドウメッセージ WM_PAINT が送られてきた時実行されます。したがって、WndProc の switch 文の中で case WM_PAINT の内容をカスタマイズします。

ここでは、ウィンドウに "Hello, World!" と表示することとします。具体的には、下のコードを参照してください。

ウィンドウのサイズを変更してみる。

デフォルトでは、かなり大きめのウィンドウが表示されますが、これを常に 800 x 600 (SVGA サイズ) で表示することにします。

この場合、新しいウィンドウメッセージ WM_CREATE に対応するようにコードを追加します。(WM_SHOWWINDOW でも可能ですが、これだとウィンドウが表示されてからサイズが変更されます)

ウィンドウサイズを変更するには、SetWindowPos API 関数を使用します。この関数は、ウィンドウサイズだけでなく位置や Z オーダーも同時に変更できますが、ここではサイズ以外はデフォルトを使用します。

具体的には、下のコードを参照してください。ビルドして実行すると次のようにウィンドウが表示されます。

VCProj_Win32DesktopCust.png

カスタマイズした WndProc 関数全体は下のようになります。

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウの描画
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LPCWSTR hello = _T("Hello, World!");

    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...
            // ここで、"Hellow World" を描画
            TextOut(hdc, 20, 20, hello, wcslen(hello));
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_SHOWWINDOW:
        // WM_SHOWWINDOW に反応しても可能だが、ウィンドウ表示後にサイズが変更されるため、あまりスマートではない。
        //SetWindowPos(hWnd, HWND_TOP, 0, 0, 800, 600, SWP_NOMOVE);
        break;

    case WM_CREATE:
       // ウィンドウサイズを指定 (ウィンドウ位置は既定)
       SetWindowPos(hWnd, HWND_TOP, 0, 0, 800, 600, SWP_NOMOVE);
       break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

-

20
24
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
24