You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }
}
问题分析与解决方案

错误原因排查

  1. 窗口样式直接替换错误:
    原代码直接将窗口样式设置为WS_OVERLAPPED,丢失了WS_VISIBLE等核心样式,导致窗口无法正常显示,同时任务栏图标消失(因为窗口状态异常)。正确做法是保留原有必要样式,仅移除边框、标题栏相关属性。

  2. SetWindowPos参数错误:

    • 参数中hwnd为小写,与方法参数hWnd大小写不匹配,属于语法错误;
    • 未添加SWP_SHOWWINDOW标志,窗口样式修改后无法确保正确显示;
    • 使用SM_CXSCREEN获取的是主显示器分辨率,但未考虑任务栏遮挡,导致窗口无法真正全屏覆盖屏幕。
  3. 未处理扩展窗口样式:
    部分窗口的边框由扩展样式(如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);
    }
}

整体方案优化建议

  1. 等待窗口初始化完成:
    游戏启动后不要立即调用全屏,建议通过循环检测IsWindowVisible(hWnd)返回true后再执行操作,避免窗口未完全创建时修改样式导致异常。

  2. 多显示器适配:
    如果需要支持多显示器,不要使用SM_CXSCREEN,而是通过MonitorFromWindow和GetMonitorInfoAPI获取目标窗口所在显示器的真实分辨率,确保全屏适配正确。

  3. 错误处理与日志记录:
    添加SetWindowLong和SetWindowPos的返回值检查,调用失败时记录日志,便于排查问题。

  4. 支持窗口恢复:
    如果需要退出全屏功能,建议在修改样式前保存原有窗口样式、位置和尺寸,以便后续恢复。

内容的提问来源于stack exchange,提问作者Guye Incognito

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 11:57:57