如何在C# Windows服务中全局拦截并取消BLE鼠标的各类操作?
Hey there! I get that you’ve got a working solution in WinForms but are stuck translating that to a Windows Service—let’s break down why that’s tricky and how to fix it.
First, the core issue: Windows Services run in Session 0 by default, which is isolated from user desktop sessions (Session 1+). The WinForms hooks you’re used to rely on interacting with the user’s desktop message loop, which won’t work in Session 0. So we need a way to capture mouse input in the user’s active session and block BLE-specific events, controlled by your service.
Solution 1: Low-Level Mouse Hook with a User-Session Helper Process
The WH_MOUSE_LL low-level mouse hook is special because it doesn’t require injecting into other processes, but it still needs to run in the user’s session to capture their input. Here’s how to set this up:
Step 1: Create a Helper Executable
First, build a small console app (the helper) that runs the hook. This will be launched by your service in the active user session.
using System; using System.Runtime.InteropServices; public class BLEMouseBlocker { private const int WH_MOUSE_LL = 14; private const int WM_MOUSEMOVE = 0x0200; private const int WM_LBUTTONDOWN = 0x0201; // Add other mouse messages you want to block (right click, scroll, etc.) private static IntPtr _hookId = IntPtr.Zero; private static LowLevelMouseProc _proc = HookCallback; public static void StartBlocking() { _hookId = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(null), 0); if (_hookId == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(); } // Keep the message loop running to listen for mouse events var msg = new MSG(); while (GetMessage(ref msg, IntPtr.Zero, 0, 0) > 0) { TranslateMessage(ref msg); DispatchMessage(ref msg); } UnhookWindowsHookEx(_hookId); } private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && IsBLEMouse(lParam)) { // Block the message by returning non-zero (prevents it from reaching apps) return (IntPtr)1; } return CallNextHookEx(_hookId, nCode, wParam, lParam); } private static bool IsBLEMouse(IntPtr lParam) { // Parse the mouse hook structure to get device context var mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); // Use Raw Input API to check if the device is a BLE mouse return CheckIfBluetoothDevice(mouseHookStruct.dwExtraInfo); } private static bool CheckIfBluetoothDevice(IntPtr deviceExtraInfo) { // Implement logic to verify the device is BLE: // 1. Use GetRawInputDeviceList to enumerate input devices // 2. Use GetRawInputDeviceInfo to get device details // 3. Check if the bus type is BUS_BLUETOOTH (0x05) in RID_DEVICE_INFO // Placeholder return—replace with actual detection logic return true; } // P/Invoke declarations private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetMessage(ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool TranslateMessage(ref MSG lpMsg); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr DispatchMessage(ref MSG lpMsg); private struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam; public IntPtr lParam; public uint time; public POINT pt; } private struct POINT { public int x; public int y; } private struct MSLLHOOKSTRUCT { public POINT pt; public uint mouseData; public uint flags; public uint time; public IntPtr dwExtraInfo; } } // Helper app's entry point class Program { static void Main() { BLEMouseBlocker.StartBlocking(); } }
Step 2: Launch the Helper from Your Windows Service
Your service needs to start the helper executable in the active user session. To do this, use CreateProcessAsUser and get the active session ID.
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.ServiceProcess; public class BLEBlockerService : ServiceBase { private Process _helperProcess; protected override void OnStart(string[] args) { // Get the active user session ID var sessionId = WTSGetActiveConsoleSessionId(); if (sessionId == 0xFFFFFFFF) { // No active user session—handle this case (e.g., wait for a session to start) return; } // Get the user token for the active session if (!WTSQueryUserToken(sessionId, out IntPtr userToken)) { throw new System.ComponentModel.Win32Exception(); } // Configure process startup to target the user's desktop var startupInfo = new STARTUPINFO(); var processInfo = new PROCESS_INFORMATION(); startupInfo.cb = Marshal.SizeOf(startupInfo); startupInfo.lpDesktop = @"winsta0\default"; // Target the user's desktop session // Path to your helper executable var helperPath = @"C:\Your\Path\To\BLEMouseBlockerHelper.exe"; // Launch the helper as the active user if (!CreateProcessAsUser(userToken, helperPath, null, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref startupInfo, out processInfo)) { throw new System.ComponentModel.Win32Exception(); } // Clean up unmanaged handles CloseHandle(userToken); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); _helperProcess = Process.GetProcessById((int)processInfo.dwProcessId); } protected override void OnStop() { // Terminate the helper process when the service stops if (_helperProcess != null && !_helperProcess.HasExited) { _helperProcess.Kill(); } _helperProcess?.Dispose(); } // P/Invoke declarations for session and process management [DllImport("wtsapi32.dll", SetLastError = true)] private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("wtsapi32.dll", SetLastError = true)] private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); private struct STARTUPINFO { public int cb; public string lpReserved; public string lpDesktop; public string lpTitle; public int dwX; public int dwY; public int dwXSize; public int dwYSize; public int dwXCountChars; public int dwYCountChars; public int dwFillAttribute; public int dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } private struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } }
Critical Notes for Success
- Permissions: Your service needs
SeIncreaseQuotaPrivilegeandSeAssignPrimaryTokenPrivilegeto useCreateProcessAsUser. Grant these privileges in your service installer. - BLE Device Detection: To accurately filter only BLE mice, use the Raw Input API to query device details. Look for the bus type
BUS_BLUETOOTH(0x05) in theRID_DEVICE_INFOstructure. - Session Switch Handling: If the user switches sessions, your service should detect the new active session and restart the helper process.
内容的提问来源于stack exchange,提问作者ronfl

