PaperSloth’s diary

主にゲーム開発関連についての記事を書きます。

Poem Windowをもっと作る

追記 2018/03/01

GWLP_USERDATA
GWL_USERDATA
の2つを分けていましたが
コメントで指摘いただいたようにGet/Set WindowLongPtr系を使う場合はGWLP_USERDATAのみで問題なさそうです。
無駄なifdefも削除できるのでLongPtr, GWLPのみで統一します。
M.K.さん、ありがとうございます。


環境

Visual Studio 2015 Community
C/C++(C++11)
SVN


概要

ポエムです。制作日記みたいなものです。

前回はようやくWindowをつくりました。
papersloth.hatenablog.com


Windowをもっと作る

WindowProcを編集して複数Windowを開きたい時とかに対応できるようにしました。
なので、Window自体はまだ1つです。

前回のコールバック関数

Window.hpp

static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Window.cpp

LRESULT Window::WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_KEYDOWN:
            switch (wParam)
            {
                // TODO: キー入力処理は別途メインループから行う
                case VK_ESCAPE:
                    SendMessage(hWnd, WM_CLOSE, 0, 0);;
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return 0;
}

変更後
ちゃんとコメントも入れましたとさ。

Window.hpp

/**
* @brief 継承させて使用するWindow固有のコールバック関数
*/
virtual LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
/**
* @brief 共通のWindowコールバック関数
*/
static  LRESULT CALLBACK WindowProcWrapper(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Window.cpp

HRESULT Window::Register(LPCSTR title)
{
    const HINSTANCE hInst = GetModuleHandle(nullptr);

    wcex.cbSize       = sizeof(wcex);
    wcex.style        = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc  = WindowProcWrapper;

    // 省略
}

WindowProcを登録していた部分をWindowProcWrapperに変更しました。

/**
* @brief Windowsコールバック関数
*/
LRESULT Window::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_KEYDOWN:
            switch (wParam)
            {
                // TODO: キー入力処理は別途メインループから行う
                case VK_ESCAPE:
                    SendMessage(hwnd, WM_CLOSE, 0, 0);;
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

ここの処理は特に変わらずです。
header部分でstaticを廃止し、継承することも考えてvirtualにしました。

/**
* @brief 共通のWindowコールバック関数
*/
LRESULT Window::WindowProcWrapper(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    Window* window = reinterpret_cast<Window*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));

    if (window)
    {
        return window->WindowProc(hWnd, msg, wParam, lParam);
    }

    if (msg == WM_CREATE)
    {
        window = reinterpret_cast<Window*>((reinterpret_cast<LPCREATESTRUCT>(lParam))->lpCreateParams);
        if (window)
        {
            SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(window));
        }
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}


USERDATAがWin32とWin64で違うので、雑なマクロを作成しました。


WinProc関連で調べるとGetWindowLongがよく出てくるんですが
それだとLONGより大きいサイズのWindow*へのキャストで警告が出ます。
警告をエラーにしているので、コンパイルエラーとなります。


そのため、GetWindowLongPtrにしています。
SetWindowも同様です。
警告が出るため、SetWindowPtrにしています。

追記 2018/03/01
参考資料にMSDNも追加しましたが
Get / Set WindowLongPtr について。

"Get / Set WindowLong 関数の改訂版です。
32 ビット版 Windows と 64 ビット版 Windows の両方ともと互換性のあるコードを記述するには
Get / Set WindowLongPtr 関数を使ってください"
と、公式ドキュメントに書いてあるんですね・・・
ちゃんとドキュメント読みましょう。


上手くいっていないとDefWindowProcが動作するため
ESCキーで終了できないので成功してるかが判断できます。
もちろんログを出してもブレークポイントはってもなんでもいいですが。

とりあえずこれで動作しました。
とはいえ、メインループがよろしくないのでその辺を解決しないと色々問題が起きそうですが・・・