There is no opportunity to install a low-level hook on a separate process. The low-level hook is therefore called the "low-level" because it is called before the input event reaches the target process (more precisely, the stream, since the message queues belong to the threads). Although the SetWindowsHookEx function allows you to pass the fourth parameter of the thread ID to set the hook only on events of this thread, for WH_KEYBOARD_LL this parameter does not work, it seems, precisely because of its particular nature.
However, it is not a secret to which process the message will be sent to: it is the process that owns the current active window. This means that it is quite simple to implement filtering of the hook events of a specific process using the GetWindowThreadProcessId and GetForegroundWindow functions. After reworking the example from here , we get the following code:
using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Diagnostics; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Process[] prs = Process.GetProcessesByName("notepad"); if (prs.Length == 0) throw new ApplicationException("Process not found!"); KeyLogger.SetHook((uint)prs[0].Id); } } class KeyLogger { const int WH_KEYBOARD_LL = 13; const int HC_ACTION = 0; const int WM_KEYDOWN = 0x0100; const uint VK_CAPITAL = 0x14; static uint hookProcessID; static IntPtr hookID; static KeyboardProc Callback = KeyboardHookCallback; public static void SetHook(uint pid) { hookProcessID = pid; hookID = SetWindowsHookEx(WH_KEYBOARD_LL, Callback, IntPtr.Zero, 0); if (hookID == IntPtr.Zero) throw new ApplicationException("Failed to install hook!"); Console.WriteLine("Started listening keyboard events..."); Application.Run(); } static IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) { int vkCode = Marshal.ReadInt32(lParam); uint pid = 0; GetWindowThreadProcessId(GetForegroundWindow(), out pid); if (pid == hookProcessID) { Console.Out.WriteLine("Key: " + ((Keys)vkCode).ToString() + ";"); } } return CallNextHookEx(hookID, nCode, wParam, lParam); } delegate IntPtr KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] static extern IntPtr SetWindowsHookEx(int idHook, KeyboardProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId); [DllImport("user32.dll")] static extern IntPtr GetForegroundWindow(); } }
SetWindowsHookEx
accepts the PID of the process. - D .StarkNULL
to the 3rd parameter. - D .Stark