用HOOK实现Win7下假关机

昨天写了《C# 实现假关机》,发现其中的办法对Win7不适应,还是会被强制关掉。今天想到另一种方法,在鼠标即将点击开始菜单关机按钮的时候,把鼠标的消息拦截下来,然后我们的程序收到这个消息,处理一下,比如关闭显示器,然后把消息丢弃掉,这样关机按钮的消息就收不到,自然就不会关机了。这样的好处是,系统收不到关机消息,自然没有强制关闭那个窗口了。

这里要用到钩子HOOK,需要用Form,完整代码如下:

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
 
namespace HookShutdown
{
    public partial class FormMain : Form
    {
        internal enum HookType
        {
            MsgFilter = -1,
            JournalRecord = 0,
            JournalPlayback = 1,
            Keyboard = 2,
            GetMessage = 3,
            CallWndProc = 4,
            CBT = 5,
            SysMsgFilter = 6,
            Mouse = 7,
            Hardware = 8,
            Debug = 9,
            Shell = 10,
            ForegroundIdle = 11,
            CallWndProcRet = 12,
            KeyboardLL = 13,
            MouseLL = 14
        }
        internal delegate IntPtr HookProc(int code, int wparam, IntPtr lparam);
        public struct POINT
        {
            public int x;
            public int y;
        }
 
        public struct MSLLHOOKSTRUCT
        {
            public POINT pt;
            public uint mouseData;
            public uint flags;
            public uint time;
            public int dwExtraInfo;
        }
 
        private IntPtr m_NextHookPtr = IntPtr.Zero;
        private IntPtr m_Handle = IntPtr.Zero;
        private IntPtr _handle = IntPtr.Zero;
        private uint trayWndThreadId = 0;
 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        [DllImport("User32.dll")]
        static extern void UnhookWindowsHookEx(IntPtr handle);
        [DllImport("User32.dll", SetLastError = true)]
        static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
        [DllImport("User32.dll")]
        static extern IntPtr CallNextHookEx(IntPtr handle, int code, int wparam, IntPtr lparam);
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
        [DllImport("user32.dll")]
        static extern IntPtr WindowFromPoint(POINT Point);
        [DllImport("user32.dll", SetLastError = true)]
        static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
        [DllImport("kernel32.dll")]
        static extern uint GetLastError();
 
        [DllImport("user32.dll")]
        static extern bool BlockInput(bool fBlockIt);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
 
        const UInt32 WM_SYSCOMMAND = 0x0112;
        const UInt32 SC_MONITORPOWER = 0xF170;
 
        public FormMain()
        {
            InitializeComponent();
 
            IntPtr hWnd = FindWindow("Shell_TrayWnd", (string)null);
            if (hWnd != IntPtr.Zero)
            {
                uint procID = 0;
                trayWndThreadId = GetWindowThreadProcessId(hWnd, out procID);
                InstallHook();//安装钩子
            }
        }
 
        ~FormMain()
        {
            UnInstallHook();//卸载钩子
        }
 
        private void InstallHook()
        {
            HookProc hookProc = new HookProc(IdentyFormHookProc);
            this.m_NextHookPtr = SetWindowsHookEx(HookType.MouseLL, hookProc, GetModuleHandle(
                        Process.GetCurrentProcess().MainModule.ModuleName
                        ), 0);
            //if(m_NextHookPtr == IntPtr.Zero)
            //    MessageBox.Show(GetLastError().ToString());
        }
        private void UnInstallHook()
        {
            if (this.m_NextHookPtr != IntPtr.Zero)
            {
                UnhookWindowsHookEx(this.m_NextHookPtr);
            }
        }
 
        private IntPtr IdentyFormHookProc(int code, int wparam, IntPtr lparam)
        {
            switch (wparam)
            {
                case (int)MsgType.WM_LBUTTONDOWN:
                case (int)MsgType.WM_LBUTTONUP:
                    MSLLHOOKSTRUCT msll = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lparam, typeof(MSLLHOOKSTRUCT));
                    IntPtr hWnd = WindowFromPoint(msll.pt);
                    if (hWnd != IntPtr.Zero)
                    {
                        uint procID = 0;
                        if (trayWndThreadId == GetWindowThreadProcessId(hWnd, out procID)) // 如果窗口线程是开始菜单所在的窗口线程
                        {
                            StringBuilder wndText = new StringBuilder(256);
                            GetWindowText(hWnd, wndText, 256);
                            if (wndText.ToString() == "关机") // 如果鼠标点击的是关机按钮
                            {
                                BlockInput(true);
                                SendMessage(this.Handle, WM_SYSCOMMAND, (IntPtr)SC_MONITORPOWER, (IntPtr)2); // 关闭显示器
                                return (IntPtr)1;
                            }
                        }
                    }
                    break;
            }
            return CallNextHookEx(this.m_NextHookPtr, code, wparam, lparam);
        }
    }
}

注意:1.把开始菜单的电源按钮默认为 关机
2.要使得BlockInput这个函数有效,也就是在关闭显示器后中断鼠标键盘输入,程序要运行在管理员权限下
3.要恢复原有的关机,退出程序即可。

用HOOK实现Win7下假关机