[WinUI3] WinUI3でウインドウプロシージャをフックする（最大/最小ウインドウサイズの指定にも使える）

もくじ  
https://tera1707.com/entry/2022/02/06/144447#WinUI3

# やりたいこと

以前、下記の記事で調べた、WPFでウインドウプロシージャをフックするということを、WinUI3でもやりたい。

https://qiita.com/tera1707/items/fc6b4bed1b2709d21a03

# 前提

- VisualStudio2022 Community 17.3.0
- WinUI3 と パッケージプロジェクト
- Windows App SDK 1.1.3
- PInvoke.User32 v0.7.124のnugetパッケージを使用

# メモ

別記事で、[WinUI3で最小ウインドウサイズを指定する](https://tera1707.com/entry/2022/08/29/232656)やり方を調べたときの副産物として、やり方が分かったのでメモしておく。

# やりかた

ざっくり下記のような流れの処理を、メインウインドウの中で行う。

- WinUI3ウインドウのウインドウハンドルを取ってくる
- 自分の関心のあるWMメッセージの処理を書いた新しいウインドウプロシージャを用意する
- `SetWindowLong()`に、下記をセットして、WMメッセージ到来時新しいプロシージャが呼ばれるようにする
  - ウインドウのハンドル
  - GWL_WNDPROC
  - 用意した新しいウインドウプロシージャ
- SetWindowLong()の戻り値で、元のウインドウプロシージャが取得できるので、いったん保存する
- 自分が関心のないメッセージを処理してもらうために、
  - 保存した元のプロシージャを、
  - 新しいプロシージャの最後で`CallWindowProc()`を使って呼んでやる

# サンプルコード

下記のサンプルでは、WM_MOVEをフックして、ウインドウの位置をDebug出力している。

```cs
using Microsoft.UI.Xaml;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

// nuget PInvoke.User32 0.7.124

namespace MinWidthJikken
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            // サブクラス化する
            SubClassing();
        }

        private NativeMethods.WinProc newWndProc = null;
        private IntPtr oldWndProc = IntPtr.Zero;

        private void SubClassing()
        {
            // ウインドウのハンドルを取ってくる
            var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            newWndProc = new NativeMethods.WinProc(NewWindowProc);
            //oldWndProc = NativeMethods.SetWindowLong(hwnd, PInvoke.User32.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
            oldWndProc = NativeMethods.SetWindowLongPtr64(hwnd, PInvoke.User32.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
        }

        private IntPtr NewWindowProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
        {
            switch (Msg)
            {
                case PInvoke.User32.WindowMessage.WM_MOVE:
                {
                    // 移動した座標を取得
                    var position = lParam.ToInt32();
                    var x = position % 0x010000;
                    var y = position / 0x010000;
                    Debug.WriteLine($"WM_MOVE ({x}, {y})");
                }
                break;

            }
            // WM_GETMINMAXINFO以外の、自分で処理したくないMsgは、もとのWndProcに任せる
            return NativeMethods.CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam);
        }
    }

    public class NativeMethods
    {
        public delegate IntPtr WinProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);

        //[DllImport("user32")]
        //public static extern IntPtr SetWindowLong(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc newProc);
        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
        public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc dwNewLong);
        [DllImport("user32.dll")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);
    }
}
```

# 注意

x86ビルドで試していたので、`SetWindowLong()`を使っていたが、それをx64にすると、WndProcを通らず、うまく動かなくなる。

x64では、`SetWindowLongPtr64()`を使わないといけない。

```cs
        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
        public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc dwNewLong);
```


# 参照

WinUI3で最小ウインドウサイズを指定する方法を調べた記事

https://tera1707.com/entry/2022/08/29/232656

「サブクラス化」についてのわかりやすい記事

http://wisdom.sakura.ne.jp/system/winapi/win32/win64.html

最大/最小ウインドウサイズの指定の仕方が紹介されたMSのgithubのissue

https://github.com/microsoft/microsoft-ui-xaml/issues/2945

SetWindowLongA function (winuser.h)

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlonga

CallWindowProcA function (winuser.h)

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-callwindowproca