I'm writing a game in C# and Mono. I execute the game loop and render whenever the window is idle (that is to say, when the message queue is empty). I understand how to do this on Windows (use P/Invoke to import PeekMessage from "User32.dll"), but I don't know how to get the same information about the state of the message queue on a Linux build. I've browsed the Mono repo on Github, and it doesn't look like direct access to the message queue is exposed. The version of Linux I'm running is Peppermint, if that helps. Any guidance on this would be much appreciated.
bool WindowIsIdle()
{
#if Windows
NativeMessage result;
return PeekMessage(out result, IntPtr.Zero, 0, 0, 0) == 0;
#elif Linux
return false; //how can I get this information?
#endif
}
#if Windows
//Imports a native Win32 function for checking the windows message queue
[System.Runtime.InteropServices.DllImport("User32.dll")]
static extern int PeekMessage(out NativeMessage message, IntPtr handle, uint filterMin, uint filterMax, uint remove);
struct NativeMessage
{
IntPtr Handle;
uint Message;
IntPtr WParameter;
IntPtr LParameter;
uint Time;
Point Location;
}
#endif
Related
Both the callback and the AccessibleObjectFromEvent call appear to be working as intended, and I'm not using any locking mechanisms, but if AccessibleObjectFromEvent is present in the callback the winforms form will occasionally lock up.
I noticed when it freezes it has this odd property where right-clicking on its taskbar icon unfreezes it.
Pausing with the debugger just takes you to Application.Run(new Form1()), and nothing appears blocked on the .Net side - the debugger's window updates can even trigger some Active Accessibility events and then let you step through those events in the callback - working - all while the form remains frozen!
I note that AccessibleObjectFromEvent works by sending a WM_GETOBJECT message, but the .Net side is never stuck at the AccessibleObjectFromEvent call, and calling AccessibleObjectFromEvent from inside a SetWinEventHook callback is AFAIK a normal way to do Active Accessibility.
I've not noticed any correlation with Active Accessibility events when it freezes, but I don't really have enough information to rule that out. I also tried it compiled x86 (instead of Any), and that made no difference.
I boiled it down to its most minimal form:
using Accessibility;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApp1 {
static class Program {
// PInvoke declarations, see http://www.pinvoke.net/default.aspx/user32.setwineventhook
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, WinEventFlag dwFlags);
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("oleacc.dll")]
public static extern uint AccessibleObjectFromEvent(IntPtr hwnd, uint dwObjectID, uint dwChildID, out IAccessible ppacc, [MarshalAs(UnmanagedType.Struct)] out object pvarChild);
[Flags]
public enum WinEventFlag : uint {
/// <summary>Events are ASYNC</summary>
Outofcontext = 0x0000,
/// <summary>Don't call back for events on installer's thread</summary>
Skipownthread = 0x0001,
/// <summary>Don't call back for events on installer's process</summary>
Skipownprocess = 0x0002,
/// <summary>Events are SYNC, this causes your dll to be injected into every process</summary>
Incontext = 0x0004
}
static IntPtr hookHandle = IntPtr.Zero;
static GCHandle garbageCollectorHandle;
static void AccessibilityEventHandler(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) {
try {
object objChild = null;
IAccessible accWindow = null;
AccessibleObjectFromEvent(hWnd, (uint)idObject, (uint)idChild, out accWindow, out objChild);
Debug.WriteLine("Hook was invoked");
} catch (Exception ex) {
Debug.WriteLine("Exception " + ex);
}
}
[STAThread]
static void Main() {
WinEventDelegate callback = new WinEventDelegate(AccessibilityEventHandler);
// Use the member garbageCollectorHandle to keep the delegate object in memory. Might not be needed, and can't properly pin it because it's not a primitive type.
garbageCollectorHandle = GCHandle.Alloc(callback);
SetWinEventHook(
0x7546, // eventMin (0x7546 = PropertyChanged_IsOffscreen)
0x7546, // eventMax
IntPtr.Zero,
callback,
0,
0,
WinEventFlag.Outofcontext
);
// Two hooks are not necessary to cause a freeze, but with two it can happen much faster - sometimes within minutes
SetWinEventHook(
0x0001, // eventMin (0x0001 = System_Sound)
0x0001, // eventMax
IntPtr.Zero,
callback,
0,
0,
WinEventFlag.Outofcontext
);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
With such a minimal app it's harder to notice the message pump freeze, but you won't be able to drag the window or bring it to the foreground once it's locked. The bug is intermittent, often it happens within 5 minutes, sometimes it takes so long I give up, if you leave it overnight and the form is still responsive in the morning then it might depend on machine/OS (tried on Win 10.0.17174 and 10.0.17763, .net 4.5.2 and 4.6.1).
I'm 99% sure the call to AccessibleObjectFromEvent is required for the app to freeze, but with intermittent freezes there's no way to absolutely know for sure.
Reentrancy
Following Jimi's suggestions in the comments, I ran the minimal app with the flags
WinEventFlag.Outofcontext | WinEventFlag.Skipownthread | WinEventFlag.Skipownprocess
It took a while to freeze, but has still frozen. The Debug.WriteLine() calls indicate it's still responding to Active Accessibility events normally - i.e. no recursive busy-loop is happening through that callback (at least not now that I'm looking), but the form is frozen. It's using 0% CPU in the task manager.
The freeze is slightly different now, in that the task manager doesn't list it as "not responding" and I can bring it to the foreground by left-clicking the taskbar icon - normally the taskbar can't even bring it to the foreground. However the form still can't be moved or resized, and you can't bring the form to the foreground by clicking on it.
I'd also added some Debug.WriteLine in front of the AccessibleObjectFromEvent call, and tracked reentrancy depth, I can see that it is occasionally reentrant, but usually to a depth of only one before unwinding, and no deeper than 13 before fully unwinding. This appears to be caused by there being many events already in the message queue, rather than the hook handler recursively causing events it must then handle. The UI is currently frozen and the hook handler is 0 deep (i.e. not currently reentrant).
I found this script to change the System sound volume and it works. But what are these constant volume codes called and where can I find a full list of these codes that do more things.
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
//Volume codes, or messages, or whatever they are called
const int VOLUME_MUTE = 0x80000;
const int VOLUME_DOWN = 0x90000;
const int VOLUME_UP = 0xA0000;
SendMessage(this.Handle, 0x319, IntPtr.Zero, (IntPtr)VOLUME_UP);
These are AppCommand messages.
0x319 is the Win32 Windows MSG for WM_APPCOMMAND, and the messages are more accurately APPCOMMAND_VOLUME_UP, etc...
AppCommand messages are messages sent to windows, which are handled at a global level and perform certain application functions. These tend to be linked to Keyboard hotkeys and mouse button functions.
Your app gets first crack at processing any such messages, and if you do not handle them then your apps parent does. If that doesn't handle them, then eventually it gets sent to a global message hook to process them. The key point here is that other windows can trap these messages, so it's not a guarantee that sending these messages will accomplish the task. Just like you might have seen where pressing the volume up or down on your keyboard might not always work when certain windows have focus.
You can find the details for all the messages in the Win32 API reference:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms646275(v=vs.85).aspx
I have the below code working in my C# program to control system VOLUME _UP _DOWN _MUTE, etc.
I do this using WM_APPCOMMAND messages; however, (and this is a BIG however) I am wondering if it ONLY works because I use a Keyboard with HOTKEYS, i.e.(browser, power, media player, email, etc.) the buttons at the top of the keyboard above the Function Keys.
I would like to know if my code will work on Laptops and PCs whose keyboards are not equipped with HOTKEYS?
From researching I see that this WM_APPCOMMAND stuff goes all the way back to 2001 in some message threads, so I suspect it is pretty stable on XP, Vista and now Windows 7 & 8. And it does not appear to be deprecated as companies like Logitech and other keyboard/media console manufacturers still use it for new products.
I just need to if it works for keyboards without HOTKEYS.
Thanks!
Anthony
private const uint APPCOMMAND_VOLUME_MUTE = 8;
private const uint APPCOMMAND_VOLUME_DOWN = 9;
private const uint APPCOMMAND_VOLUME_UP = 10;
[DllImport("user32.dll")]
public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
//APPCOMMAND_VOLUME_DOWN
SendMessageW(this.Handle, WmAppcommand, this.Handle, (IntPtr)APPCOMMAND_VOLUME_DOWN);
//APPCOMMAND_VOLUME_UP
SendMessageW(this.Handle, WmAppcommand, this.Handle, (IntPtr)APPCOMMAND_VOLUME_UP);
I have to call some native C functions from C# using p/invoke. So far, I had no problems marshaling the different methods and structures to C#. Where my problem resides is in the fact that many of the methods I have to call are asynchronous, and return their final results to my WinForms application through Windows Messages. For instance, I have a call to a method that has the following signature in C:
HRESULT AsyncOpenSession( LPSTR lpszLogicalName,
HANDLE hApp,
LPSTR lpszAppID,
DWORD dwTraceLevel,
DWORD dwTimeOut,
USHORT lphService,
HWND hWnd,
DWORD dwSrvcVersionsRequired,
LPWFSVERSION lpSrvcVersion,
LPWFSVERSION lpSPIVersion,
ULONG lpRequestID );
Where lpszAppID expects to receive the NAME of my application (MyApp.exe) and hWnd is a pointer to the WINDOW HANDLE of my application, which I obtain with a call to
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
public static IntPtr GetCurrentWindowHandle()
{
IntPtr handle = GetForegroundWindow();
return handle;
}
The imported signature is:
[DllImport("MSXFS.DLL", BestFitMapping = true, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int AsyncOpenSession([MarshalAs(UnmanagedType.LPStr)] string lpszLogicalName,
IntPtr hApp,
[MarshalAs(UnmanagedType.LPStr)] string lpszAppID,
UInt32 dwTraceLevel,
UInt32 dwTimeOut,
out Int32 lphService,
IntPtr hWnd,
UInt32 dwSrvcVersionsRequired,
out WFSVersion lpSrvcVersion,
out WFSVersion lpSPIVersion,
out Int32 lpRequestID);
I call the method as follows:
Int32 r = AsyncOpenSession(lpszLogicalName,
appHanlder,
"MyApp.exe",
dwTraceLevel,
dwTimeOut,
out hServ,
GetCurrentWindowHandle(),
dwSrvcVersionsRequired,
out srvcVersion,
out spiVersion,
out requestID);
Initially, the method call returns a result code that indicates the requested call has been put to the execution queue. Any time after the original call the native Dll completes the execution of the request and send a Windows Message to the application by means of its windows handle and name. The Windows Message code for this method is defined as follows:
#define WM_USER 0x0400
#define OPEN_SESSION_COMPLETE (WM_USER + 1)
More info about Windows Messages here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx
In my application I have overridden the method WndProc to be able to control the Windows Messages as follows:
protected override void WndProc(ref Message m)
{
const int wm_user = 0x0400;
const int OPEN_SESSION_COMPLETE = wm_user + 1;
switch (m.Msg)
{
case OPEN_SESSION_COMPLETE:
txtEventsState.Text = m.ToString();
break;
}
base.WndProc(ref m);
}
But I never receive a Windows Message with this code. There are no error in logs or event viewer. I know my call to the method succeeded because I do not get any error code and because it is mandatory to have an opened session before calling other methods, and I can call other methods that need a session handler successfully (no error code either).
My imported signature and call to the method is in a class library project that my application is referencing. Am I doing something wrong here? Can anyone give a light on what could be happening? I do not have access to the native code, just to a test application the shows that the Windows Messages are being sent by the native C Dll.
Thank to everybody in advance.
So, here it the deal: GetForegroundWindow() is not retriving the correct Window Handle. It happens that my application has two windows. One is the main form that I use for my test and the other displays all results and logging I am doing. So, the method was actually returning the Handle of the loggin window and not the form windows.
Using this.Handle and passing that value to the native methods made everything work correctly.
Thanks to Hans for pointing this out.
I'm trying to hook a CBT hook on Windows OSes. I'm currently using Windows 7 x64.
I've read many threads talking about this issue, but none has solved my problem. The application runs well; the hook is installed and I can see some notifications coming.
Actually the problems arised is that the application is not notified about CBT hook of other processes running on the same machine.
The application is written in C# (using Microsoft .NET). Here is a running sample:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsHook
{
class Program
{
[STAThread]
static void Main(string[] args)
{
uint thid = (uint)AppDomain.GetCurrentThreadId();
bool global = true;
mHookDelegate = Marshal.GetFunctionPointerForDelegate(new HookProc(ManagedCallback));
if (global == true) {
mNativeWrapperInstance = LoadLibrary("Native_x64.dll");
thid = 0;
} else {
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
mNativeWrapperInstance = GetModuleHandle(curModule.ModuleName);
}
}
mNativeWrappedDelegate = AllocHookWrapper(mHookDelegate);
mHookHandle = SetWindowsHookEx(/*WH_CBT*/5, mNativeWrappedDelegate, mNativeWrapperInstance, thid);
if (mHookHandle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
Application.Run(new Form());
if (FreeHookWrapper(mNativeWrappedDelegate) == false)
throw new Win32Exception("FreeHookWrapper has failed");
if (FreeLibrary(mNativeWrapperInstance) == false)
throw new Win32Exception("FreeLibrary has failed");
if (UnhookWindowsHookEx(mHookHandle) == false)
throw new Win32Exception(Marshal.GetLastWin32Error());
}
static int ManagedCallback(int code, IntPtr wParam, IntPtr lParam)
{
Trace.TraceInformation("Code: {0}", code);
if (code >= 0) {
return (0);
} else {
return (CallNextHookEx(mHookHandle, code, wParam, lParam));
}
}
delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);
static IntPtr mHookHandle;
static IntPtr mHookDelegate;
static IntPtr mNativeWrapperInstance = IntPtr.Zero;
static IntPtr mNativeWrappedDelegate = IntPtr.Zero;
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int hook, IntPtr callback, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
internal static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeLibrary(IntPtr hModule);
[DllImport("Native_x64.dll")]
private static extern IntPtr AllocHookWrapper(IntPtr callback);
[DllImport("Native_x64.dll")]
private static extern bool FreeHookWrapper(IntPtr wrapper);
[DllImport("Native_x64.dll")]
private static extern int FreeHooksCount();
}
}
AllocHookWrapper and FreeHookWrapper are imported routines from a DLL (Native_x64.dll) compiler for x64 platform, located at the same directory of the application. AllocHookWrapper stores the function pointer (of the managed routine) and returns the DLL routine calling the function pointer).
Here is the code of the DLL:
#include "stdafx.h"
#include "iGecko.Native.h"
#ifdef _MANAGED
#pragma managed(push, off)
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define WRAPPER_NAME(idx) Wrapper ## idx
#define WRAPPER_IMPLEMENTATION(idx) \
LRESULT WINAPI WRAPPER_NAME(idx)(int code, WPARAM wparam, LPARAM lparam) \
{ \
if (sHooksWrapped[idx] != NULL) \
return (sHooksWrapped[idx])(code, wparam, lparam); \
else \
return (0); \
}
#define WRAPPER_COUNT 16
HOOKPROC sHooksWrapped[WRAPPER_COUNT] = { NULL };
WRAPPER_IMPLEMENTATION(0x00);
WRAPPER_IMPLEMENTATION(0x01);
WRAPPER_IMPLEMENTATION(0x02);
WRAPPER_IMPLEMENTATION(0x03);
WRAPPER_IMPLEMENTATION(0x04);
WRAPPER_IMPLEMENTATION(0x05);
WRAPPER_IMPLEMENTATION(0x06);
WRAPPER_IMPLEMENTATION(0x07);
WRAPPER_IMPLEMENTATION(0x08);
WRAPPER_IMPLEMENTATION(0x09);
WRAPPER_IMPLEMENTATION(0x0A);
WRAPPER_IMPLEMENTATION(0x0B);
WRAPPER_IMPLEMENTATION(0x0C);
WRAPPER_IMPLEMENTATION(0x0D);
WRAPPER_IMPLEMENTATION(0x0E);
WRAPPER_IMPLEMENTATION(0x0F);
const HOOKPROC sHookWrappers[] = {
WRAPPER_NAME(0x00),
WRAPPER_NAME(0x01),
WRAPPER_NAME(0x02),
WRAPPER_NAME(0x03),
WRAPPER_NAME(0x04),
WRAPPER_NAME(0x05),
WRAPPER_NAME(0x06),
WRAPPER_NAME(0x07),
WRAPPER_NAME(0x08),
WRAPPER_NAME(0x09),
WRAPPER_NAME(0x0A),
WRAPPER_NAME(0x0B),
WRAPPER_NAME(0x0C),
WRAPPER_NAME(0x0D),
WRAPPER_NAME(0x0E),
WRAPPER_NAME(0x0F)
};
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
return (TRUE);
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
extern "C" IGECKONATIVE_API HOOKPROC WINAPI AllocHookWrapper(HOOKPROC wrapped)
{
for(int i = 0; i < WRAPPER_COUNT; i++) {
if (sHooksWrapped[i] == NULL) {
sHooksWrapped[i] = wrapped;
return sHookWrappers[i];
}
}
return (NULL);
}
extern "C" IGECKONATIVE_API BOOL WINAPI FreeHookWrapper(HOOKPROC wrapper)
{
for(int i = 0; i < WRAPPER_COUNT; i++) {
if (sHookWrappers[i] == wrapper) {
sHooksWrapped[i] = NULL;
return TRUE;
}
}
return (FALSE);
}
extern "C" IGECKONATIVE_API INT WINAPI FreeHooksCount()
{
int c = 0;
for(int i = 0; i < WRAPPER_COUNT; i++) {
if (sHooksWrapped[i] == NULL)
c++;
}
return (c);
}
Actually I'm interested about window related events (creation, destruction) on a certain system, but I'm actually unable to being notified by the OS...
What's going on? What have I missed?
Note that I'm running with the Administratos group.
I've found this interesting section in this page
Global hooks are not supported in the .NET Framework
You cannot implement global hooks in Microsoft .NET Framework. To install a global hook, a hook must have a native DLL export to insert itself in another process that requires a valid, consistent function to call into. This behavior requires a DLL export. The .NET Framework does not support DLL exports. Managed code has no concept of a consistent value for a function pointer because these function pointers are proxies that are built dynamically.
I thought to do the trick by implementing a native DLL containing the hook callback, which calls the managed callback. However, the managed callback is called only in the process which calls the SetWindowsHookEx routine and not called by other processes.
What are the possible workarounds?
Maybe allocating heap memory storing a process id (the managed one), and sending user messages describing the hooked functions?
What I'm trying to achieve is a system wide monitor, which detect new processes executed, detect created windows position and size, as well closed windows, moved windows, minimized/maximized windows. Successively the monitor shall detect mouse and keyboard events (always system-wide) and also it has to "emulate" mouse and keyboard events.
Every process in the same desktop has to be monitored, indepently by the archiveture (32 or 64 bit) and by the underlying framework (native or managed).
The monitor shall force process windows position, size and movements, and shall be able to act as local user in order to allow remote user to act as a local user (something like VNC).
Sorry but I don't understand the sense of "wrapping" unmanaged DLL and usage of ManagedCallback as the hook inside of managed EXE.
You should understand, that the method which you use as the callback of system wide CBT hook (parameter of SetWindowsHookEx) must be loaded in the address space of all process (it will be done a DLL injection of the module where hook function is implemented). In Windows SDK (MSDN) you can read following (see remark on http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx):
SetWindowsHookEx can be used to inject
a DLL into another process. A 32-bit
DLL cannot be injected into a 64-bit
process, and a 64-bit DLL cannot be
injected into a 32-bit process. If an
application requires the use of hooks
in other processes, it is required
that a 32-bit application call
SetWindowsHookEx to inject a 32-bit
DLL into 32-bit processes, and a
64-bit application call
SetWindowsHookEx to inject a 64-bit
DLL into 64-bit processes. The 32-bit
and 64-bit DLLs must have different
names.
Moreover you write in your question about system wide hook and use not 0 as the last parameter of SetWindowsHookEx. One more problem: as the third parameter of SetWindowsHookEx (HINSTANCE hMod) you use an instance of not the dll with the code of hook (the code of hook you have currently in the EXE).
So my suggestion: you have to write a new native code for implementation of system wide CBT hook and place it inside a DLL. I recommend you also to choose a base address (linker switch) for the DLL, which is not a standard value to reduce DLL rebasing. It is not mandatory but this will save memory resources.
Sorry for the bad news, but in my opinion your current code should be full rewritten.
UPDATED based on update in the question: One more time I repeat, that if you call in one process SetWindowsHookEx to set a CBT hook, you should give as a parameter the module instance (the start address) of a DLL and the address of a function in the DLL which implement the hook. It is not important from which process you call SetWindowsHookEx function. The DLL used as a parameter will be loaded (injected) in all processes of the same windows station which use User32.dll. So you have some native restrictions. If you want support both 32-bit and 64-bit platforms you have to implement two dlls: one 32-bit and 64-bit DLL. Moreover there are a problem with the usage of different .NET versions in the same process. It should be theoretically possible to do this only with .NET 4.0. In general it is very complex problem. And you should understand it I write about a DLL I mean not only the DLL, but all its dependencies. So if you implement a native DLL which call a managed DLL (.NET DLL) it would be not possible.
So if you want use global CBT hook you have to implement if as a two native DLLs (one 32-bit and 64-bit) and set install the hook inside of two processes (one 32-bit and 64-bit). So do exact what is described in the remark of the SetWindowsHookEx documentation http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx (see above quote). I see no more easier ways.