Unity(.NET2.0)调用WinAPI设置外部窗口全屏无边框异常排查
问题描述
开发基于Unity的街机游戏启动器,已通过WinAPI实现游戏启停等功能。针对部分无法自行设置全屏的游戏,尝试通过程序调用WinAPI实现全屏,但遇到以下问题:
- 单独调用
SetWindowPos时,窗口仅放大但未完全全屏; - 按示例先调用
SetWindowLong再调用SetWindowPos后,窗口出现异常(变为带白块的大黑方块,任务栏中游戏图标消失)。
注意:Unity程序使用.NET2.0兼容级别,无法使用Form类,需排查SetWindowLong调用错误原因并优化整体方案。
原始代码示例
using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; public static class SetWindowLongform { [DllImport("user32.dll")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] public static extern int GetSystemMetrics(int which); [DllImport("user32.dll")] public static extern void SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,int X, int Y, int width, int height, uint flags); [Flags] enum WindowStyles : uint { WS_OVERLAPPED = 0x00000000, WS_POPUP = 0x80000000, WS_CHILD = 0x40000000, WS_MINIMIZE = 0x20000000, WS_VISIBLE = 0x10000000, WS_DISABLED = 0x08000000, WS_CLIPSIBLINGS = 0x04000000, WS_CLIPCHILDREN = 0x02000000, WS_MAXIMIZE = 0x01000000, WS_BORDER = 0x00800000, WS_DLGFRAME = 0x00400000, WS_VSCROLL = 0x00200000, WS_HSCROLL = 0x00100000, WS_SYSMENU = 0x00080000, WS_THICKFRAME = 0x00040000, WS_GROUP = 0x00020000, WS_TABSTOP = 0x00010000, WS_MINIMIZEBOX = 0x00020000, WS_MAXIMIZEBOX = 0x00010000, WS_CAPTION = WS_BORDER | WS_DLGFRAME, WS_TILED = WS_OVERLAPPED, WS_ICONIC = WS_MINIMIZE, WS_SIZEBOX = WS_THICKFRAME, WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW, WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU, WS_CHILDWINDOW = WS_CHILD, } enum WindowLongFlags : int { GWL_EXSTYLE = -20, GWLP_HINSTANCE = -6, GWLP_HWNDPARENT = -8, GWL_ID = -12, GWL_STYLE = -16, GWL_USERDATA = -21, GWL_WNDPROC = -4, DWLP_USER = 0x8, DWLP_MSGRESULT = 0x0, DWLP_DLGPROC = 0x4 } private const int SM_CXSCREEN = 0; private const int SM_CYSCREEN = 1; private static IntPtr HWND_TOP = IntPtr.Zero; private const int SWP_SHOWWINDOW = 64; // 0x0040 private const uint SWP_FRAMECHANGED = 0x0020; public static void SetWindowFullscreenAndBorderless(IntPtr hWnd) { SetBorderless(hWnd); SetWinFullScreen(hWnd); } static int ScreenX { get { return GetSystemMetrics(SM_CXSCREEN); } } static int ScreenY { get { return GetSystemMetrics(SM_CYSCREEN); } } static void SetBorderless(IntPtr hWnd) { SetWindowLong(hWnd,(int)WindowLongFlags.GWL_STYLE,(uint)WindowStyles.WS_OVERLAPPED); } static void SetWinFullScreen(IntPtr hWnd) { SetWindowPos(hwnd, HWND_TOP, 0, 0, ScreenX, ScreenY, SWP_FRAMECHANGED); } }
问题分析与解决方案
错误原因排查
窗口样式直接替换错误:
原代码直接将窗口样式设置为WS_OVERLAPPED,丢失了WS_VISIBLE等核心样式,导致窗口无法正常显示,同时任务栏图标消失(因为窗口状态异常)。正确做法是保留原有必要样式,仅移除边框、标题栏相关属性。SetWindowPos参数错误:- 参数中
hwnd为小写,与方法参数hWnd大小写不匹配,属于语法错误; - 未添加
SWP_SHOWWINDOW标志,窗口样式修改后无法确保正确显示; - 使用
SM_CXSCREEN获取的是主显示器分辨率,但未考虑任务栏遮挡,导致窗口无法真正全屏覆盖屏幕。
- 参数中
未处理扩展窗口样式:
部分窗口的边框由扩展样式(如WS_EX_WINDOWEDGE、WS_EX_CLIENTEDGE)控制,仅修改主样式无法完全移除边框。
修正后的代码
using System; using System.Runtime.InteropServices; using UnityEngine; public static class WindowFullscreenHelper { [DllImport("user32.dll", EntryPoint = "SetWindowLong")] static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong); [DllImport("user32.dll", EntryPoint = "GetWindowLong")] static extern uint GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] public static extern int GetSystemMetrics(int which); [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int width, int height, uint flags); [Flags] private enum WindowStyles : uint { WS_VISIBLE = 0x10000000, WS_OVERLAPPED = 0x00000000, WS_CAPTION = 0x00C00000, WS_THICKFRAME = 0x00040000, WS_MINIMIZEBOX = 0x00020000, WS_MAXIMIZEBOX = 0x00010000, WS_SYSMENU = 0x00080000 } [Flags] private enum WindowExStyles : uint { WS_EX_WINDOWEDGE = 0x00000100, WS_EX_CLIENTEDGE = 0x00000200 } private enum WindowLongFlags : int { GWL_STYLE = -16, GWL_EXSTYLE = -20 } private const int SM_CXSCREEN = 0; private const int SM_CYSCREEN = 1; private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); private const uint SWP_FRAMECHANGED = 0x0020; private const uint SWP_SHOWWINDOW = 0x0040; private const uint SWP_NOZORDER = 0x0004; public static void SetWindowFullscreenAndBorderless(IntPtr hWnd) { if (hWnd == IntPtr.Zero) return; // 移除边框、标题栏相关的主样式,保留WS_VISIBLE uint currentStyle = GetWindowLong(hWnd, (int)WindowLongFlags.GWL_STYLE); uint newStyle = currentStyle & ~(uint)(WindowStyles.WS_CAPTION | WindowStyles.WS_THICKFRAME | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_SYSMENU); newStyle |= (uint)WindowStyles.WS_VISIBLE; SetWindowLong(hWnd, (int)WindowLongFlags.GWL_STYLE, newStyle); // 移除扩展样式中的边框属性 uint currentExStyle = GetWindowLong(hWnd, (int)WindowLongFlags.GWL_EXSTYLE); uint newExStyle = currentExStyle & ~(uint)(WindowExStyles.WS_EX_WINDOWEDGE | WindowExStyles.WS_EX_CLIENTEDGE); SetWindowLong(hWnd, (int)WindowLongFlags.GWL_EXSTYLE, newExStyle); // 设置全屏位置和尺寸,覆盖任务栏 SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOZORDER); } }
整体方案优化建议
等待窗口初始化完成:
游戏启动后不要立即调用全屏,建议通过循环检测IsWindowVisible(hWnd)返回true后再执行操作,避免窗口未完全创建时修改样式导致异常。多显示器适配:
如果需要支持多显示器,不要使用SM_CXSCREEN,而是通过MonitorFromWindow和GetMonitorInfoAPI获取目标窗口所在显示器的真实分辨率,确保全屏适配正确。错误处理与日志记录:
添加SetWindowLong和SetWindowPos的返回值检查,调用失败时记录日志,便于排查问题。支持窗口恢复:
如果需要退出全屏功能,建议在修改样式前保存原有窗口样式、位置和尺寸,以便后续恢复。
内容的提问来源于stack exchange,提问作者Guye Incognito
相关产品推荐
相关产品推荐

