5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

はじめに

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)でウィンドウメニューを作ろう!

5
3
0

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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?