search
LoginSignup
3

More than 1 year has passed since last update.

posted at

OpenSiv3D(Windows)で自前のウィンドウプロシージャを使用する

はじめに

OpenSiv3Dでウィンドウプロシージャを使用したい場面にであったのですが
裏で呼ばれているウィンドウプロシージャは隠蔽されていて、ユーザーコード側で触れないので
このあたりの対処法をメモしておきます。

OpenSiv3D自体にはそのような機能はないのでWin32APIで解決します。

実装

以下は最小限のコードです

Main.cpp
#include <Siv3D.hpp> // OpenSiv3D v0.4.3
#include <Siv3D/Windows.hpp>

namespace
{
    LONG_PTR g_baseProc = 0;
    LRESULT CALLBACK CustomWindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        // 自前のイベント処理をする

        // もともとOpenSiv3Dで呼ばれてたWindowProcを呼ぶ
        return CallWindowProc(reinterpret_cast<WNDPROC>(g_baseProc), hWnd, message, wParam, lParam);
    }
}

void Main()
{
    // Windowハンドル取得
    auto hWnd = static_cast<HWND>(s3d::Platform::Windows::Window::GetHWND());

    // 自前のWindowProcに切り替え
    // 返り値は元々のWindowProc
    g_baseProc = ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(::CustomWindowProc));

    while (System::Update()) {
        // something
    }
}

たったこれだけです。

Win32APIのSetWindowLongPtrで自前のウィンドウプロシージャを設定し、
設定した自前のウィンドウプロシージャからCallWindowProcでもとのウィンドウプロシージャを呼び出すようにしています。


ちなみにOpenSiv3Dには<Siv3D/Windows.hpp>が用意されていたので
生の<Windows.h>をインクルードするよりこちらの使用を強く推奨します。
(NOMINMAXなどをセットでやってくれています。)

おまけ class化する

私はclassにしておきたかったのでむりやりclass化しました。

Main.cpp
#include <Siv3D.hpp> // OpenSiv3D v0.4.3
#include "CustomWindowProc.hpp"

void Main()
{
    // 自前ウィンドウプロシージャ登録
    Windows::CustomWindowProc proc([](HWND, UINT, WPARAM, LPARAM)->s3d::Optional<LRESULT>{

        // 自前のイベント処理をする

        return s3d::none; // 特になければnoneを返す
    });

    while (System::Update()) {
        // something
    }
}

実装


CustomWindowProc.hpp
CustomWindowProc.hpp
#pragma once
#include <memory>
#include <functional>
#include <Siv3D/Optional.hpp>
#include <Siv3D/Windows.hpp>

namespace Windows
{
    /// <summary>
    /// カスタムウィンドウプロシージャ
    /// </summary>
    class CustomWindowProc
    {
    public:
        using Callback_t = std::function<s3d::Optional<LRESULT> CALLBACK(HWND, UINT, WPARAM, LPARAM)>;

        class Impl;
    public:

        /// <summary>
        /// コンストラクタ
        /// </summary>
        CustomWindowProc();

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="callback">登録するコールバック</param>
        CustomWindowProc(const Callback_t& callback);

        /// <summary>
        /// プロシージャのコールバックを登録
        /// </summary>
        /// <param name="callback"></param>
        void setCallback(const Callback_t& callback);
    private:
        std::shared_ptr<Impl> m_pImpl;
    };
}



CustomWindowProc.cpp
CustomWindowProc.cpp
#include "CustomWindowProc.hpp"
#include <Siv3D.hpp>

namespace
{
    HWND g_hWnd;
    LONG_PTR g_baseProc = 0;

    s3d::HashTable<Windows::CustomWindowProc::Impl*, Windows::CustomWindowProc::Callback_t> g_callbacks;

    LRESULT CALLBACK CustomWindowProcImpl(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        for (auto&& [_, callback] : g_callbacks) {
            if (!callback) {
                continue;
            }
            if (auto result = callback(hWnd, message, wParam, lParam); result) {
                return result.value();
            }
        }
        return CallWindowProc(reinterpret_cast<WNDPROC>(g_baseProc), hWnd, message, wParam, lParam);
    }
}

namespace Windows
{
    class CustomWindowProc::Impl
    {
    public:
        Impl()
        {
            if (g_callbacks.empty() && !g_baseProc) {
                g_hWnd = static_cast<HWND>(s3d::Platform::Windows::Window::GetHWND());
                g_baseProc = ::SetWindowLongPtr(g_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(::CustomWindowProcImpl));
            }
            g_callbacks[this] = nullptr;
        }

        ~Impl()
        {
            g_callbacks.erase(this);
            if (g_callbacks.empty() && g_baseProc) {
                ::SetWindowLongPtr(g_hWnd, GWLP_WNDPROC, g_baseProc);
            }
        }
    };
    CustomWindowProc::CustomWindowProc() :
        m_pImpl(std::make_shared<Impl>())
    {}

    CustomWindowProc::CustomWindowProc(const Callback_t& callback) :
        CustomWindowProc()
    {
        this->setCallback(callback);
    }

    void CustomWindowProc::setCallback(const Callback_t& callback)
    {
        g_callbacks[m_pImpl.get()] = callback;
    }
}


まとめ

OpenSiv3DでWinAPI絡みののイベントハンドリングをしたい場合は

  1. SetWindowLongPtrで自前のウィンドウプロシージャを設定
  2. 自前のウィンドウプロシージャからCallWindowProcでもとのウィンドウプロシージャを呼び出す

で実現できました。

次回予告

OpenSiv3D(Windows)でウィンドウメニューを作ろう!

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
What you can do with signing up
3