はじめに
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絡みののイベントハンドリングをしたい場合は
-
SetWindowLongPtr
で自前のウィンドウプロシージャを設定 - 自前のウィンドウプロシージャから
CallWindowProc
でもとのウィンドウプロシージャを呼び出す
で実現できました。
次回予告
OpenSiv3D(Windows)でウィンドウメニューを作ろう!