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

使用Win32 C#禁用带Ribbon控件应用关闭按钮的问题

解决带Ribbon控件应用的关闭按钮禁用问题

普通应用通过修改系统菜单SC_CLOSE项状态的方式,在带Ribbon控件的应用中无效,核心原因是这类应用大多会自定义系统菜单的响应逻辑,不会依赖系统菜单的状态判断是否允许关闭。以下是两种可行的解决方案:


方案一:通过UI自动化定位并禁用关闭按钮

无需进程注入,利用Windows UI自动化框架直接定位窗口的关闭按钮控件,通过发送消息禁用它。

代码示例

首先引用System.Windows.Automation程序集,然后实现如下逻辑:

using System;
using System.Windows.Automation;
using System.Runtime.InteropServices;

public static class WindowHelper
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    private const int WM_ENABLE = 0x000A;

    public static void DisableCloseButton(IntPtr hwnd)
    {
        var windowElement = AutomationElement.FromHandle(hwnd);
        if (windowElement == null) return;

        // 查找关闭按钮的条件(可根据目标应用实际属性调整)
        var closeBtnCondition = new AndCondition(
            new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
            new PropertyCondition(AutomationElement.NameProperty, "关闭"),
            new PropertyCondition(AutomationElement.ClassNameProperty, "Button")
        );

        AutomationElement closeBtn = windowElement.FindFirst(TreeScope.Descendants, closeBtnCondition);
        
        // 尝试从标题栏容器中查找(部分Ribbon应用的关闭按钮嵌套在标题栏内)
        if (closeBtn == null)
        {
            var titleBar = windowElement.FindFirst(TreeScope.Descendants, 
                new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TitleBar));
            closeBtn = titleBar?.FindFirst(TreeScope.Descendants, closeBtnCondition);
        }

        if (closeBtn != null)
        {
            // 获取按钮的原生句柄并发送禁用消息
            var btnHwnd = new IntPtr((int)closeBtn.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty));
            SendMessage(btnHwnd, WM_ENABLE, IntPtr.Zero, IntPtr.Zero);
        }
    }
}

注意事项

  • 可使用Windows SDK中的Inspect工具(替代UISpy)查看目标应用关闭按钮的具体属性,调整查找条件(比如Name可能为英文"Close",ClassName可能不同)。
  • 部分自定义Ribbon控件可能无法被UI自动化识别,此时需改用方案二。

方案二:进程注入+窗口子类化拦截关闭命令

通过注入DLL到目标进程,子类化窗口并拦截WM_SYSCOMMAND消息,直接忽略SC_CLOSE命令。

步骤1:编写注入用C++ DLL

#include <windows.h>

WNDPROC g_oldWndProc = nullptr;
HWND g_targetHwnd = nullptr;

LRESULT CALLBACK NewWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_SYSCOMMAND && wParam == SC_CLOSE)
    {
        // 拦截关闭命令,不传递给原窗口过程
        return 0;
    }
    return CallWindowProc(g_oldWndProc, hwnd, msg, wParam, lParam);
}

extern "C" __declspec(dllexport) void SetCloseHook(HWND hwnd)
{
    g_targetHwnd = hwnd;
    g_oldWndProc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_DETACH:
        // 卸载时恢复原窗口过程
        if (g_oldWndProc != nullptr && g_targetHwnd != nullptr)
        {
            SetWindowLongPtr(g_targetHwnd, GWLP_WNDPROC, (LONG_PTR)g_oldWndProc);
        }
        break;
    }
    return TRUE;
}

步骤2:C#实现DLL注入逻辑

using System;
using System.Runtime.InteropServices;
using System.Text;

public static class InjectHelper
{
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out uint lpNumberOfBytesWritten);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    private const uint PROCESS_ALL_ACCESS = 0x1F0FFF;
    private const uint MEM_COMMIT = 0x1000;
    private const uint MEM_RESERVE = 0x2000;
    private const uint PAGE_READWRITE = 0x04;

    public static bool InjectCloseHook(int processId, string dllPath, IntPtr targetHwnd)
    {
        var hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
        if (hProcess == IntPtr.Zero) return false;

        try
        {
            // 加载DLL到目标进程
            var dllPathBytes = Encoding.Auto.GetBytes(dllPath + "\0");
            var remoteMem = VirtualAllocEx(hProcess, IntPtr.Zero, (uint)dllPathBytes.Length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
            if (remoteMem == IntPtr.Zero) return false;

            if (!WriteProcessMemory(hProcess, remoteMem, dllPathBytes, (uint)dllPathBytes.Length, out _)) return false;

            var loadLibAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
            if (loadLibAddr == IntPtr.Zero) return false;

            if (CreateRemoteThread(hProcess, IntPtr.Zero, 0, loadLibAddr, remoteMem, 0, out _) == IntPtr.Zero) return false;

            // 调用DLL中的SetCloseHook函数设置钩子
            var dllModule = GetModuleHandle(dllPath);
            var setHookAddr = GetProcAddress(dllModule, "SetCloseHook");
            if (setHookAddr == IntPtr.Zero) return false;

            return CreateRemoteThread(hProcess, IntPtr.Zero, 0, setHookAddr, targetHwnd, 0, out _) != IntPtr.Zero;
        }
        finally
        {
            CloseHandle(hProcess);
        }
    }
}

注意事项

  • 需要管理员权限执行注入操作,且需保证注入程序与目标进程的位数一致(32位程序无法注入64位进程,反之亦然)。
  • 部分杀毒软件会拦截进程注入行为,需临时放行或添加信任。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 06:35:20