From 6fefc3bee0692310ed14babecba948e3f9777c69 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 17 Dec 2023 14:09:38 +0900 Subject: [PATCH] Safer unload --- .../Hooking/WndProcHook/WndProcHookManager.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs b/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs index 00934f27f0..91020f8980 100644 --- a/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs +++ b/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; +using Dalamud.Interface.Internal; using Dalamud.Interface.Utility; using Dalamud.Logging.Internal; @@ -21,6 +22,8 @@ internal sealed class WndProcHookManager : IServiceType, IDisposable private readonly Hook dispatchMessageWHook; private readonly Dictionary wndProcOverrides = new(); + private HWND mainWindowHwnd; + [ServiceManager.ServiceConstructor] private unsafe WndProcHookManager() { @@ -31,6 +34,12 @@ private unsafe WndProcHookManager() 0, this.DispatchMessageWDetour); this.dispatchMessageWHook.Enable(); + + // Capture the game main window handle, + // so that no guarantees would have to be made on the service dispose order. + Service + .GetAsync() + .ContinueWith(r => this.mainWindowHwnd = (HWND)r.Result.Manager.GameWindowHandle); } [UnmanagedFunctionPointer(CallingConvention.StdCall)] @@ -49,7 +58,19 @@ private unsafe WndProcHookManager() /// public void Dispose() { + if (this.dispatchMessageWHook.IsDisposed) + return; + this.dispatchMessageWHook.Dispose(); + + // Ensure that either we're on the main thread, or DispatchMessage is executed at least once. + // The game calls DispatchMessageW only from its main thread, so if we're already on one, + // this line does nothing; if not, it will require a cycle of GetMessage ... DispatchMessageW, + // which at the point of returning from DispatchMessageW(=point of returning from SendMessageW), + // the hook would be guaranteed to be fully disabled and detour delegate would be safe to be released. + SendMessageW(this.mainWindowHwnd, WM.WM_NULL, 0, 0); + + // Now this.wndProcOverrides cannot be touched from other thread. foreach (var v in this.wndProcOverrides.Values) v.InternalRelease(); this.wndProcOverrides.Clear();