使用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
相关产品推荐
相关产品推荐

