I was looking for a possibility to be notified in a .NET windows application when any window is activated in the OS (Windows XP 32-bit). On CodeProject I have found a solution by using global system hooks.
http://www.codeproject.com/Articles/18638/Using-Window-Messages-to-Implement-Global-System-H .
Here is a short summary of this procedure:
In an unmanaged assembly (written in C++) a method is implemented which installs the WH_CBT hook.
bool InitializeCbtHook(int threadID, HWND destination)
{
if (g_appInstance == NULL)
{
return false;
}
if (GetProp(GetDesktopWindow(), " HOOK_HWND_CBT") != NULL)
{
SendNotifyMessage((HWND)GetProp(GetDesktopWindow(), "HOOK_HWND_CBT"),
RegisterWindowMessage("HOOK_CBT_REPLACED"), 0, 0);
}
SetProp(GetDesktopWindow(), " HOOK_HWND_CBT", destination);
hookCbt = SetWindowsHookEx(WH_CBT, (HOOKPROC)CbtHookCallback, g_appInstance, threadID);
return hookCbt != NULL;
}
In the callback method (filter function) depending on the hook type windows messages are sent to a destination window.
static LRESULT CALLBACK CbtHookCallback(int code, WPARAM wparam, LPARAM lparam)
{
if (code >= 0)
{
UINT msg = 0;
if (code == HCBT_ACTIVATE)
msg = RegisterWindowMessage("HOOK_HCBT_ACTIVATE");
else if (code == HCBT_CREATEWND)
msg = RegisterWindowMessage("HOOK_HCBT_CREATEWND");
else if (code == HCBT_DESTROYWND)
msg = RegisterWindowMessage("HOOK_HCBT_DESTROYWND");
else if (code == HCBT_MINMAX)
msg = RegisterWindowMessage("HOOK_HCBT_MINMAX");
else if (code == HCBT_MOVESIZE)
msg = RegisterWindowMessage("HOOK_HCBT_MOVESIZE");
else if (code == HCBT_SETFOCUS)
msg = RegisterWindowMessage("HOOK_HCBT_SETFOCUS");
else if (code == HCBT_SYSCOMMAND)
msg = RegisterWindowMessage("HOOK_HCBT_SYSCOMMAND");
HWND dstWnd = (HWND)GetProp(GetDesktopWindow(), HOOK_HWND_CBT");
if (msg != 0)
SendNotifyMessage(dstWnd, msg, wparam, lparam);
}
return CallNextHookEx(hookCbt, code, wparam, lparam);
}
To use this assembly in a .NET Windows Application the following method has to be imported:
[DllImport("GlobalCbtHook.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool InitializeCbtHook (int threadID, IntPtr DestWindow);
[DllImport("GlobalCbtHook.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void UninitializeCbtHook(int hookType);
After calling InitializeCbtHook the messages received from GlobalCbtHook.dll can be processed in:
protected override void WndProc(ref Message msg)
The messages have to be registered in both the assembly and the application by calling
RegisterWindowMessage.
[DllImport("user32.dll")]
private static extern int RegisterWindowMessage(string lpString);
This implementation works fine. But in most cases when I activate Microsoft Office Outlook
my .NET Application receives the activate-event after I minimize Outlook or activate an other window. At first I thought that my .NET wrapper is the cause of the problem. But after I used the sources from the above link I could recognized the same behaviour.
My actually workaround is to use WH_SHELL hook. I know that one difference between WH_CBT and WH_SHELL hook is when using WH_CBT hook it is possible to interrupt the filter function chain by not calling the CallNextHookEx method. Could this play a role in my problem?
Please provide help.
obviously the hooking does not work in cases of outlook - what about other microsoft products (word, power point ...)??
but, why hooking? this little class will work even if outlook is activated
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsMonitor
{
public class ActiveWindowChangedEventArgs : EventArgs
{
public IntPtr CurrentActiveWindow { get; private set; }
public IntPtr LastActiveWindow { get; private set; }
public ActiveWindowChangedEventArgs(IntPtr lastActiveWindow, IntPtr currentActiveWindow)
{
this.LastActiveWindow = lastActiveWindow;
this.CurrentActiveWindow = currentActiveWindow;
}
}
public delegate void ActiveWindowChangedEventHandler(object sender, ActiveWindowChangedEventArgs e);
public class ActiveWindowMonitor
{
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
private Timer monitorTimer;
public IntPtr ActiveWindow { get; private set; }
public event ActiveWindowChangedEventHandler ActiveWindowChanged;
public ActiveWindowMonitor()
{
this.monitorTimer = new Timer();
this.monitorTimer.Tick += new EventHandler(monitorTimer_Tick);
this.monitorTimer.Interval = 10;
this.monitorTimer.Start();
}
private void monitorTimer_Tick(object sender, EventArgs e)
{
CheckActiveWindow();
}
private void CheckActiveWindow()
{
IntPtr currentActiveWindow = GetForegroundWindow();
if (this.ActiveWindow != currentActiveWindow)
{
IntPtr lastActiveWindow = this.ActiveWindow;
this.ActiveWindow = currentActiveWindow;
OnActiveWindowChanged(lastActiveWindow, this.ActiveWindow);
}
}
protected virtual void OnActiveWindowChanged(IntPtr lastActiveWindow, IntPtr currentActiveWindow)
{
ActiveWindowChangedEventHandler temp = ActiveWindowChanged;
if (temp != null)
{
temp.Invoke(this, new ActiveWindowChangedEventArgs(lastActiveWindow, currentActiveWindow));
}
}
}
}
usage
public void InitActiveWindowMonitor()
{
WindowsMonitor.ActiveWindowMonitor monitor = new WindowsMonitor.ActiveWindowMonitor();
monitor.ActiveWindowChanged += new WindowsMonitor.ActiveWindowChangedEventHandler(monitor_ActiveWindowChanged);
}
private void monitor_ActiveWindowChanged(object sender, WindowsMonitor.ActiveWindowChangedEventArgs e)
{
//ouh a window got activated
}
Related
I have 2 application and contractor
the contractor
namespace EventAppShellContractor
{
[ComVisible(true)]
public interface IEventAppShell
{
bool IsRunning { get; }
void Run();
void Stop();
event EventHandler<byte[]> Scanned;
}
}
Applition 1 - the shell application
This application only communication with some device ro get results
[ComVisible(true)]
public partial class Form1 : Form, IEventAppShell
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (int)AppShellEventMessage.WM_GET_HANDLER)
{
var result = (IntPtr)Marshal.GetComInterfaceForObject(this, typeof(IEventAppShell));
m.Result = result;
return;
}
base.WndProc(ref m);
}
public event EventHandler<byte[]> Scanned;
public bool IsRunning => true;
public void Run()
{
try
{
var device = new SomeNewDeviceComm();
device.Init();
device.OnScanned += async (s, data) =>
{
Scanned?.Invoke(this, data);
};
device.Start();
}
catch (Exception ex)
{
throw;
}
}
public void Stop()
{
//Close device
device?.Close();
Application.Exit();
}
}
in the paretn application when I need to run the device I'm calling this method:
public class EventAppShellRunner {
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private IEventAppShell m_Device;
private Process m_Process
public void StartDevice(){
m_Process = new Process();
m_Process.EnableRaisingEvents= true;
m_Process.StartInfo.FileName = [EXE file of 1st applcation];
m_Process.Start();
m_Process.WaitForInputIdle();
var mainFormHandle = FindWindow(null, "Form1"); //Receive Uint not zero
IntPtr result = SendMessage(mainFormHandle, (uint)AppShellEventMessage.WM_GET_HANDLER, IntPtr.Zero, IntPtr.Zero);
//Result is Zero for some reason
m_Device= (IEventAppShell)Marshal.GetObjectForNativeVariant(result);
m_Device.Scanned += SomeScanResultHandler...
//in the 'm_Device` I get exption because result is zero
m_Device.Run();
}
public void Stop(){
m_Device?.Stop();
}
}
Is there a way to communicate directly to the 1st application
I tried named pipe and it didn't work well
If the goal is to get two applications to communicate, my approach would be to setup some type of server or message queue to send serialized messages between the applications.
While this might sound complicated, most of the complicated parts are handled by libraries. I managed to setup a simple message transfer using MQTT in about an hour by following this guide. The important part here is that you can send messages, and use standard serialization libraries, like json or protobuf, to generate messages from objects. Many Lower level techniques only provide an "stream of bytes", so you need to handle things like message framing yourself.
Note that I'm not advocating for MQTT specifically, it is just one library I have used. There are many to chose from, rabbitMQ, NetMQ, MQTT, http, gRPC, and many more.
Simple and universal - use TCP socket (localhost).
I am trying to subscribe to windows message events/messaging system from c# net core through unamanged c++ dll using pinvoke.
Issues I am having.
Getting the handle for my process or creating an empty window (does .net even support that).
var hwnd = Process.GetCurrentProcess().Handle;
var hwnd1 = Process.GetCurrentProcess().Handle.ToPointer();
Is either of that is valid to get the handle.
How do I marshal that handle to c++ HWND type. IntPtr seems like obvious choice, but it does not work.
Here is what I am using to subscribe to events
public class MsgSubscribe : IDisposable
{
private readonly Importer _importer;
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate Status DMsgSubscribe(uint msgType, uint msgQ, int hwnd, uint msgId);
private static DMsgSubscribe _dMsgSubscribe;
private IntPtr PMsgSubscribe { get; set; }
public bool Available { get; set; }
public MsgSubscribe(Importer importer)
{
_importer = importer;
if (_importer.hCurModule != IntPtr.Zero)
{
PMsgSubscribe = Importer.GetProcAddress(_importer.hCurModule, "MsgSubscribe");
Available = PUlyMsgSubscribe != IntPtr.Zero;
}
}
public Status MsgSubscribe(uint msgType, uint msgQ, int hwnd, uint msgId)
{
Status result = Status.FunctionNotAvailable;
if (Available)
{
_dMsgSubscribe = (DMsgSubscribe)Marshal.GetDelegateForFunctionPointer(PMsgSubscribe, typeof(DMsgSubscribe));
result = _dMsgSubscribe(msgType, msgQ, hwnd, msgId);
}
return result;
}
public void Dispose()
{
}
}
I've tried IntPtr and int for HWND marshalling, neither works.
Also I am not sure how I am supposed to catch window message based events, there is very little online if anything.
Any help appreciated.
Eventually found a way to make this work, it involves creating a window through c++ pinvoke.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Constants.Constants;
using Constants.Enums;
using Models.WindowsApiModels;
namespace Dependencies.MessagingHandling
{
public class CustomWindow : IDisposable
{
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private const int ErrorClassAlreadyExists = 1410;
public IntPtr Handle { get; private set; }
public List<YourType> Messages { get; set; }
public void Dispose()
{
if (Handle != IntPtr.Zero)
{
Importer.DestroyWindow(Handle);
Handle = IntPtr.Zero;
}
}
public CustomWindow()
{
Messages = new List<YourType>();
var className = "Prototype Messaging Class";
WndProc mWndProcDelegate = CustomWndProc;
// Create WNDCLASS
WNDCLASS windClass = new WNDCLASS
{
lpszClassName = className,
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(mWndProcDelegate)
};
UInt16 classAtom = Importer.RegisterClassW(ref windClass);
int lastError = Marshal.GetLastWin32Error();
if (classAtom == 0 && lastError != ErrorClassAlreadyExists)
{
throw new Exception("Could not register window class");
}
// Create window
Handle = Importer.CreateWindowExW(
0,
className,
"Prototype Messaging Window",
0, 0, 0, 0, 0,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
}
private IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
//handle your message here
return Importer.DefWindowProc(hWnd, msg, wParam, lParam);
}
public Task GetMessage()
{
IntPtr handle = Handle;
int bRet;
while ((bRet = Importer.GetMessage(out var msg, Handle, 0, 0)) != 0)
{
switch (bRet)
{
case -1:
Console.WriteLine("Error");
CancellationToken token = new CancellationToken(true);
return Task.FromCanceled(token);
default:
Importer.TranslateMessage(ref msg);
Importer.DispatchMessage(ref msg);
break;
}
}
return Task.FromResult(true);
}
}
}
Run this in your main Method in the main thread and your menu/gui in secondary thread
Task.Run(ShowMenu);
_customWindow.GetMessage();
Importer is custom class containing c++ marshalled functions to create/handle window, look the up by the name as they are the same. All CAPS class/struct are windows/c++ api structs, those can be found on official msdn.
In general using an IntPtr is corrent.
Handle()
Returns such a IntPtr.
It looks like that you're trying to use your process HWND to get window messages which will not work because you have to use a window HWND to get the messages associated to that HWND.
I am working on an WPF application to monitor my activities on my computer. I use Process.GetProcesses() and some filtering to get the processes I am interested in (example:Calculator) then I record their StartTime. I am also using WIN32/USER32 API method GetForegroundWindow() to get the window the user is using.
The problem is that when the windows are Windows/UWP applications they are always hosted by the process ApplicationFrameHost. So the GetForegroundWindow() method returns that window with a title (example:Calculator), but not the real process being hosted.
What I need is either another way to get the foreground window that includes the real process being hosted, or some way to connect the window to process.
Anyone that knows how to accomplish this? All help would be really appreciated.
I eventually found a way to do this, so I am going answer my own question so maybe someone in the future with the same problem could find it useful.
This is the class with the WinApiFunctions:
public class WinAPIFunctions
{
//Used to get Handle for Foreground Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetForegroundWindow();
//Used to get ID of any Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
public delegate bool WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc callback, IntPtr lParam);
public static int GetWindowProcessId(IntPtr hwnd)
{
int pid;
GetWindowThreadProcessId(hwnd, out pid);
return pid;
}
public static IntPtr GetforegroundWindow()
{
return GetForegroundWindow();
}
}
And this is the class I used to test if it would work. I used it in a simple console program that just writes out the name of the process that has current focus:
class FindHostedProcess
{
public Timer MyTimer { get; set; }
private Process _realProcess;
public FindHostedProcess()
{
MyTimer = new Timer(TimerCallback, null, 0, 1000);
Console.ReadKey();
}
private void TimerCallback(object state)
{
var foregroundProcess = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(WinAPIFunctions.GetforegroundWindow()));
if (foregroundProcess.ProcessName == "ApplicationFrameHost")
{
foregroundProcess = GetRealProcess(foregroundProcess);
}
Console.WriteLine(foregroundProcess.ProcessName);
}
private Process GetRealProcess(Process foregroundProcess)
{
WinAPIFunctions.EnumChildWindows(foregroundProcess.MainWindowHandle, ChildWindowCallback, IntPtr.Zero);
return _realProcess;
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = Process.GetProcessById(WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ProcessName != "ApplicationFrameHost")
{
_realProcess = process;
}
return true;
}
}
Chris, there is the alternative way which I have discovered trying to apply your solution to a related problem. While trying to analyse how the ApplicationFrameHost.exe-related stuff worked, I have stumbled upon the documented way of getting the true foreground window / thread and its process by passing 0 instead of the actual thread ID to GetGUIThreadInfo.
It might not fully work for the edge-case scenarios of your problem, but I felt that this might be a useful contribution for people of the future who might face the same problems ;-)
Here is the example of how this can be applied (pseudo C++'ish code):
GUITHREADINFO gti = { sizeof(GUITHREADINFO) };
GetGUIThreadInfo(0, >i); // <- note the `0`
DWORD processId = 0;
GetWindowThreadProcessId(gti.hwndFocus, &processId);
const auto procName = Util::GetProcessName(processId);
It solved my problem (obtaining the actual keyboard layout + finding the real foreground window) for all more-or-less common apps I have tested it against.
Basically, the answer you provided will also fail if the active window is in full screen mode,
for example if you have Skype app opened along with Microsoft Remote Desktop, which is the
active one and on full screen mode, EnumChildWindows would return SkypeApp not RDPClient.
and to get this fixed, you should follow the workaround suggested by Ivanmoskalev,
check this out:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
public static IntPtr getThreadWindowHandle(uint dwThreadId)
{
IntPtr hWnd;
// Get Window Handle and title from Thread
var guiThreadInfo = new GUITHREADINFO();
guiThreadInfo.cbSize = Marshal.SizeOf(guiThreadInfo);
GetGUIThreadInfo(dwThreadId, ref guiThreadInfo);
hWnd = guiThreadInfo.hwndFocus;
//some times while changing the focus between different windows, it returns Zero so we would return the Active window in that case
if (hWnd == IntPtr.Zero)
{
hWnd = guiThreadInfo.hwndActive;
}
return hWnd;
}
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr windowHandle, out int processId);
static void Main(string[] args)
{
var current = getThreadWindowHandle(0);
int processId = 0;
GetWindowThreadProcessId(current, out processId);
var foregroundProcess = GetActiveProcess(processId);
}
private static Process GetActiveProcess(int activeWindowProcessId)
{
Process foregroundProcess = null;
try
{
foregroundProcess = Process.GetProcessById(activeWindowProcessId);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
if (string.IsNullOrWhiteSpace(GetProcessNameSafe(foregroundProcess)))
{
var msg = "Process name is empty.";
Console.WriteLine(msg);
}
return foregroundProcess;
}
About the same code from Chris Johnsson except that I'm not using Process because it doesn't work very well on the UWP app.
Code for start:
new Timer(TimerCallback, null, 0, 1000);
void TimerCallback(object state)
{
var process = new ProcessUtils.FindHostedProcess().Process;
string name = string.Empty;
if (process.IsPackaged)
{
var apps = process.GetAppDiagnosticInfos();
if (apps.Count > 0)
name = apps.First().AppInfo.DisplayInfo.DisplayName;
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
}
else
name = System.IO.Path.GetFileNameWithoutExtension(process.ExecutableFileName);
Debug.WriteLine(name);
}
And the FindHostedProcess class where I use ProcessDiagnosticInfo instead of Process
public class FindHostedProcess
{
public ProcessDiagnosticInfo Process { get; private set; }
public FindHostedProcess()
{
var foregroundProcessID = WinAPIFunctions.GetforegroundWindow();
Process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(foregroundProcessID));
// Get real process
if (Process.ExecutableFileName == "ApplicationFrameHost.exe")
WinAPIFunctions.EnumChildWindows(foregroundProcessID, ChildWindowCallback, IntPtr.Zero);
}
private bool ChildWindowCallback(IntPtr hwnd, IntPtr lparam)
{
var process = ProcessDiagnosticInfo.TryGetForProcessId((uint)WinAPIFunctions.GetWindowProcessId(hwnd));
if (process.ExecutableFileName != "ApplicationFrameHost.exe")
Process = process;
return true;
}
}
Finally reuse Chris Johnson's WinAPIFunctions class.
I want to leverage machine learning to model a user's intent and potentially automate commonly performed tasks. To do this I would like to have access to a fire-hose of information about user actions and the machine state. To this end, it is my current thinking that getting access to the stream of windows messages is probably the way forward.
I would like to have as much information as is possible, filtering the information to that which is pertinent I would like to leave to the machine learning tool.
How would this be accomplished? (Preferably in C#).
Please assume that I know how to manage and use this large influx of data.
Any help would be gratefully appreciated.
You can use SetWindowsHookEx to set low level hooks to catch (specific) windows messages.
Specifically these hook-ids might be interesting for monitoring:
WH_CALLWNDPROC (4) Installs a hook procedure that monitors messages
before the system sends them to the destination window procedure. For
more information, see the CallWndProc hook procedure.
WH_CALLWNDPROCRET(12) Installs a hook procedure that monitors
messages after they have been processed by the destination window
procedure. For more information, see the CallWndRetProc hook
procedure.
It's been a while since I've implemented it, but as an example I've posted the base class I use to hook specific messages. (For example, I've used it in a global mousewheel trapper, that makes sure my winforms apps behave the same as internet explorer: scroll the control underneath the cursor, instead of the active control).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Subro.Win32;
namespace Subro
{
/// <summary>
/// Base class to relatively safely register global windows hooks
/// </summary>
public abstract class GlobalHookTrapper : FinalizerBase
{
[DllImport("user32", EntryPoint = "SetWindowsHookExA")]
static extern IntPtr SetWindowsHookEx(int idHook, Delegate lpfn, IntPtr hmod, IntPtr dwThreadId);
[DllImport("user32", EntryPoint = "UnhookWindowsHookEx")]
private static extern int UnhookWindowsHookEx(IntPtr hHook);
[DllImport("user32", EntryPoint = "CallNextHookEx")]
static extern int CallNextHook(IntPtr hHook, int ncode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThreadId();
IntPtr hook;
public readonly int HookId;
public readonly GlobalHookTypes HookType;
public GlobalHookTrapper(GlobalHookTypes Type):this(Type,false)
{
}
public GlobalHookTrapper(GlobalHookTypes Type, bool OnThread)
{
this.HookType = Type;
this.HookId = (int)Type;
del = ProcessMessage;
if (OnThread)
hook = SetWindowsHookEx(HookId, del, IntPtr.Zero, GetCurrentThreadId());
else
{
var hmod = IntPtr.Zero; // Marshal.GetHINSTANCE(GetType().Module);
hook = SetWindowsHookEx(HookId, del, hmod, IntPtr.Zero);
}
if (hook == IntPtr.Zero)
{
int err = Marshal.GetLastWin32Error();
if (err != 0)
OnHookFailed(err);
}
}
protected virtual void OnHookFailed(int Error)
{
throw Win32Functions.TranslateError(Error);
}
private const int HC_ACTION = 0;
[MarshalAs(UnmanagedType.FunctionPtr)]
private MessageDelegate del;
private delegate int MessageDelegate(int code, IntPtr wparam, IntPtr lparam);
private int ProcessMessage(int hookcode, IntPtr wparam, IntPtr lparam)
{
if (HC_ACTION == hookcode)
{
try
{
if (Handle(wparam, lparam)) return 1;
}
catch { }
}
return CallNextHook(hook, hookcode, wparam, lparam);
}
protected abstract bool Handle(IntPtr wparam, IntPtr lparam);
protected override sealed void OnDispose()
{
UnhookWindowsHookEx(hook);
AfterDispose();
}
protected virtual void AfterDispose()
{
}
}
public enum GlobalHookTypes
{
BeforeWindow = 4, //WH_CALLWNDPROC
AfterWindow = 12, //WH_CALLWNDPROCRET
KeyBoard = 2, //WH_KEYBOARD
KeyBoard_Global = 13, //WH_KEYBOARD_LL
Mouse = 7, //WH_MOUSE
Mouse_Global = 14, //WH_MOUSE_LL
JournalRecord = 0, //WH_JOURNALRECORD
JournalPlayback = 1, //WH_JOURNALPLAYBACK
ForeGroundIdle = 11, //WH_FOREGROUNDIDLE
SystemMessages = 6, //WH_SYSMSGFILTER
MessageQueue = 3, //WH_GETMESSAGE
ComputerBasedTraining = 5, //WH_CBT
Hardware = 8, //WH_HARDWARE
Debug = 9, //WH_DEBUG
Shell = 10, //WH_SHELL
}
public abstract class FinalizerBase : IDisposable
{
protected readonly AppDomain domain;
public FinalizerBase()
{
System.Windows.Forms.Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
domain = AppDomain.CurrentDomain;
domain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit);
domain.DomainUnload += new EventHandler(domain_DomainUnload);
}
private bool disposed;
public bool IsDisposed{get{return disposed;}}
public void Dispose()
{
if (!disposed)
{
GC.SuppressFinalize(this);
if (domain != null)
{
domain.ProcessExit -= new EventHandler(CurrentDomain_ProcessExit);
domain.DomainUnload -= new EventHandler(domain_DomainUnload);
System.Windows.Forms.Application.ApplicationExit -= new EventHandler(Application_ApplicationExit);
}
disposed = true;
OnDispose();
}
}
void Application_ApplicationExit(object sender, EventArgs e)
{
Dispose();
}
void domain_DomainUnload(object sender, EventArgs e)
{
Dispose();
}
void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
Dispose();
}
protected abstract void OnDispose();
/// Destructor
~FinalizerBase()
{
Dispose();
}
}
}
I stitched together from code I found in internet myself WH_KEYBOARD_LL helper class:
Put the following code to some of your utils libs, let it be YourUtils.cs:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MYCOMPANYHERE.WPF.KeyboardHelper
{
public class KeyboardListener : IDisposable
{
private static IntPtr hookId = IntPtr.Zero;
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
try
{
return HookCallbackInner(nCode, wParam, lParam);
}
catch
{
Console.WriteLine("There was some error somewhere...");
}
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
private IntPtr HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
}
else if (wParam == (IntPtr)InterceptKeys.WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, false));
}
}
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
public event RawKeyEventHandler KeyDown;
public event RawKeyEventHandler KeyUp;
public KeyboardListener()
{
hookId = InterceptKeys.SetHook((InterceptKeys.LowLevelKeyboardProc)HookCallback);
}
~KeyboardListener()
{
Dispose();
}
#region IDisposable Members
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
#endregion
}
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;
public static int WM_KEYDOWN = 0x0100;
public static int WM_KEYUP = 0x0101;
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
public class RawKeyEventArgs : EventArgs
{
public int VKCode;
public Key Key;
public bool IsSysKey;
public RawKeyEventArgs(int VKCode, bool isSysKey)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args);
}
Which I use like this:
App.xaml:
<Application ...
Startup="Application_Startup"
Exit="Application_Exit">
...
App.xaml.cs:
public partial class App : Application
{
KeyboardListener KListener = new KeyboardListener();
private void Application_Startup(object sender, StartupEventArgs e)
{
KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
Console.WriteLine(args.Key.ToString());
// I tried writing the data in file here also, to make sure the problem is not in Console.WriteLine
}
private void Application_Exit(object sender, ExitEventArgs e)
{
KListener.Dispose();
}
}
The problem is that it stops working after hitting keys a while. No error is raised what so ever, I just don't get anything to output after a while. I can't find a solid pattern when it stops working.
Reproducing this problem is quiet simple, hit some keys like a mad man, usually outside the window.
I suspect there is some evil threading problem behind, anyone got idea how to keep this working?
What I tried already:
Replacing return HookCallbackInner(nCode, wParam, lParam); with something simple.
Replacing it with asynchronous call, trying to put Sleep 5000ms (etc).
Asynchronous call didn't make it work any better, it seems stop always when user keeps single letter down for a while.
You're creating your callback delegate inline in the SetHook method call. That delegate will eventually get garbage collected, since you're not keeping a reference to it anywhere. And once the delegate is garbage collected, you will not get any more callbacks.
To prevent that, you need to keep a reference to the delegate alive as long as the hook is in place (until you call UnhookWindowsHookEx).
The winner is: Capture Keyboard Input in WPF, which suggests doing :
TextCompositionManager.AddTextInputHandler(this,
new TextCompositionEventHandler(OnTextComposition));
...and then simply use the event handler argument’s Text property:
private void OnTextComposition(object sender, TextCompositionEventArgs e)
{
string key = e.Text;
...
}
IIRC, when using global hooks, if your DLL isn't returning from the callback quick enough, you're removed from the chain of call-backs.
So if you're saying that its working for a bit but if you type too quickly it stops working, I might suggest just storing the keys to some spot in memory and the dumping the keys later. For an example, you might check the source for some keyloggers since they use this same technique.
While this may not solve your problem directly, it should at least rule out one possibility.
Have you thought about using GetAsyncKeyState instead of a global hook to log keystrokes? For your application, it might be sufficient, there's lots of fully implemented examples, and was personally easier to implement.
I have used the Dylan's method to hook global keyword in WPF application and refresh hook after each key press to prevent events stop firing after few clicks . IDK, if it is good or bad practice but gets the job done.
_listener.UnHookKeyboard();
_listener.HookKeyboard();
Implementation details here
I really was looking for this. Thank you for posting this here.
Now, when I tested your code I found a few bugs. The code did not work at first. And it could not handle two buttons click i.e.: CTRL + P.
What I have changed are those values look below:
private void HookCallbackInner to
private void HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
}
}
}
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using FileManagerLibrary.Objects;
namespace FileCommandManager
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
readonly KeyboardListener _kListener = new KeyboardListener();
private DispatcherTimer tm;
private void Application_Startup(object sender, StartupEventArgs e)
{
_kListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
private List<Key> _keysPressedIntowSecound = new List<Key>();
private void TmBind()
{
tm = new DispatcherTimer();
tm.Interval = new TimeSpan(0, 0, 2);
tm.IsEnabled = true;
tm.Tick += delegate(object sender, EventArgs args)
{
tm.Stop();
tm.IsEnabled = false;
_keysPressedIntowSecound = new List<Key>();
};
tm.Start();
}
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
var text = args.Key.ToString();
var m = args;
_keysPressedIntowSecound.Add(args.Key);
if (tm == null || !tm.IsEnabled)
TmBind();
}
private void Application_Exit(object sender, ExitEventArgs e)
{
_kListener.Dispose();
}
}
}
this code work 100% in windows 10 for me :)
I hope this help u