プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

C++ 極小レベルのWinFormアプリを.NET系無しwin32で作成

概要とか

元々 C# で WindowsFormsApplication とか WPF やらのウィンドウアプリ触ってたんですが、
C# がデフォで使う .NET Framework って中間言語という扱いらしく、
高速起動とかメモリ消費量とかにこだわる場合は .NET系に頼らない選択肢もあるっぽいので、C++ に手を出した時のメモです

プロジェクト作成など

まず Visual Studio Installer から C++ によるデスクトップ開発 を有効化(インストール)しておく。 ( 画像右上 )

ちなみに、本末転倒な話だけど .NET .NET Framework を使うなら、
インストールの詳細/オプション内から v143 ビルド ツール用 C++/CLI サポート (最新) にチェックを入れると良さそう?

新規プロジェクト作成 ~ .cpp 追加

Windows デスクトップ ウィザード を選択して次へ。
( 画像は [ C++ & Windows & デスクトップ ] で条件を絞り込んだ状態 )

「アプリケーションの種類」を デスクトップアプリケーション (.exe) に変更し、
空のプロジェクト にチェックを入れて作成すればOK

こうすると、マジで何もないプロジェクトが出来上がるので、手作業でファイルを追加。
プロジェクト メニュー >> 新しい項目の追加 >> C++ ファイル(.cpp)
本例ではファイル名を main.cpp とした。

あとは .cpp に次項のソースを貼り付けて実行すれば、
以下のようなウィンドウが表示される

.cpp 用ソース

ちょいちょいコメントメモ付き

#include <windows.h>
#include <stdlib.h>
#include <tchar.h>

// 関数の事前宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine,_In_ int nCmdShow)
{
    // ウィンドウクラス名。
    static TCHAR szWindowClass[] = _T("DesktopApp");

    // ▼ ウィンドウクラス登録用の情報を詰め込んでいく
    WNDCLASSEX 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(wcex.hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    // ▼ 上で仕込んだクラス情報をここで登録
    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL, _T("ウィンドウ登録失敗"), L"エラー" , NULL);
        return 1;
    }

    // ▼ ウィンドウ作成
    HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, L"タイトル" , WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        300, 100,
        NULL, NULL, hInstance, NULL );

    if (hWnd == NULL)
    {
        MessageBox(NULL, _T("ウィンドウ作成失敗"), L"エラー" , NULL);
        return 1;
    }

    // ▼ ウィンドウ表示
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) // メッセージループ
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

// メインウィンドウのメッセージ処理用
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    TCHAR szMessage[] = _T("表示したい文字列");
    switch (message)
    {
        // メインウィンドウ描画
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            // テキスト描画のレイアウト処理
            TextOut(hdc, 5, 5, szMessage, _tcslen(szMessage));
            // レイアウト処理終了
            EndPaint(hWnd, &ps);
            break;
        // 終了時、メッセージを投げて戻る
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

旧スタイルでウィンドウ作成

uxtheme.lib 周りを使えるようにしつつ、 SetWindowTheme() を仕込むだけでスタイルが変わった。
( ※ メニューとか細部のスタイルまでは未検証 )

#pragma comment (lib, "uxtheme")
#include <windows.h>
#include <Uxtheme.h>
// ★★ ↑ Uxtheme 関連のおぜんだて ★★

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
{
    const WCHAR* szClassName = L"myWindowClass";
    WNDCLASSEX 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(wcex.hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szClassName;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL, L"ウィンドウ登録失敗" , L"エラー", NULL);
        return 1;
    }

    HWND hWnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, szClassName, L"タイトル", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 250, 100,
        NULL, NULL, hInstance, NULL);

    if (hWnd == NULL)
    {
        MessageBox(NULL, L"ウィンドウ作成失敗" , L"エラー", NULL);
        return 1;
    }

    SetWindowTheme(hWnd, L" ", L" "); // ★★ このタイミングで仕込む ★★
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
  • 余談
    「Uxtheme を使うには プロジェクトプロパティから "リンカー > 入力 > 追加の依存ファイル" で uxtheme.lib 突っ込んでくれ」
    …みたいな古めの情報があったんだけど、自環境ではうまくいかず。
    代わりに、 #pragma comment (lib, "uxtheme") を冒頭に載せるだけで自分は解決しました。
    https://learn.microsoft.com/en-us/answers/questions/302560/x64-and-comctl32-lib

リソースファイル(.rc)でメニューを追加表示

下記リンクを参考に、プロジェクトにリソースファイル(.rc)を追加 ⇒ メニュー項目を作成
シフトシステム株式会社 - 一週間で身につくWIN32プログラミングの基本

特に捻らなければ画像左側末端の通り IDR_MENU1 というリソースシンボル名でメニューが作られるはずなので、
あとはコード内で MAKEINTRESOURCE(IDR_MENU1) として得たのち WNDCLASSEX.lpszMenuName へ割り当てればOK
microsoft.com - MAKEINTRESOURCEW マクロ (winuser.h)

( ※ メニュー内項目が押された時の挙動は未設定です )

    const WCHAR* szClassName = L"myWindowClass";
    WNDCLASSEX wcex;
    // …中略…
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
-    wcex.lpszMenuName = NULL;
+    wcex.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
    wcex.lpszClassName = szClassName;
    // …以下略…

Classicスタイルにならない問題

ウィンドウ・メニューバーと違い、展開された子メニュースタイルはOS標準(?)のほうだった。

解決方法がわからなかったので保留というかスルー
もっと深い部分をこねくりまわしながら、動的メニュー作成をすれば上手くいくかもしれない。