How can I programmatically (in C#) determine, if ANOTHER foreign application (native, java, .NET or whatever...) is currently demanding user input? Can this be done fully in Managed code?
What I'm looking for is the implementation of:
static Boolean IsWaitingForUserInput(String processName)
{
???
}
By demanding user input I mean when an application asks the user to enter some data or quit an error message (Modal dialogs) and is not able to perform its normal tasks anymore. A drawing application that is waiting for the user to draw something is not meant here.
PS: After edits to reflect the comments at the bottom and make the concern clearer, some comments and answers may not be 100% consistent with the question. Take this into account when evaluating the answers and remarks.
It's in general impossible. Take for instance a common kind of application, a word processor. Nowadays that will be running spellchecks in the background, it periodically auto-saves your document, etcetera. Yet from a users perspective it's waiting for input all the time.
Another common case would be a slideshow viewer. At any moment in time you could press a key to advance a slide. Yet your typical user would not view this as "waiting for input".
To summarize: "waiting for input" is a subjective state and therefore cannot be determined programmatically.
How do you like this?
I worked out a solution that seems to work, please notify me in case of problems with this code so I also gain benefit of improvements. It works for Excel as far as I tested. The only issue I dislike is that I had to use unmanaged calls. It also handles the case when an application is based on a dialog like for MFC, derived from CDialog.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
namespace Util
{
public class ModalChecker
{
public static Boolean IsWaitingForUserInput(String processName)
{
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length == 0)
throw new Exception("No process found matching the search criteria");
if (processes.Length > 1)
throw new Exception("More than one process found matching the search criteria");
// for thread safety
ModalChecker checker = new ModalChecker(processes[0]);
return checker.WaitingForUserInput;
}
#region Native Windows Stuff
private const int WS_EX_DLGMODALFRAME = 0x00000001;
private const int GWL_EXSTYLE = (-20);
private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
[DllImport("user32")]
private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
[DllImport("user32", CharSet = CharSet.Auto)]
private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32")]
private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
#endregion
// The process we want the info from
private Process _process;
private Boolean _waiting;
private ModalChecker(Process process)
{
_process = process;
_waiting = false; //default
}
private Boolean WaitingForUserInput
{
get
{
EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
return _waiting;
}
}
private int WindowEnum(IntPtr hWnd, int lParam)
{
if (hWnd == _process.MainWindowHandle)
return 1;
IntPtr processId;
GetWindowThreadProcessId(hWnd, out processId);
if (processId.ToInt32() != _process.Id)
return 1;
uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
if ((style & WS_EX_DLGMODALFRAME) != 0)
{
_waiting = true;
return 0; // stop searching further
}
return 1;
}
}
}
If I understand you well, you may try to enumerate the process's threads and check their states. Windows Task Manager does something similar. This however will require Win32 functions - Thread32First and Thread32Next among others - but you can achieve this by the simplest use of P/Invoke in C#:
[DllImport("Executor.dll")]
public static extern bool Thread32First(IntPtr handle, IntPtr threadEntry32);
(Precise signature may differ).
EDIT: Ok, there are corresponding functions in the .NET library.
If possible, rewrite the other code to be a concurrent input processor (similar to the algorithm for a concurrent web server):
Wait for input
Fork process
Parent: Repeat
Child: (Worker) handle input
Of course, you could still have your function:
static Boolean IsWaitingForUserInput(String processName) {
return true;
}
Related
I am making a Unity app that will run in Windows in a custom arcade cabinet and will load and stop games.
I have been abusing winAPI to do this.
Eg. I start the game process, then use findwindow to wait until a window of the correct name exists and also get an IntPtr for that window which I can send to the SetForegroundWindow winAPI function to make sure the game is in front and focused for inputs.
This all works fine.
Except for some unreal games I was using to test. Eg. this game 'Peekaboo', despite calling findwindow every frame in my Unity app a window called Peekaboo is never found, though when I look in Windows it is clearly there.
Its the same story for another Unreal engine game 'Mechwarrior 5 mercenaries'
I think it might have something to do with the fact that unreal games seems to launch several nested processes like in the image below.
(Eg. To stop non-unreal games I can just stop the process using the reference I got when I started it. But this did not work with Unreal games, the exe I started is gone and I needed to find that Win64-Shipping process and stop that.)
Here is the code I am using to call findwindow and also setforground
using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;
public static class WinAPIGetWindowIfExists
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static IntPtr DoesWindowExist(string windowName)
{
Debug.Log("looking for window: " + windowName);
return FindWindow(null, #windowName);
}
}
public static class WinAPISetForegroundWindow
{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr handle);
public static void SetForeground(IntPtr windowHandle)
{
SetForegroundWindow(windowHandle);
}
}
So how can I get a reference to an unreal game window so I can set it to the forground?
UPDATE:
I am super confused now, as I have called EnumWindows, and called GetWindowText for every window returned by it, and I have found a window called Peekaboo.. If I pass that window's handle to SetForeground the correct game window is set to foreground, also if I sendmessage with this message 0x000D; I get the text peekboo back. Yet, findwindow still finds no window named peekaboo...
So I can use this EnumWindows to solve my issue.. but this utterly sucks. Why does iterating through every window, calling get windowtext, then checking if the text contains the window title work, whereas findwindow doesnt work?
So here is the code for using enumwindows. This code does work for Unreal engine games. I found this somewhere on the internet. It's a good thing too cus figuring out how to make these functions work with interop from just MS documentation is not trivial imo. It was really not clear to me from the MS documentation that after calling the enumwindows function it would call the callback function over and over for every single window returned. It seems like a kind of ridiculous pattern to me, just to avoid having any unsafe code.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
public delegate bool WNDENUMPROC(
UInt32 hWnd,
IntPtr lParam
);
internal static class WinAPI
{
[DllImport("user32.dll")]
internal static extern bool EnumWindows(
WNDENUMPROC cb, // WNDENUMPROC lpEnumFiunc
IntPtr manageObject // LPARAM lParam (the managed object))
);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetWindowText(
UInt32 hWnd,
StringBuilder title,
int size
);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam,
StringBuilder lParam);
}
public class WindowEnumerator
{
private const uint WM_GETTEXT = 0x000D;
public static IntPtr FindWindowWithThisTitle(string findthis)
{
List<UInt32> hWnds = new List<UInt32>();
//
// Prevent the managed object (hWnds) from being collected
// by the garbage collector:
//
GCHandle objHandle = GCHandle.Alloc(hWnds);
//
// Create an instance of a delegate in order to
// provide a «callback» for EnumWindows:
//
WNDENUMPROC enumProc = new WNDENUMPROC(callback);
//
// Get an internal representation for the gc handle
// so as to be able to pass it to the second parameter
// of EnumWindows:
//
IntPtr objHandlePtr = GCHandle.ToIntPtr(objHandle);
WinAPI.EnumWindows( // Let Windows iterate over each window and
enumProc, // call enumProc (which is «initialized» for the method callback)
objHandlePtr // and pass this pointer to the method
);
//
// Free the handle of the object so that
// the object can be collected and
// thus to prevent memory leaks:
//
objHandle.Free();
StringBuilder title = new StringBuilder(256);
foreach (UInt32 hWnd in hWnds)
{
WinAPI.GetWindowText(hWnd, title, 256);
if (title.ToString().Contains(findthis))
{
return (IntPtr)hWnd;
}
//UnityEngine.Debug.Log(" "+ hWnd+" "+ title);
}
return IntPtr.Zero;
}
private static bool callback(
//
// After calling WinAPI.EnumWindows, Windows calls
// this method for each Window and passes it
// the hWnd of the respective Window and
// the value that was given as second parameter
// to EnumWindows (objHandlePtr);
//
UInt32 hWnd,
IntPtr objHandlePtr
)
{
//
// Get the handle to the object from the pointer:
//
GCHandle objHandle = GCHandle.FromIntPtr(objHandlePtr);
//
// and cast the handle's target into the underlying
// managed object:
//
List<UInt32> obj = (List<UInt32>)objHandle.Target;
obj.Add(hWnd);
return true;
}
}
I've searched all over to try and find an answer to my predicament but cannot seem to find a valid answer. I'm trying to write some equivalent user32.dll code with Xlib instead so I can support Linux users. I'm of course running Linux, so I'm using Mono. Problem comes along when I cannot even grab a window handle from the Process class because it was never even implemented:
[MonoTODO]
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
[MonitoringDescription ("The handle of the main window of the process.")]
public IntPtr MainWindowHandle {
get {
return((IntPtr)0);
}
}
(Source)
This is especially frustrating because there is seemingly no alternative. I'm trying to grab a window handle like so:
[DllImport("libX11")]
private static extern IntPtr XOpenDisplay(IntPtr display);
[DllImport("libX11")]
private static extern int XRaiseWindow(IntPtr display, IntPtr window);
private IntPtr ApplicationHandle;
private IntPtr Display;
private void TestXlib() {
Process process = Process.GetProcessById(myPid);
ApplicationHandle = process.MainWindowHandle;
Display = XOpenDisplay(IntPtr.Zero);
XRaiseWindow(Display, ApplicationHandle);
}
NOTE: In place of "myPid" is a proper process ID. Replace "myPid" with a valid process ID. Yes, I did make sure the replaced "myPid" was a valid process ID and my code didn't throw any errors indicating any process IDs I used as invalid.
This doesn't crash my application, but almost every time I call XRaiseWindow it prints:
X11 Error encountered:
Error: BadWindow (invalid Window parameter)
Request: 12 (0)
Resource ID: 0x0
Serial: 121
Hwnd: <null>
Control: <null>
This obviously occurs because Process.MainWindowHandle returns IntPtr.Zero. Is there no other way to get a window handle? Thanks in advance!
Yes, I know this was forever ago that I asked this, but I'm answering it now because I kept forgetting to answer it after I found the solution myself. I originally used #SushiHangover's solution but didn't really like it because I felt relying on an external program(xwininfo) was a hotfix and ultimately just added another dependency. Hopefully this helps other C# developers using Mono. This code was originally written for .NET Framework 2.0. It's not fancy and isn't really documented well. My solution was just to natively enumerate the windows using Xlib myself and return all windows whose title's match the described title.
In X11Wrapper.cs:
using System;
using System.Runtime.InteropServices;
namespace Program.PInvoke.Xlib {
public static class X11Wrapper {
public const string SOName = "libX11.so";
[DllImport(SOName)]
// See: https://tronche.com/gui/x/xlib/display/display-macros.html#DefaultRootWindow
public static extern IntPtr XDefaultRootWindow(IntPtr display);
[DllImport(SOName)]
// See: https://tronche.com/gui/x/xlib/window-information/XQueryTree.html
public static extern int XQueryTree(IntPtr display, IntPtr w,
out IntPtr root_return, out IntPtr parent_return,
out IntPtr[] children_return, out int nchildren_return);
[DllImport(SOName)]
// See: https://tronche.com/gui/x/xlib/ICC/client-to-window-manager/XFetchName.html
public static extern int XFetchName(IntPtr display, IntPtr w,
out string window_name_return);
}
}
In Linux.Utilities.cs:
using Program.PInvoke.Xlib;
namespace Program {
public static partial class Utilities {
public static bool IsUnix {
get {
return Environment.OSVersion.
Platform == PlatformID.Unix;
}
}
private static IntPtr[] FindChildWindows(IntPtr display, IntPtr window,
string title, ref List<IntPtr> windows) {
IntPtr rootWindow;
IntPtr parentWindow;
IntPtr[] childWindows = new IntPtr[0];
int childWindowsLength;
X11Wrapper.XQueryTree(display, window,
out rootWindow, out parentWindow,
out childWindows, out childWindowsLength);
childWindows = new IntPtr[childWindowsLength];
X11Wrapper.XQueryTree(display, window,
out rootWindow, out parentWindow,
out childWindows, out childWindowsLength);
string windowFetchedTitle;
X11Wrapper.XFetchName(display, window, out windowFetchedTitle);
if(title == windowFetchedTitle &&
!windows.Contains(window)) {
windows.Add(window);
}
for(int childWindowsIndexer = 0;
childWindowsIndexer < childWindows.Length;
childWindowsIndexer++) {
IntPtr childWindow = childWindows[childWindowsIndexer];
string childWindowFetchedTitle;
X11Wrapper.XFetchName(display, childWindow,
out childWindowFetchedTitle);
if(title == childWindowFetchedTitle &&
!windows.Contains(childWindow)) {
windows.Add(childWindow);
}
FindChildWindows(display, childWindow, title, ref windows);
}
windows.TrimExcess();
return windows.ToArray();
}
public static IntPtr[] FindWindows(IntPtr display, string title) {
List<IntPtr> windows = new List<IntPtr>();
return FindChildWindows(display,
X11Wrapper.XDefaultRootWindow(display),
title,
ref windows);
}
}
}
Footnote: I initially stated I wasn't a C developer(Things have changed since then and I've learned C) so I was hesitant to implement the functionality myself using interop. If you do end up using Xlib a lot more like I did then consider using tronche as an Xlib API reference. It is in C but I found it was pretty easy to translate to PInvokable functions and marshable structs in C#. Has some good notes to take into account too. Another good resource to help translation is directly using the source to find the definitions of the low level types to help find C# equivalents. Something like this should greatly aid you: http://refspecs.linuxbase.org/LSB_4.0.0/LSB-Desktop-generic/LSB-Desktop-generic/libx11-ddefs.html
I have to stop windows from going into sleep when my program is running.
And I don't only want to prevent the sleep-timer, I also want to cancel the sleep-event if I press the sleep-button or in any other way actively tell the computer to sleep. Therefore SetThreadExecutionState is not enough.
Or...I don't actually have to prevent the sleep completely, only delay it 5-10sec to allow my program to finish a task.
(I know that this is bad program behavior but it's only for personal use.)
After considering vim's answer
"Using PowerCreateRequest, PowerSetRequest, and PowerClearRequest
functions is the preferred method."
with the linked AvailabilityRequests.docx on msdn which is exhausting to get into it (too much to read), I have searched the web for a concrete example in c# that is based on the PowerCreateRequest and found http://go4answers.webhost4life.com/Example/problem-monitor-wakeup-service-windows7-12092.aspx [EDIT 2016 - isn't available anymore]
Copied and adapted it to my needs (PInvoke of CloseHandle copied from msdn):
using System.Runtime.InteropServices;
#region prevent screensaver, display dimming and automatically sleeping
POWER_REQUEST_CONTEXT _PowerRequestContext;
IntPtr _PowerRequest; //HANDLE
// Availability Request Functions
[DllImport("kernel32.dll")]
static extern IntPtr PowerCreateRequest(ref POWER_REQUEST_CONTEXT Context);
[DllImport("kernel32.dll")]
static extern bool PowerSetRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll")]
static extern bool PowerClearRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
internal static extern int CloseHandle(IntPtr hObject);
// Availablity Request Enumerations and Constants
enum PowerRequestType
{
PowerRequestDisplayRequired = 0,
PowerRequestSystemRequired,
PowerRequestAwayModeRequired,
PowerRequestMaximum
}
const int POWER_REQUEST_CONTEXT_VERSION = 0;
const int POWER_REQUEST_CONTEXT_SIMPLE_STRING = 0x1;
const int POWER_REQUEST_CONTEXT_DETAILED_STRING = 0x2;
// Availablity Request Structures
// Note: Windows defines the POWER_REQUEST_CONTEXT structure with an
// internal union of SimpleReasonString and Detailed information.
// To avoid runtime interop issues, this version of
// POWER_REQUEST_CONTEXT only supports SimpleReasonString.
// To use the detailed information,
// define the PowerCreateRequest function with the first
// parameter of type POWER_REQUEST_CONTEXT_DETAILED.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct POWER_REQUEST_CONTEXT
{
public UInt32 Version;
public UInt32 Flags;
[MarshalAs(UnmanagedType.LPWStr)]
public string
SimpleReasonString;
}
[StructLayout(LayoutKind.Sequential)]
public struct PowerRequestContextDetailedInformation
{
public IntPtr LocalizedReasonModule;
public UInt32 LocalizedReasonId;
public UInt32 ReasonStringCount;
[MarshalAs(UnmanagedType.LPWStr)]
public string[] ReasonStrings;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct POWER_REQUEST_CONTEXT_DETAILED
{
public UInt32 Version;
public UInt32 Flags;
public PowerRequestContextDetailedInformation DetailedInformation;
}
#endregion
/// <summary>
/// Prevent screensaver, display dimming and power saving. This function wraps PInvokes on Win32 API.
/// </summary>
/// <param name="enableConstantDisplayAndPower">True to get a constant display and power - False to clear the settings</param>
private void EnableConstantDisplayAndPower(bool enableConstantDisplayAndPower)
{
if (enableConstantDisplayAndPower)
{
// Set up the diagnostic string
_PowerRequestContext.Version = POWER_REQUEST_CONTEXT_VERSION;
_PowerRequestContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
_PowerRequestContext.SimpleReasonString = "Continuous measurement"; // your reason for changing the power settings;
// Create the request, get a handle
_PowerRequest = PowerCreateRequest(ref _PowerRequestContext);
// Set the request
PowerSetRequest(_PowerRequest, PowerRequestType.PowerRequestSystemRequired);
PowerSetRequest(_PowerRequest, PowerRequestType.PowerRequestDisplayRequired);
}
else
{
// Clear the request
PowerClearRequest(_PowerRequest, PowerRequestType.PowerRequestSystemRequired);
PowerClearRequest(_PowerRequest, PowerRequestType.PowerRequestDisplayRequired);
CloseHandle(_PowerRequest);
}
}
I had a problem like this with a hardware device connected via usb. XP /Vista would sleep/hibernate right in the middle of ... Great you say, when it resumes it can just continue. If the hardware is still connected!!!
Users have the habit of pulling cables out whenever they feel like it.
You need to handle XP and Vista
Under XP trap the WM_POWERBROADCAST and look for the PBT_APMQUERYSUSPEND wparam.
// See if bit 1 is set, this means that you can send a deny while we are busy
if (message.LParam & 0x1)
{
// send the deny message
return BROADCAST_QUERY_DENY;
} // if
else
{
return TRUE;
} // else
Under Vista use SetThreadExecutionState like this
// try this for vista, it will fail on XP
if (SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED) == NULL)
{
// try XP variant as well just to make sure
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
} // if
and when you app has finished set it back to normal
// set state back to normal
SetThreadExecutionState(ES_CONTINUOUS);
Using PowerCreateRequest, PowerSetRequest, and PowerClearRequest functions is the preferred method. Details and sample code (C/C#) are inside http://msdn.microsoft.com/en-us/library/windows/hardware/gg463205.aspx
set wsc = CreateObject("WScript.Shell")
Do
WScript.Sleep (60*1000)
wsc.SendKeys ("{SCROLLLOCK 2}")
Loop
-put the above code in notepad and save the file as .vbs and double click the file
The same technique applies as for preventing the screensaver should be used. See Programmatically prevent Windows screensaver from starting.
Note that some security settings can override this (forcing computers to lock after a certain time is one).
If you need a display that should work until your application is running then try to set "ES_DISPLAY_REQUIRED" instead of away mode:
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
Once you are done with your application make sure to clear all other flags.
SetThreadExecutionState(ES_CONTINUOUS);
How about waking it back up if it goes to sleep?
http://www.enterprisenetworksandservers.com/monthly/art.php?1049
Below is my attempt using the modern power availability request API (supersedes SetThreadExecutionState), as suggested by vim.
I'm using a nice P/Invoke NuGet I came across, Vanara.PInvoke.Kernel32:
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
// create request object
using var request = PowerCreateRequest(new REASON_CONTEXT("App FOO is working"));
if (request.IsInvalid)
{
throw new InvalidOperationException(
$"Could not create power availability request: {Win32Error.GetLastError()}");
}
// send request
if (!PowerSetRequest(request, POWER_REQUEST_TYPE.PowerRequestSystemRequired))
{
throw new InvalidOperationException(
$"Could not send power availability request: {Win32Error.GetLastError()}");
}
// do stuff that required the machine to be up
Console.WriteLine("Doing stuff...");
await Task.Delay(5000);
// clear request
if (!PowerClearRequest(request, POWER_REQUEST_TYPE.PowerRequestSystemRequired))
{
Console.WriteLine(
"WARNING: Could not clear power availability request: {0}",
Win32Error.GetLastError());
}
You can see your request in action by issuing powercfg /requests from an admin terminal.
I have a Windows CE embedded 6.0 application that opens another app in the background, and I want to bring the other app to the front. I first tried SetParent with the third party app's MainWindowHandle and it didnt work. I then tried SetActiveWindow on the same MainWindowHandle again and it didnt work. This led me to believe that the MainWindowHandle was messed up, and when I print it on the console, its always 0. This brings me to my first question: Is it possible that the dev for the app forgot to mention what the MainWindow is? Or is it assigned automatically in .NET?
Secondly, now that that approach failed, I tried to EnumWindows, then get the ID for each window and match it to the process Id I knew for my required program. This gave me an exception 0x80131515 saying "EnumWindows" is not supported. I have imported EnumWindows from CoreDll just fine. Second question: what could be the cause of this error? What am I doing wrong?
Sorry! Here's some code (Assume VCProcess has already been started):
[DllImport("coredll.dll")]
static extern int EnumWindows(CallbackDef callback, int lParam);
[DllImport("coredll.dll")]
static extern int GetWindowThreadProcessId(IntPtr hWnd, int pid);
static void Main()
{
callBackPtr = new CallBackPtr(Report);
EnumWindows(callBackPtr, 0);
}
public static bool Report(int hwnd, int lParam)
{
int pid = 0;
GetWindowThreadProcessId(hWnd, pid);
if (pid == VCProcessId)
{
SetForegroundWindow(hWnd);
}
MessageBox.show("Window handle is "+hwnd);
return true;
}
Your OEM must not have included support for EnumWindows. You could try FindWindow instead.
I would probably P/Invoke SetForegroundWindow to do this. SetActiveWindow does not work if the application is in the background.
-PaulH
Edit
P/Invoking EnumWindows can't throw a System.NotSupportedException (unless you throw it in your code) and GetLastError() wouldn't return an HRESULT COR_E_NOTSUPPORTED. There's something fishy in your code.
I am answering this question after having the same issue and having it resolved.
While it is true that OEMs may not include certain portions of the OS as part of WindowsCE (being the nature of its modular architecture), it is also true that a call like EnumWindows, or most other low level calls for that matter, are intrinsically part of the OS and it would be crazy to remove them.
I actually received a message back from a Microsoft engineer (!) which pointed out that the issue is the way the callback is defined. While I tried different approaches (delegates, intPtr vs int, and others) he gave the following answer that actually works well in WindowsCE 5/6 for different devices:
"[The “EnumWindows call from .Net/C# Application results in NotSupportedException 0x80131515” error because it ONLY supports Integer return types: I2, I4 etc. This applies to all callback methods and may vary depending on the call being used]"
So INSTEAD OF defining your callback as you did (I tried delegates, WinProcs and others unsuccessfully as well), define it as:
[DllImport("coredll.dll")]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int EnumWindows(IntPtr callPtr, int param);
which works perfectly!!
The following is my working code implementing this approach and works flawlessly in different devices running PocketPC/WindowsCE etc:
public delegate int CallBackPtr(int hwnd, int param);
[DllImport("coredll.dll")]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int EnumWindows(IntPtr callPtr, int param);
private static List<IntPtr> windows = new List<IntPtr>();
private static int CallBackMethod(int hwnd, int param)
{
windows.Add(new IntPtr(hwnd));
return 1;
}
private static void GetAllWindowsHandles()
{
// using a delegate does NOT work.
//EnumWindows(delegate(IntPtr wnd, IntPtr param)
//{
// windows.Add(wnd);
// return true;
//}, IntPtr.Zero);
CallBackPtr callbackPtr = CallBackMethod;
IntPtr cb = Marshal.GetFunctionPointerForDelegate(callbackPtr);
EnumWindows(cb, 0);
}
CJ.
90% of the time I am unable to launch osk.exe from a 32bit process on Win7 x64. Originally the code was just using:
Process.Launch("osk.exe");
Which won't work on x64 because of the directory virtualization. Not a problem I thought, I'll just disable virtualization, launch the app, and enable it again, which I thought was the correct way to do things. I also added some code to bring the keyboard back up if it has been minimized (which works fine) - the code (in a sample WPF app) now looks as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;using System.Diagnostics;
using System.Runtime.InteropServices;
namespace KeyboardTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
private const UInt32 WM_SYSCOMMAND = 0x112;
private const UInt32 SC_RESTORE = 0xf120;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private string OnScreenKeyboadApplication = "osk.exe";
public MainWindow()
{
InitializeComponent();
}
private void KeyboardButton_Click(object sender, RoutedEventArgs e)
{
// Get the name of the On screen keyboard
string processName = System.IO.Path.GetFileNameWithoutExtension(OnScreenKeyboadApplication);
// Check whether the application is not running
var query = from process in Process.GetProcesses()
where process.ProcessName == processName
select process;
var keyboardProcess = query.FirstOrDefault();
// launch it if it doesn't exist
if (keyboardProcess == null)
{
IntPtr ptr = new IntPtr(); ;
bool sucessfullyDisabledWow64Redirect = false;
// Disable x64 directory virtualization if we're on x64,
// otherwise keyboard launch will fail.
if (System.Environment.Is64BitOperatingSystem)
{
sucessfullyDisabledWow64Redirect = Wow64DisableWow64FsRedirection(ref ptr);
}
// osk.exe is in windows/system folder. So we can directky call it without path
using (Process osk = new Process())
{
osk.StartInfo.FileName = OnScreenKeyboadApplication;
osk.Start();
osk.WaitForInputIdle(2000);
}
// Re-enable directory virtualisation if it was disabled.
if (System.Environment.Is64BitOperatingSystem)
if (sucessfullyDisabledWow64Redirect)
Wow64RevertWow64FsRedirection(ptr);
}
else
{
// Bring keyboard to the front if it's already running
var windowHandle = keyboardProcess.MainWindowHandle;
SendMessage(windowHandle, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
}
}
}
}
But this code, most of the time, throws the following exception on osk.Start():
The specified procedure could not be found
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
I've tried putting long Thread.Sleep commands in around the osk.Start line, just to make sure it wasn't a race condition, but the same problem persists. Can anyone spot where I'm doing something wrong, or provide an alternative solution for this? It seems to work fine launching Notepad, it just won't play ball with the onscreen keyboard.
A 32 bit application running on a 64 bit operating system should start the 64 bit version of osk.exe.
Below you see a code snipped written in C# to start the correct on screen keyboard.
private static void ShowKeyboard()
{
var path64 = #"C:\Windows\winsxs\amd64_microsoft-windows-osk_31bf3856ad364e35_6.1.7600.16385_none_06b1c513739fb828\osk.exe";
var path32 = #"C:\windows\system32\osk.exe";
var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
Process.Start(path);
}
I don't have a very solid explanation for the exact error message you are getting. But disabling redirection is going to mess up the .NET framework. By default, Process.Start() P/Invokes the ShellExecuteEx() API function to start the process. This function lives in shell32.dll, a DLL that might have to be loaded if that wasn't previously done. You'll get the wrong one when you disable redirection.
A workaround for that is to set ProcessStartInfo.UseShellExecute to false. You don't need it here.
Clearly, disabling redirection is a risky approach with side-effects you cannot really predict. There are lots of DLLs that get demand-loaded. A very small helper EXE that you compile with Platform Target = Any CPU can solve your problem.
This is my code
var path64 = Path.Combine(Directory.GetDirectories(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "winsxs"), "amd64_microsoft-windows-osk_*")[0], "osk.exe");
var path32 = #"C:\windows\system32\osk.exe";
var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
if(File.Exists(path))
{
Process.Start(path);
}
Certain things are going on under the hood that require you to start osk.exe from an MTA thread. The reason seems to be that a call to Wow64DisableWow64FsRedirection only affects the current thread. However, under certain conditions, Process.Start will create the new process from a separate thread, e.g. when UseShellExecute is set to false and also when being called from an STA thread as it seems.
The code below checks the apartment state and then makes sure to start the On-Screen Keyboard from an MTA thread:
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd,
UInt32 Msg,
IntPtr wParam,
IntPtr lParam);
private const UInt32 WM_SYSCOMMAND = 0x112;
private const UInt32 SC_RESTORE = 0xf120;
private const string OnScreenKeyboardExe = "osk.exe";
[STAThread]
static void Main(string[] args)
{
Process[] p = Process.GetProcessesByName(
Path.GetFileNameWithoutExtension(OnScreenKeyboardExe));
if (p.Length == 0)
{
// we must start osk from an MTA thread
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
ThreadStart start = new ThreadStart(StartOsk);
Thread thread = new Thread(start);
thread.SetApartmentState(ApartmentState.MTA);
thread.Start();
thread.Join();
}
else
{
StartOsk();
}
}
else
{
// there might be a race condition if the process terminated
// meanwhile -> proper exception handling should be added
//
SendMessage(p[0].MainWindowHandle,
WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
}
}
static void StartOsk()
{
IntPtr ptr = new IntPtr(); ;
bool sucessfullyDisabledWow64Redirect = false;
// Disable x64 directory virtualization if we're on x64,
// otherwise keyboard launch will fail.
if (System.Environment.Is64BitOperatingSystem)
{
sucessfullyDisabledWow64Redirect =
Wow64DisableWow64FsRedirection(ref ptr);
}
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = OnScreenKeyboardExe;
// We must use ShellExecute to start osk from the current thread
// with psi.UseShellExecute = false the CreateProcessWithLogon API
// would be used which handles process creation on a separate thread
// where the above call to Wow64DisableWow64FsRedirection would not
// have any effect.
//
psi.UseShellExecute = true;
Process.Start(psi);
// Re-enable directory virtualisation if it was disabled.
if (System.Environment.Is64BitOperatingSystem)
if (sucessfullyDisabledWow64Redirect)
Wow64RevertWow64FsRedirection(ptr);
}
}
Clumsy method:
Run this batch file on the side (started from 64 bit explorer) :
:lab0
TIMEOUT /T 1 >nul
if exist oskstart.tmp goto lab2
goto lab0
:lab2
del oskstart.tmp
osk
goto lab0
Create file oskstart.tmp when you need the keyboard
For those who are facing "Could not start On-Screen Keyboard.", change your project's Platform Target to Any CPU.